HIGH insufficient loggingfastapihmac signatures

Insufficient Logging in Fastapi with Hmac Signatures

Insufficient Logging in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability

Insufficient logging in a FastAPI application that uses HMAC signatures can amplify the impact of tampering and complicate incident response. HMAC signatures are designed to guarantee integrity and authenticity: the client computes a signature over the request payload and selected headers using a shared secret, and the server recomputes and compares the signature. If logging omits signature verification outcomes, timestamp context, or key identifiers, an attacker’s manipulations may leave no auditable trace.

Consider a FastAPI endpoint that expects an X-API-Key to identify the client and an X-Signature containing the HMAC (e.g., HMAC-SHA256). Without logging the verification result, a mismatched signature might be silently ignored. An attacker who guesses or steals a weak secret can forge requests; if each forged request lacks a logged verification failure, there is no detectable pattern of unauthorized access. Logging only successful requests also creates a gap: defenders see a stream of 200 OK responses while malicious activity remains invisible.

The combination of FastAPI, HMAC signatures, and insufficient logging becomes especially risky when requests include business-impacting actions (e.g., changing email or payment details). If logs do not capture the client identity derived from the key, the signed payload content, or a unique request identifier, correlating events across services is difficult. For instance, an unsigned log might show a user profile update but not indicate whether the request was authenticated by HMAC, making it hard to distinguish legitimate changes from forged ones. Attack patterns such as request replay or tampering (e.g., altering the amount in a transaction) can therefore persist unnoticed.

Real-world analogies in the API security domain, such as findings mapped to the OWASP API Top 10, highlight that logging and monitoring failures often coexist with broken authentication and integrity checks. PCI-DSS and SOC2 expectations around audit trails further underscore the need to record security decisions. In FastAPI, this means explicitly logging key events: signature validity, key usage, request timestamps, and outcome status. Without these, even a robust HMAC implementation offers reduced visibility when incidents arise.

Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes

To address insufficient logging when using HMAC signatures in FastAPI, instrument the verification flow so each critical step is recorded with sufficient context. Log the key identifier (e.g., the API key ID without exposing the secret), the signature status, relevant headers, and a stable request identifier. Ensure logs do not store the shared secret or full raw signatures that could be reused maliciously.

Below is a complete, syntactically correct FastAPI example with HMAC-SHA256 verification and structured logging. It includes a helper to extract the key identifier, a dependency that validates the signature, and explicit log statements for success and failure cases.

import time
import hmac
import hashlib
import logging
import json
from typing import Optional
from fastapi import FastAPI, Depends, Header, HTTPException, Request
from fastapi.responses import JSONResponse

logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s %(name)s %(message)s')
logger = logging.getLogger('hmac_api')

app = FastAPI()

# In practice, store key_id -> secret securely (e.g., vault), not in source code.
SHARED_SECRETS = {
    "key_abc123": b'super-secret-key-abc123',
    "key_xyz789": b'super-secret-key-xyz789',
}

def extract_key_id(api_key: str) -> Optional[str]:
    # Example: key format could be "key_id:nonce"; parse as needed.
    if ':' in api_key:
        return api_key.split(':')[0]
    return None

async def verify_hmac(
    request: Request,
    api_key: str = Header(...),
    signature: str = Header(...),
) -> dict:
    key_id = extract_key_id(api_key)
    if not key_id or key_id not in SHARED_SECRETS:
        logger.warning(
            "signature_verification_failed",
            extra={
                "event": "signature_verification_failed",
                "key_id": key_id,
                "request_id": request.headers.get("X-Request-ID", "unknown"),
                "reason": "missing_or_unknown_key_id",
            },
        )
        raise HTTPException(status_code=401, detail="Invalid API key")

    secret = SHARED_SECRETS[key_id]
    body = await request.body()
    computed = hmac.new(secret, body, hashlib.sha256).hexdigest()
    expected = signature.strip()

    is_valid = hmac.compare_digest(computed, expected)
    if not is_valid:
        logger.warning(
            "signature_verification_failed",
            extra={
                "event": "signature_verification_failed",
                "key_id": key_id,
                "request_id": request.headers.get("X-Request-ID", "unknown"),
                "method": request.method,
                "path": request.url.path,
                "received_signature": expected,
                "computed_signature": computed,
            },
        )
        raise HTTPException(status_code=401, detail="Invalid signature")

    logger.info(
        "signature_verification_success",
        extra={
            "event": "signature_verification_success",
            "key_id": key_id,
            "request_id": request.headers.get("X-Request-ID", "unknown"),
            "method": request.method,
            "path": request.url.path,
        },
    )
    return {"key_id": key_id}

@app.post("/transfer")
async def transfer(
    payload: dict,
    auth: dict = Depends(verify_hmac),
):
    # Example business logic; ensure you also validate and sanitize payload fields.
    logger.info(
        "transfer_request_processed",
        extra={
            "event": "transfer_request_processed",
            "key_id": auth["key_id"],
            "request_id": request.headers.get("X-Request-ID", "unknown"),
            "amount": payload.get("amount"),
            "to": payload.get("to"),
        },
    )
    return JSONResponse(content={"status": "queued", "transaction_id": "tx-123"})

Key logging practices demonstrated:

  • Log at the point of signature verification, recording whether it succeeded or failed.
  • Include a stable request identifier (e.g., X-Request-ID) to trace related events across services.
  • Log key identifiers, method, and path, but avoid logging the shared secret or raw signatures.
  • On failure, log enough context (received vs computed) to investigate without exposing secrets.

For production, route these structured logs to a centralized system and set alerts on repeated verification failures. This makes patterns such as credential stuffing or replay attempts visible quickly, closing the gap created by earlier insufficient logging practices.

Frequently Asked Questions

What should I log when HMAC signature verification fails in FastAPI?
Log the key identifier (without the secret), the request method and path, a unique request identifier, the received and computed signatures (avoid storing raw signatures long-term), and the timestamp. Do not log the shared secret.
Can insufficient logging with HMAC signatures affect compliance frameworks?
Yes. Frameworks such as PCI-DSS and SOC2 require audit trails for authentication and integrity checks. Insufficient logging of HMAC verification outcomes can impede demonstrating compliance and hinder incident investigation.