Vulnerable Components in Axum with Cockroachdb
Vulnerable Components in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
When building a Rust API service with Axum and Cockroachdb, several patterns common in web backends can introduce security weaknesses. The combination often surfaces issues around authentication, input validation, and data exposure because Axum leaves routing and handler wiring to the developer, while Cockroachdb is frequently used as a distributed SQL store with strong consistency guarantees.
One prevalent issue is missing authorization checks at the handler level (BOLA/IDOR). For example, a route like /users/{user_id}/profile might query Cockroachdb using an ID taken directly from the URL without verifying that the authenticated subject owns that user_id. Because Cockroachdb returns rows efficiently, an attacker can iterate through numeric IDs to enumerate other users' data. This becomes more impactful when the query constructs SQL by string interpolation rather than using typed, parameterized statements, which can also lead to SQL injection if input validation is weak.
Input validation gaps are another critical area. Axum does not enforce a schema on deserialized request bodies beyond what Serde can parse. If the application relies solely on Serde structs and passes values directly into Cockroachdb queries via string formatting or concatenation, malicious payloads can manipulate query structure. For instance, a string field intended for a column might contain newline characters or SQL comments that alter query semantics when interpolated unsafely.
Data exposure and encryption concerns arise when sensitive fields such as API keys or personal identifiers are stored in Cockroachdb but transmitted or logged without protection. An Axum handler that returns full database rows might inadvertently expose columns that should be masked. Additionally, if TLS is not enforced on the Cockroachdb connection string (e.g., using insecure connection parameters in development that are accidentally promoted), data in transit can be exposed. The scan checks for unencrypted data pathways and excessive data exposure in responses, which are relevant when Axum services interact with Cockroachdb over misconfigured connections.
SSRF risks can appear when Axum applications use user-supplied URLs or hostnames to interact with internal Cockroachdb-compatible services or admin dashboards. If an endpoint accepts a database proxy address from a request and opens a connection, an attacker can pivot into internal networks. This is especially relevant in distributed setups where Cockroachdb clusters expose HTTP administrative interfaces internally.
Inventory management and unsafe consumption patterns also contribute. If the application constructs dynamic SQL for Cockroachdb based on feature flags or versioned APIs without strict validation, deprecated endpoints might retain broad permissions. For example, a legacy route might run SELECT * FROM accounts without row-level security, exposing all accounts when it should only expose the current tenant's rows. The scan checks for such overly permissive queries and missing tenant context in SQL statements.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
To secure Axum applications using Cockroachdb, adopt typed queries, strict input validation, and explicit tenant scoping. Below are concrete, realistic code examples that demonstrate secure patterns.
Use parameterized queries with the postgres crate to prevent SQL injection and ensure Cockroachdb receives values safely:
use axum::{routing::get, Router};
use postgres::{Client, NoTls};
use serde::Deserialize;
#[derive(Deserialize)]
struct ProfileParams {
user_id: i64,
}
async fn get_profile(
params: axum::extract::Query,
db_client: axum::extract::State<Client>
) -> Result<String, (axum::http::StatusCode, String)> {
let subject_id = get_authenticated_subject_id(); // application-specific auth helper
// Ensure the subject can only access their own profile
let row = db_client.query_opt(
"SELECT display_name, email FROM user_profiles WHERE user_id = $1 AND tenant_id = $2",
&[¶ms.user_id, &subject_id],
).map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
match row {
Some(r) => Ok(format!("{:?}", r)),
None => Err((axum::http::StatusCode::NOT_FOUND, "Not found".into())),
}
}
fn main() {
let mut client = Client::connect("host=localhost user=app dbname=app sslmode=require", NoTls).unwrap();
let app = Router::new()
.route("/profile", get(get_profile))
.with_state(client);
// run(app)
}
Validate and sanitize inputs before using them in SQL expressions. For string fields that may be used in identifiers or dynamic filters, enforce allowlists and use Cockroachdb’s format_name utilities carefully:
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct SearchParams {
filter: String,
}
fn validate_filter(filter: &str) -> bool {
// Allow only alphanumeric and underscores, reject potentially dangerous characters
filter.chars().all(|c| c.is_alphanumeric() || c == '_')
}
async fn search_items(
Query(params): Query<SearchParams>,
db_client: State<Client>
) -> Result<String, (axum::http::StatusCode, String)> {
if !validate_filter(¶ms.filter) {
return Err((axum::http::StatusCode::BAD_REQUEST, "Invalid filter".into()));
}
// Use parameterized values for data, not identifiers
let stmt = format!("SELECT id, name FROM items WHERE status = $1 AND kind = $2 AND {} = $3", params.filter);
// Prefer static queries; the above is illustrative only. Use static SQL where possible.
let rows = db_client.query(&stmt, &[&"active", &"book", &"electronics"]).unwrap();
Ok(format!("{:?}", rows))
}
Enforce tenant isolation and row-level security in Cockroachdb and reflect it in Axum handlers. If your schema includes a tenant_id column, ensure every query includes it, and avoid returning raw rows directly to the client:
use axum::{routing::post, Json};
use serde::Serialize;
#[derive(Serialize)]
struct SafeProfile {
display_name: String,
email: String,
}
async fn secure_profile(
db_client: State<Client>,
Json(payload): Json<ProfileParams>,
) -> Result<Json<SafeProfile>, (axum::http::StatusCode, String)> {
let subject_id = get_authenticated_subject_id();
let row = db_client.query_one(
"SELECT display_name, email, tenant_id FROM user_profiles WHERE user_id = $1",
&[&payload.user_id],
).map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
if row.get::<_, i64>("tenant_id") != subject_id {
return Err((axum::http::StatusCode::FORBIDDEN, "Access denied".into()));
}
let safe = SafeProfile {
display_name: row.get("display_name"),
email: row.get("email"),
};
Ok(Json(safe))
}
For connections, prefer sslmode=require or higher in production and avoid embedding secrets in source code. Use environment variables and runtime configuration to pass connection strings to Axum state, and ensure Cockroachdb certificates are validated where applicable.