HIGH logging monitoring failuresaxum

Logging Monitoring Failures in Axum

How Logging Monitoring Failures Manifests in Axum

Logging and monitoring are foundational to API security. Without them, attacks like brute force, BOLA, and data exfiltration can occur undetected, delaying response times and violating compliance frameworks such as PCI-DSS and HIPAA. In Axum, the Rust web framework known for its async and modular design, logging failures often arise from misconfigured middleware, omitted logs for critical events, or accidental exposure of sensitive data in logs.

Axum applications typically use the tower middleware stack and the tracing crate for structured logging. Common failure patterns include:

  • Missing authentication logs: Failed login attempts or token validation errors are not recorded. For example, an Axum extractor that returns Unauthorized without logging the attempt leaves brute-force attacks invisible.
  • Missing authorization logs: BOLA/IDOR attempts where a user accesses another user's resource are silently denied without a trace. Without logs, you cannot detect active exploitation.
  • No rate limiting logs: If using tower-http::limit::RateLimitLayer, failing to log when limits are hit removes a key signal of abuse.
  • Logging sensitive data: Logging full request bodies, passwords, or API tokens creates a data exposure risk (CWE-532: Insertion of Sensitive Information into Log File).
  • Unstructured logs: Using println! instead of tracing produces logs that are difficult to parse and correlate.
  • Missing request correlation: Axum does not generate request IDs by default. Without a unique identifier per request (e.g., X-Request-ID), tracing an attack across multiple endpoints is impossible.
  • Insufficient error context: Returning generic error messages without logging stack traces or request details hampers forensic analysis.
  • Exposed log endpoints: Accidentally mounting a route that serves log files (e.g., /logs) can leak sensitive information to attackers.

These failures often stem from Axum's flexibility: developers must explicitly add logging middleware and configure it correctly. A typical Axum router might omit tower_http::trace::TraceLayer entirely, resulting in no request-level logging. Or, an extractor might reject a request without logging the reason. Even when logging is present, if it's not structured or lacks key fields (user ID, resource ID), it's of limited value for security monitoring.

Axum-Specific Detection

Detecting logging and monitoring failures in Axum requires both code review and runtime inspection. While there is no substitute for examining your logging configuration, tools like middleBrick can highlight endpoints that demand rigorous logging due to their risk profile.

Manual detection involves:

  • Checking the Axum router for TraceLayer or custom logging middleware. Absence of such layers means no request logging.
  • Reviewing extractors and handlers for explicit logging of security events (authentication/authorization failures). Look for tracing::warn! or error! calls in failure paths.
  • Ensuring logs are structured (JSON) and include fields like method, uri, status, user_id, and request_id.
  • Verifying that sensitive data (passwords, tokens, PII) is never logged. Search for log::debug! or tracing::debug! that might print request bodies.
  • Confirming that logs are written to a secure, centralized store (e.g., Elasticsearch) and that log rotation is configured.
  • Checking for a request ID middleware that generates and propagates X-Request-ID.

Using middleBrick: middleBrick's black-box scanner cannot directly access your server logs, but its findings guide you to audit logging for high-risk endpoints. For example, if middleBrick detects a BOLA vulnerability (category: BOLA/IDOR) or missing rate limiting, those endpoints must have detailed logging to detect active exploitation. Similarly, if middleBrick flags data exposure (e.g., an endpoint returning excessive user data), ensure that access to that endpoint is logged with user context. middleBrick's Inventory Management check also ensures you have visibility into your API surface, which is the first step to knowing what to monitor. After scanning, review the prioritized findings and for each critical or high-severity issue, verify that your Axum application logs all relevant request details (without sensitive data) for that endpoint.

Axum-Specific Remediation

Remediating logging and monitoring failures in Axum involves implementing structured logging, adding security-relevant log events, and integrating with monitoring systems. Below are concrete steps with code examples.

1. Enable structured logging with tracing and TraceLayer

Axum integrates seamlessly with the tracing ecosystem. Use tracing_subscriber to configure JSON logging and tower_http::trace::TraceLayer to log HTTP requests/responses.

use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

fn init_logging() {
    tracing_subscriber::registry()
        .with(
            EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| EnvFilter::new("info"))
        )
        .with(tracing_subscriber::fmt::layer().json())
        .init();
}

Add TraceLayer to your router. Configure it to log metadata but not bodies (to avoid sensitive data):

use axum::Router;
use tower_http::trace::TraceLayer;
use tracing::info_span;

let app = Router::new()
    .route("/login", post(login))
    .layer(
        TraceLayer::new_for_http()
            .make_span_with(|request: &axum::http::Request<_>| {
                info_span!(
                    "http_request",
                    method = %request.method(),
                    uri = %request.uri(),
                    user_agent = ?request.headers().get("User-Agent"),
                    request_id = ?request.headers().get("X-Request-ID")
                )
            })
            .on_response(|response: &axum::http::Response<_>, latency: std::time::Duration, _span: &tracing::Span| {
                tracing::info!(
                    status = response.status().as_u16(),
                    latency_ms = latency.as_millis(),
                    "response"
                );
            })
            .on_failure(|error: &tower_http::BoxError, latency: std::time::Duration, _span: &tracing::Span| {
                tracing::error!(
                    error = %error,
                    latency_ms = latency.as_millis(),
                    "request failed"
                );
            })
    );

2. Log authentication and authorization failures

In your extractors or handlers, explicitly log security events. For example, an authentication extractor that fails should log the attempt without revealing sensitive data:

use axum::{async_trait, extract::FromRequestParts, http::{request::Parts, StatusCode}, response::IntoResponse};

#[async_trait]
impl<S> FromRequestParts<S> for User
where
    S: Send + Sync,
{
    type Rejection = (StatusCode, &'static str);

    async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, Self::Rejection> {
        let token = parts
            .headers
            .get("Authorization")
            .ok_or_else(|| (StatusCode::UNAUTHORIZED, "Missing token"))?
            .to_str()
            .map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid token"))?;

        if validate_token(token).await {
            Ok(User { id: "user123".to_string() })
        } else {
            // Log failed auth: do not log the full token
            tracing::warn!(
                token_type = ?token.split('.').next(), // for JWT, log header part only
                uri = %parts.uri,
                method = %parts.method,
                "Authentication failed"
            );
            Err((StatusCode::UNAUTHORIZED, "Invalid token"))
        }
    }
}

Similarly, log authorization failures (BOLA attempts):

async fn get_resource(
    user: User,
    axum::extract::Path(id): axum::extract::Path<String>,
) -> Result<Json<String>, (StatusCode, &'static str)> {
    if user.id == id || user.roles.contains(&"admin".to_string()) {
        Ok(Json(format!("Resource {} data", id)))
    } else {
        tracing::warn!(
            user_id = %user.id,
            resource_id = %id,
            "Authorization failed: user attempted to access resource they don't own"
        );
        Err((StatusCode::FORBIDDEN, "Access denied"))
    }
}

3. Generate and propagate request IDs

Add middleware to assign a unique X-Request-ID to each request. This allows you to correlate logs across services.

use axum::{middleware::Next, response::Response, Body, Request};
use std::sync::Arc;
use uuid::Uuid;

async fn request_id_middleware(
    mut request: Request<Body>,
    next: Next,
) -> Response<Body> {
    let request_id = Uuid::new_v4().to_string();
    request.headers_mut().insert(
        "X-Request-ID",
        axum::http::HeaderValue::from_str(&request_id).unwrap(),
    );
    next.run(request).await
}

// In router:
let app = Router::new()
    .layer(axum::middleware::from_fn(request_id_middleware))
    .layer(TraceLayer::new_for_http().make_span_with(|request: &Request<_>| {
        let request_id = request
            .headers()
            .get("X-Request-ID")
            .and_then(|h| h.to_str().ok())
            .unwrap_or("unknown");
        info_span!(
            "http_request",
            method = %request.method(),
            uri = %request.uri(),
            request_id = request_id,
        )
    }));

4. Integrate with monitoring and alerting

Export logs to a centralized system (e.g., Elasticsearch, Splunk) and set up alerts for spikes in authentication failures, authorization failures, or 5xx errors. You can also export metrics using axum-prometheus:

use axum_prometheus::{self, PrometheusMetricLayer, metrics};

let (prometheus_layer, metrics) = PrometheusMetricLayer::pair();

let app = Router::new()
    .layer(prometheus_layer)
    .route("/metrics", get(metrics.clone().serve_metrics));

Finally, secure your logs: set strict file permissions, encrypt log storage, and rotate logs regularly. In production, avoid logging to stdout/stderr without a log collector that handles rotation and retention.

Frequently Asked Questions

Can middleBrick directly detect if my Axum API has logging misconfigurations?
No. middleBrick is a black-box scanner that tests your API from the outside. It cannot inspect your server-side logging configuration. However, it identifies vulnerable endpoints (e.g., those with BOLA, missing rate limiting, or data exposure) that should be monitored. For each finding, you should audit the corresponding Axum handlers and middleware to ensure comprehensive logging without sensitive data.
How do I prevent logging sensitive data like passwords or tokens in my Axum application?
Avoid logging request/response bodies unless absolutely necessary. When logging errors, sanitize the error messages. Use structured logging and be explicit about which fields you log. For example, in an authentication extractor, log only a token type or hash, never the full token. Configure your logging framework (e.g., tracing_subscriber) to filter out sensitive fields by default, and review log statements to ensure no sensitive data is inadvertently included.