DEV Community

Rxkov
Rxkov

Posted on Originally published at blog.mago.team

API Log Redaction: Filtering After Serialization Does Not Work

Your API is logging Bearer tokens to Splunk right now. Not because your team misconfigured something, but because the logging middleware runs in the wrong order and the framework already serialized the Authorization header before your redaction rule could be consulted.

The standard advice to "redact sensitive fields in logs" is architecturally broken for structured logging. Most frameworks serialize the full request object before applying any filter. Authentication tokens and credentials have already transited memory, IPC, and the log aggregator pipeline before any redaction rule can run.

Framework defaults ship with credential-capturing logging

CVE-2025-62232 (CVSS 7.5), assigned to Apache APISIX in 2025, documents what had been happening silently since version 1.0: the basic-auth plugin logs plaintext usernames and passwords at INFO level by default. The problem affects all versions from 1.0 through 3.14. No additional configuration is needed to leak credentials; installing the plugin and enabling basic authentication on any route is sufficient.

Spring Boot Actuator exposes /actuator/env to any HTTP client that reaches the management port. That endpoint lists all environment variables, including API keys embedded in connection strings. Django with DEBUG=True logs the full headers of every request, including the Authorization header, to the default console handler.

Morgan's "combined" format logs the full URL including query strings. OAuth tokens are frequently passed as ?access_token=... by misconfigured client SDKs or by developers following outdated examples. These are not configuration mistakes by careless teams: they are the default options of frameworks processing billions of requests daily. The CVE received CVSS 7.5 because the applicable category is CWE-532: Insertion of Sensitive Information into Log File.

The serialization race that makes post-logging redaction ineffective

Pino, the most popular structured logger in the Node.js ecosystem, applies the redact option to the JSON string after serialization, not to the in-memory object graph. The sensitive value has already been written to the transport buffer before the mask runs. Any system the log transits during that interval may have received the unredacted payload, including the network transport to the log agent, the message queue before the redaction processor, and the aggregator that indexes the payload before applying filters.

// This does NOT protect against leaking to the transport buffer
const logger = pino({
  redact: ['req.headers.authorization']
})
logger.info({ req }, 'incoming request')
// The full string including the token was already serialized before redact ran
Enter fullscreen mode Exit fullscreen mode

Winston follows the same pattern: transports receive the formatted log entry after all transforms. During any uncaught exception in the formatter, a network transport sends the unredacted string as part of the error message.

CVE-2023-33001 (CVSS 7.5, Jenkins HashiCorp Vault Plugin) and CVE-2023-30513 (CVSS 7.5, Jenkins Kubernetes Plugin) document exactly this pattern in CI/CD pipelines. In both cases, credentials appear in raw build logs because the durable task system in push mode writes to the stream before masking is applied. Jenkins displays the masked string in the page render. The raw log stream, accessible via REST API or filesystem, does not go through the same transformation.

Log aggregation turns one leaking API into an org-wide credential store

The exposure surface of a single endpoint that logs authorization tokens is not the API process. It is every engineer, analyst, and third-party integration with read access to the Splunk index or ELK cluster where those logs are shipped.

In 2022, Aqua Security researchers found that the Travis CI public API exposed more than 770 million build log lines. Analysis of 20,000 logs uncovered more than 73,000 tokens, keys, and credentials, including GitHub, AWS, and Docker Hub tokens in plaintext. Travis CI characterized the behavior as "by design."

H1 #215625 documents a GitHub PAT from a HackerOne engineer exposed in public Travis CI build logs for a rubysec project. An external researcher found the token before any internal detection system did. H1 #503283 documents a debug URL at slackb.com that exposed Slack real-time error logs to external observers without authentication, including session IDs, team IDs, and API call metadata. H1 #496937 documents a Grammarly employee GitHub token in Travis CI build logs, resulting in access to internal company repositories.

One token in one log line, visible to anyone with index access, is the most underestimated exfiltration vector in API security.

OWASP API3:2023 and GDPR both classify log pipelines as unauthorized disclosure channels

OWASP API Security API3:2023 (Broken Object Property Level Authorization) covers server-side filtering failures that allow sensitive properties to exit through any channel, not just API responses. Log pipelines are implicit disclosure channels covered by this category. The logic is straightforward: if a field should not appear in an API response, it should not appear in an access log.

GDPR Article 32 requires appropriate technical measures for all processing of personal data, including log pipelines. CVE-2019-11250 (Kubernetes client-go, CVSS 6.5) demonstrates the principle at infrastructure level: bearer tokens logged at verbosity level 7 or higher are readable by any cluster operator with log access. LGPD Article 49 imposes equivalent requirements with fines of up to 2% of Brazilian annual revenue, capped at BRL 50 million per violation. The GDPR Enforcement Tracker records 3,206 enforcement actions totaling EUR 2.72 billion in fines.

The correct fix is architectural: sanitize the source, not the output

Effective remediation is not configuring the logger to mask field X. It is ensuring the raw request object never reaches the logger, by replacing sensitive fields with sanitized equivalents in the middleware layer that runs before the logging middleware in the stack.

In Express, the redaction middleware must mount before morgan and winston. Middleware execution order is the security control, not the logger configuration. In Python, logging.Filter must be attached to the Logger, not the Handler.

# Wrong: filter on Handler arrives after formatting
handler = logging.StreamHandler()
handler.addFilter(RedactFilter())

# Correct: filter on Logger, before serialization
logger = logging.getLogger('api')
logger.addFilter(RedactFilter())
Enter fullscreen mode Exit fullscreen mode

The OpenTelemetry redaction processor provides the correct architectural model: it operates on the span attribute map before export, preventing sensitive values from entering the OTLP payload. The safest approach is to define a LoggableRequest value object that excludes Authorization, Cookie, and body fields by construction. The type system enforces redaction; runtime filters depend on nobody forgetting to register them. The MAGO team tool (mago.team) analyzes API logging configurations to identify which frameworks and plugins capture credentials by default, before any redaction rule can run.

Audit the middleware execution order in every service. Identify every path where a raw request object reaches a logger and replace those paths with sanitized value objects before serialization occurs. The correct guarantee is not "credentials are masked in stored logs": it is "credentials never enter the logging pipeline."

Top comments (0)