HIGH timing attackactixcockroachdb

Timing Attack in Actix with Cockroachdb

Timing Attack in Actix with Cockroachdb — how this specific combination creates or exposes the vulnerability

A timing attack in an Actix service that uses CockroachDB can occur when authentication or lookup operations perform differently depending on whether a user exists and whether a supplied credential matches. In such flows, an attacker can measure response times to infer information such as valid usernames or the correctness of a password, even when the API returns the same high-level error message.

Actix-web does not introduce a timing channel by default, but application code can inadvertently create one. For example, comparing a user-supplied token or password using a simple equality check against a value retrieved from CockroachDB can short-circuit on the first mismatching byte. If user lookup by username is performed with a SQL query like SELECT id, password_hash FROM users WHERE username = $1, an attacker can send many requests with guessed usernames and observe whether a slightly longer response time occurs when the username exists and the comparison proceeds to the hash. This extra time can indicate a valid user, enabling username enumeration.

When CockroachDB is the backend, the presence or absence of rows and the shape of query execution can also affect timing. An endpoint that first queries for a record and then conditionally compares values in Rust can exhibit measurable differences if the database returns results versus empty results, or if prepared statement caching interacts differently with existent versus non-existent keys. Even when the API returns a consistent HTTP status code, network and processing variations can be enough for a carefully instrumented attacker to build a statistical profile.

To illustrate a vulnerable pattern in Actix with CockroachDB, consider a login handler that retrieves a row and compares the password hash in Rust:

use actix_web::{post, web, HttpResponse};
use sqlx::postgres::PgPoolOptions;
use sqlx::FromRow;

#[derive(FromRow)]
struct User {
    id: i32,
    password_hash: String,
}

#[post("/login")]
async fn login(
    pool: web::Data<sqlx::PgPool>,
    form: web::Form<LoginForm>,
) -> HttpResponse {
    let user = sqlx::query_as::<_, User>("SELECT id, password_hash FROM users WHERE username = $1")
        .bind(&form.username)
        .fetch_optional(pool.get_ref())
        .await
        .unwrap_or_default();

    if let Some(user) = user {
        // This comparison can vary in time based on input and hash.
        if user.password_hash == form.password {
            return HttpResponse::Ok().finish();
        }
    }
    HttpResponse::Unauthorized().finish()
}

In this snippet, the equality check on password_hash is vulnerable to timing differences. An attacker can observe whether a valid username leads to a hash comparison versus an early return. The fix is to use a constant-time comparison regardless of whether the user exists, and to avoid branching on sensitive data derived from the database.

Additionally, if the API exposes unauthenticated endpoints that return different response sizes or latencies for existing versus non-existing resources (e.g., user profile endpoints), an attacker can use timing to confirm resource ownership. With CockroachDB, ensuring that queries for non-existent resources take similar time as queries for existent ones (by introducing dummy work or consistent delays) can mitigate this, but the preferred approach is to remove timing differences in the application logic rather than relying on server-side delays.

middleBrick can support risk assessment for such issues by scanning the Actix endpoint and, when an OpenAPI spec is available, cross-referencing runtime behavior with spec definitions. In LLM/AI Security contexts, it can detect whether system prompt leakage or output anomalies might be amplified by timing differences, but it does not fix the implementation. Developers must apply constant-time algorithms and ensure that unauthenticated endpoints do not leak existence via timing.

Cockroachdb-Specific Remediation in Actix — concrete code fixes

Remediation focuses on removing data-dependent branching and ensuring that operations that reach the database take similar time regardless of input. Below are concrete, idiomatic Actix patterns that address timing risks when interacting with CockroachDB.

Use constant-time comparison for secrets

Replace standard equality checks with a constant-time comparison function. In Rust, the subtle crate provides ConstantTimeEq for this purpose. This ensures that comparing a hash or token does not leak information via timing.

use actix_web::{post, web, HttpResponse};
use sqlx::postgres::PgPoolOptions;
use sqlx::FromRow;
use subtle::ConstantTimeEq;

#[derive(FromRow)]
struct User {
    id: i32,
    password_hash: String,
}

#[post("/login")]
async fn login(
    pool: web::Data<sqlx::PgPool>,
    form: web::Form<LoginForm>,
) -> HttpResponse {
    let user = sqlx::query_as::<_, User>("SELECT id, password_hash FROM users WHERE username = $1")
        .bind(&form.username)
        .fetch_optional(pool.get_ref())
        .await
        .unwrap_or_default();

    // Retrieve the stored hash or a dummy of the same length.
    let stored_hash = user.map(|u| u.password_hash).unwrap_or_else(|| "0".repeat(64));
    // Constant-time comparison regardless of user existence.
    let valid = stored_hash.ct_eq(&form.password).into();

    if valid {
        HttpResponse::Ok().finish()
    } else {
        HttpResponse::Unauthorized().finish()
    }
}

This approach ensures that the comparison always takes the same amount of time, even when the user does not exist, because we substitute a same-length dummy hash when the row is absent.

Consistent query behavior for existence checks

If you must check existence, perform work that is independent of the secret. Avoid early returns based on row presence when handling sensitive operations. For non-sensitive lookups, you can still use standard queries, but for authentication, prefer the constant-time pattern above.

Avoid branching on sensitive database-derived values

Do not branch logic based on whether a row was found when that row contains secrets. Instead, unify the code path so that the same operations execute for valid and invalid users, differing only in the final comparison result.

Use parameterized queries and prepared statements

Always use SQLx’s parameterized queries to avoid injection and ensure stable execution paths. CockroachDB benefits from prepared statement caching, and using placeholders keeps query planning consistent.

let user: Option<User> = sqlx::query_as(
        "SELECT id, password_hash FROM users WHERE username = $1",
    )
    .bind(username)
    .fetch_optional(pool.get_ref())
    .await?;

Consider server-side delays only as a last resort

While adding artificial delays can obscure timing differences, it is fragile and can harm availability. Prefer fixing the application logic to remove data-dependent timing variation. middleBrick’s scans can highlight inconsistent response patterns that suggest timing risks, but remediation must be implemented in code.

Frequently Asked Questions

Can a timing attack reveal valid usernames even when the API always returns 401?
Yes. If the server performs a database lookup and a comparison only when the username exists, the extra processing time can make responses for valid usernames measurably longer, enabling username enumeration despite uniform error responses.
Does using an ORM or query builder prevent timing attacks with CockroachDB in Actix?
Not by itself. ORMs can still produce data-dependent branches if the application compares secrets after fetching rows. Use constant-time comparison and avoid branching on sensitive values regardless of the query layer.