HIGH time of check time of useaxumcockroachdb

Time Of Check Time Of Use in Axum with Cockroachdb

Time Of Check Time Of Use in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability

Time Of Check Time Of Use (TOCTOU) occurs in an Axum service using CockroachDB when an authorization check and a subsequent data mutation are not performed as a single, atomic operation. A common pattern that creates the vulnerability is reading a row to verify ownership or permissions, then using that read result to decide whether to execute a write. Between the read check and the write, an attacker can change the underlying data in CockroachDB, bypassing the intended guardrails.

Consider an endpoint that deletes a user document. The route first queries CockroachDB to confirm the document belongs to the requesting user. If the check passes, the route proceeds to delete the document. Because the check and the delete are separate SQL statements, a malicious actor with a valid session can alter the document’s ownership between the two statements, leading the server to delete a resource the user should not access. This pattern maps to the BOLA/IDOR category in middleBrick’s 12 checks and would be surfaced as a high-severity finding with remediation guidance to consolidate logic into a single conditional operation.

In Axum, this often manifests in handler code that performs a select, validates a condition, and then executes a delete or update. The non-atomic sequence means the security decision made at check time does not hold at use time. CockroachDB’s strong consistency helps ensure each statement sees the latest committed data, but it does not prevent this class of bug because the server still processes each statement independently. An attacker does not need to exploit a race condition in the database itself; they exploit the gap in application logic where state is re-evaluated after the initial check.

When this pattern is combined with middleBrick’s unauthenticated scan, the tool can detect the existence of the split authorization logic by analyzing the OpenAPI spec and correlating runtime behavior. The scan does not fix the issue, but it provides prioritized findings with severity and remediation guidance, pointing developers toward atomic operations or server-side filtering that keeps authorization and mutation together. This is especially important for endpoints that handle sensitive data or perform financial or administrative actions, where the cost of a BOLA/IDOR exploit is high.

Developers should structure Axum handlers so that the condition and the write are expressed in a single database statement or transaction. Instead of loading a row and then acting on it, push the condition into the SQL WHERE clause so that the update or delete only matches rows the user is allowed to modify. This eliminates the window between check and use and aligns with secure coding practices for database interactions in Rust web services.

Cockroachdb-Specific Remediation in Axum — concrete code fixes

To eliminate TOCTOU in Axum with CockroachDB, refactor authorization checks to be part of the mutation itself. Below are two concrete approaches with syntactically correct Rust examples using sqlx and CockroachDB’s PostgreSQL-compatible wire protocol.

Approach 1: Conditional DELETE with a WHERE clause that encodes ownership

use axum::extract::Path;
use sqlx::postgres::PgPoolOptions;
use uuid::Uuid;

async fn delete_document_handler(
    Path(id): Path,
    user_id: axum::extract::State<Uuid>,
    pool: axum::extract::State<sqlx::PgPool>,
) -> Result<impl axum::response::IntoResponse, (axum::http::StatusCode, String)> {
    let pool = pool.0;
    let user = user_id.0;

    // Single atomic operation: delete only if the document belongs to the user
    let result = sqlx::query!(
        r#"
        DELETE FROM documents
        WHERE id = $1 AND owner_id = $2
        "#,
        id,
        user
    )
    .execute(&pool)
    .await
    .map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;

    if result.rows_affected() == 0 {
        return Err((axum::http::StatusCode::FORBIDDEN, "Not authorized".to_string()));
    }

    Ok(axum::http::StatusCode::NO_CONTENT)
}

This pattern ensures that the check for ownership and the deletion occur in one statement. CockroachDB evaluates the WHERE clause atomically, so there is no window for the document’s ownership to change between check and delete. The handler returns 403 if no rows are affected, which correctly signals authorization failure without leaking information about existence.

Approach 2: Use a transaction with explicit read followed by conditional write

If application logic requires reading the row before deciding how to mutate it, wrap both operations in a CockroachDB transaction to serialize access and re-check conditions inside the transaction. This prevents TOCTOU because no other transaction can commit changes to the affected rows under the default isolation level when appropriate conflict handling is used.

use sqlx::Transaction;
use sqlx::postgres::PgPool;
use sqlx::postgres::TransactionPostgres;

async fn update_profile_handler(
    user_id: Uuid,
    pool: &PgPool,
    new_email: String,
) -> Result<(), String> {
    let mut tx = pool.begin().await.map_err(|e| e.to_string())?;

    // Read inside the transaction to establish a consistent view
    let row = sqlx::query!(
        r#"SELECT id, email FROM profiles WHERE user_id = $1 FOR UPDATE"#,
        user_id
    )
    .fetch_optional(&mut *tx)
    .await
    .map_err(|e| e.to_string())?;

    let Some(row) = row else { return Err("Not found".to_string()) };

    // Re-check business rules using the transaction’s consistent snapshot
    if row.email != new_email {
        sqlx::query!(
            r#"UPDATE profiles SET email = $1 WHERE user_id = $2"#,
            new_email,
            user_id
        )
        .execute(&mut *tx)
        .await
        .map_err(|e| e.to_string())?;
    }

    tx.commit().await.map_err(|e| e.to_string())?;
    Ok(())
}

The FOR UPDATE clause locks the selected row for the duration of the transaction, preventing concurrent modifications that could violate authorization assumptions. This approach is heavier than the conditional DELETE but necessary when the logic requires inspecting related rows before writing.

In both cases, avoid patterns where you first SELECT into a struct, validate fields in Rust, and then call a second query to UPDATE or DELETE. Those split operations reintroduce the TOCTOU risk. middleBrick’s scans can highlight these patterns in your OpenAPI definitions and runtime tests, guiding you toward atomic designs that keep authorization close to the data layer.

Frequently Asked Questions

Why does a simple SELECT followed by a DELETE in Axum create a security risk?
Because the authorization decision is made at check time based on the state of CockroachDB, but the action occurs at use time after a separate DELETE statement. An attacker can modify the data between the two statements, bypassing the check.
Does using CockroachDB transactions fully prevent TOCTOU?
When transactions include proper locking (e.g., SELECT ... FOR UPDATE) and re-check conditions inside the transaction, TOCTOU is prevented. Conditional statements that combine check and mutation in a single query are simpler and reduce risk further.