Information Disclosure in Actix with Cockroachdb
Information Disclosure in Actix with Cockroachdb — how this specific combination creates or exposes the vulnerability
Information Disclosure occurs when an API returns data that should be restricted or hidden from the caller. In an Actix web service that uses Cockroachdb as the backend datastore, this risk arises from a combination of application-level data handling and database-side configuration. Actix routes typically deserialize request parameters, query Cockroachdb, and serialize results back to the client. If query construction does not enforce field-level authorization and the database contains columns with sensitive or overly broad data, the response may inadvertently include information such as internal identifiers, email addresses, or administrative flags.
With Cockroachdb, the risk is amplified when schemas include columns that are not masked or filtered before transmission. For example, a users table might contain columns like email, password_hash, or role. If an endpoint like GET /users/{id} retrieves a row using a broad SELECT * and returns the full row as JSON, sensitive fields can be exposed to any authenticated or unauthenticated caller depending on the route’s security setup. Even when authentication is enforced, missing property-level checks can enable IDOR-style access where one user can view another user’s sensitive columns.
The interaction between Actix and Cockroachdb also involves serialization behavior. Cockroachdb returns values in a binary format that, when mapped into Rust structs, may include fields that are unintentionally public. If the Actix handler passes the entire query result directly to a JSON serializer without pruning or transforming sensitive attributes, confidential data can leak into HTTP responses. This is particularly concerning when error messages or stack traces inadvertently surface database column names or internal row structures, providing an attacker with reconnaissance details useful for further exploitation.
Additionally, the use of Cockroachdb-specific features such as array or JSONB columns can introduce disclosure risks if these complex types are returned in full. An endpoint that returns a user profile including a JSONB preferences field might expose internal business logic or configuration details if the data is not sanitized. Because Actix applications often rely on strong typing and automatic deserialization, developers might assume that what is stored is already safe, overlooking the need for explicit output filtering.
In a black-box scan context, middleBrick tests this combination by probing endpoints that interact with Cockroachdb, checking for unauthenticated data exposure, excessive data returned in responses, and whether authorization checks are consistently applied at the property level. Findings include whether responses contain sensitive fields like keys, emails, or internal statuses, and whether access controls align with the principle of least privilege across all data dimensions.
Cockroachdb-Specific Remediation in Actix — concrete code fixes
Remediation centers on precise data retrieval and strict serialization practices. Instead of selecting all columns, explicitly select only the fields required for the response. Implement property-level authorization so that sensitive columns are excluded based on caller context. Below are concrete examples that demonstrate secure patterns when using Cockroachdb with Actix.
1. Select only required fields
Avoid SELECT *. Define queries that return only the columns needed by the client. This reduces the attack surface and prevents accidental exposure of sensitive fields.
use sqlx::postgres::PgRow;
use sqlx::Row;
async fn get_user_public(pool: &sqlx::PgPool, user_id: i32) -> Result<UserPublic, sqlx::Error> {
let row = sqlx::query(
"SELECT id, username, display_name FROM users WHERE id = $1"
)
.bind(user_id)
.fetch_one(pool)
.await?;
Ok(UserPublic {
id: row.get("id"),
username: row.get("username"),
display_name: row.get("display_name"),
})
}
2. Use separate response structs
Define distinct structs for internal database models and external responses. This ensures that sensitive fields such as password hashes or API keys are never serialized.
#[derive(sqlx::FromRow)]
struct UserDb {
id: i32,
username: String,
email: String,
password_hash: String,
role: String,
}
#[derive(serde::Serialize)]
struct UserResponse {
id: i32,
username: String,
display_name: String,
}
async fn get_user_response(pool: &sqlx::PgPool, user_id: i32) -> Result<UserResponse, sqlx::Error> {
let row: UserDb = sqlx::query_as("SELECT id, username, email, password_hash, role FROM users WHERE id = $1")
.bind(user_id)
.fetch_one(pool)
.await?;
// Map to a response that excludes sensitive fields
Ok(UserResponse {
id: row.id,
username: row.username,
display_name: derive_display_name(&row.username),
})
}
3. Apply property-level authorization
Even when data is requested, ensure that the caller is authorized to view each field. Do not rely solely on row-level filters (e.g., user owns the row). Explicitly omit or mask fields based on role or consent.
async fn get_user_authorized(
pool: &sqlx::PgPool,
requester_id: i32,
target_id: i32,
) -> Result<UserAuthorized, sqlx::Error> {
let row = sqlx::query(
"SELECT id, username, email, role, is_email_verified FROM users WHERE id = $1"
)
.bind(target_id)
.fetch_one(pool)
.await?;
let requester_role = get_role(pool, requester_id).await?;
// Build response based on authorization
let email = if requester_role == "admin" || requester_id == target_id {
Some(row.email)
} else {
None
};
Ok(UserAuthorized {
id: row.id,
username: row.username,
email,
is_email_verified: if requester_role == "admin" { Some(row.is_email_verified) } else { None },
})
}
4. Avoid exposing internal identifiers in URLs
When using Cockroachdb, prefer natural keys or opaque identifiers in URLs instead of internal primary keys. If internal IDs are exposed, ensure they are not sequential or guessable to prevent IDOR across users.
// Use a UUID public_id instead of an integer ID in routes
async fn get_user_by_public_id(pool: &sqlx::PgPool, public_id: &str) -> Result<UserResponse, sqlx::Error> {
let row: UserDb = sqlx::query_as("SELECT id, username, email, password_hash, role FROM users WHERE public_id = $1")
.bind(public_id)
.fetch_one(pool)
.await?;
Ok(convert_to_response(row))
}
5. Sanitize error messages
Ensure that database errors do not leak schema or column details. Use generic error messages in production and log detailed errors server-side only.
async fn safe_query(pool: &sqlx::PgPool, user_id: i32) -> Result<UserResponse, &'static str> {
match sqlx::query_as(<UserDb>("SELECT id, username, email FROM users WHERE id = $1").bind(user_id).fetch_optional(pool)).await {
Ok(Some(row)) => Ok(convert_to_response(row)),
Ok(None) => Err("Not found"),
Err(_) => Err("Internal server error"),
}
}
By combining explicit column selection, segregated response models, and context-aware authorization, you mitigate information disclosure risks while maintaining functionality with Cockroachdb-backed Actix services.