Formula Injection in Axum with Cockroachdb
Formula Injection in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
Formula Injection occurs when user-controlled data is interpreted as part of a query language or formula, leading to unintended execution or data exposure. In an Axum application using CockroachDB, this risk arises when dynamic values such as identifiers, table names, or SQL fragments are concatenated into statements without proper safeguards. CockroachDB, while PostgreSQL-wire compatible, enforces strict SQL semantics and does not support some escaping behaviors found in other databases, making it important to validate and parameterize inputs carefully.
Consider an endpoint that fetches tenant data by name. If the tenant identifier is taken directly from a request and inserted into a SQL string, an attacker can supply a value like '; SELECT * FROM users WHERE deleted_at IS NOT NULL; --. Because Axum does not automatically sanitize inputs and CockroachDB processes the resulting SQL as written, the injected clause may execute, exposing data or altering behavior. Even when using the built-in SQL formatting macros in Rust, concatenation via format! bypasses parameterization and reintroduces risk.
Another scenario involves ORDER BY or column names derived from user input, such as sorting a listing by a query parameter. Direct string interpolation like format!("ORDER BY {}", column_name) allows an attacker to inject additional SQL fragments. CockroachDB will accept and execute the modified statement, potentially leaking sensitive columns or enabling privilege escalation if the injected clause references system tables or administrative views.
The combination of Axum’s type-driven routing and CockroachDB’s strict SQL compliance means developers must explicitly guard all dynamic content. Relying on manual escaping or partial validation is insufficient; only parameterized queries and strict allowlisting of identifiers prevent injection paths. Without these controls, an unauthenticated attacker can probe endpoints and observe differences in timing, error messages, or result structure to refine injection attempts.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation centers on using parameterized queries and strict allowlisting, leveraging Axum extractors and the tokio_postgres interface compatible with CockroachDB. Never embed user values directly into SQL strings, even for identifiers or table names.
Parameterized Query Example
The following example demonstrates a safe Axum handler using a prepared statement with placeholders for values. This approach ensures that user input is treated strictly as data, not executable SQL.
use axum::{routing::get, Router, extract::Query};
use serde::Deserialize;
use tokio_postgres::{NoTls, Client};
#[derive(Deserialize)]
struct TenantParams {
name: String,
}
async fn get_tenant_by_name(
Query(params): Query,
client: &Client,
) -> Result<String, (StatusCode, String)> {
// Safe: parameterization prevents injection
let row = client
.query_one(
"SELECT id, email FROM tenants WHERE name = $1",
&[¶ms.name],
)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let id: String = row.get(0);
let email: String = row.get(1);
Ok(format!("Tenant: {} ({})", id, email))
}
#[tokio::main]
async fn main() {
let (client, connection) = tokio_postgres::connect(
"host=localhost user=cockroachdb dbname=mydb sslmode=disable",
NoTls,
)
.await
.expect("connect failed");
tokio::spawn(async move {
if let Err(e) = connection.await {
eprintln!("connection error: {}", e);
}
});
let app = Router::new().route("/tenant", get(get_tenant_by_name));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Identifier Allowlisting Example
When column or table names must be dynamic, maintain a strict allowlist and map user input to safe identifiers. This prevents injection while still enabling flexible routing.
use axum::routing::get;
use serde::Deserialize;
use tokio_postgres::{Client, NoTls};
async fn get_sorted_tenants(
Query(params): Query<SortParams>,
client: &Client,
) -> Result<String, (StatusCode, String)> {
// Allowlist for column names; do not interpolate directly
let allowed = ["id", "email", "created_at"];
if !allowed.contains(¶ms.column.as_str()) {
return Err((StatusCode::BAD_REQUEST, "Invalid column".into()));
}
// Safe: column name derived from allowlist, value parameterized
let query = format!("SELECT id, email FROM tenants ORDER BY {}$1", params.column);
let rows = client
.query(&query, &[¶ms.direction])
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Process rows...
Ok(format!("Sorted by {}", params.column))
}
#[derive(Deserialize)]
struct SortParams {
column: String,
direction: String, // "ASC" or "DESC"
}
For production use with the middleBrick CLI (middlebrick scan <url>) or GitHub Action, run scans against staging endpoints to detect such issues before deployment. The Pro plan supports continuous monitoring and CI/CD integration to catch regressions early, while the MCP Server enables in-IDE scanning when iterating on Axum routes.