Last reviewed: November 5, 2026 (Asia/Shanghai); sources fetched November 4 UTC.
Direct answer
Prevent log injection in a coding-agent patch by keeping event names, severity, outcomes, and field names under application control. Put approved external values into bounded fields and serialize the complete record with an encoder suited to the collector’s format. Then test the emitted bytes and the collected event: one rejected request must not become a forged success, a second event, or a different severity.
MITRE CWE-117 defines the weakness as missing or incorrect neutralization of special elements when external input is written to logs. Consequences include false entries, misleading interpretation, and corrupted input to automated processors. The OWASP Logging Cheat Sheet recommends structured formats such as JSON, appropriate output encoding, bounded input values, and verification of logging failures.
A patch that adds logger.warning("rejected %s", submitted_value) still needs review of the final formatter. Parameter substitution alone does not establish record framing or escape carriage returns and newlines. Review the whole path from external value to serializer, collector, alert rule, and viewer.
Who this is for
Use this workflow when an agent changes authentication diagnostics, import errors, request logging, exception handling, background workers, or audit events. Search the changed paths for logger calls, print, response-body dumps, exception strings, caller-supplied field maps, and fallback writes to stderr.
The example records an import rejection and an optional, non-sensitive batch label. It addresses how diagnostics preserve the event’s meaning. For values that also enter HTTP headers, apply the separate response-header review . A safe log does not validate the HTTP response. For credentials available to the agent or its CI job, use the site’s permission and secret boundaries .
Key takeaways
- Keep event identity, severity, outcome, time, and trusted correlation context separate from submitted text.
- Use a real structured encoder; manually placing a value between JSON quotation marks is unsafe.
- Define field limits and a final encoded-byte ceiling. Escaping can make output larger than input.
- Omit sensitive values before serialization. JSON escaping does not redact a password or token.
- Preserve a security event when an optional field is malformed; record that the field was omitted.
- Verify record boundaries and field meaning after collection, including exception and outage paths.
Contract details to verify
Decide which fields can carry external data
OWASP calls for consistent event classifications, field names, types, lengths, and enough context to establish when, where, who, and what. Write those decisions before asking the agent to edit logging code. This example uses the following application policy; its limits are recommendations rather than requirements imposed by OWASP or Python.
| Field or boundary | Example contract |
|---|---|
| Event, severity, outcome, reason | Fixed values selected by this rejection call site |
| Log time | Server-generated UTC time; distinct from any reported client time |
| Interaction ID | Server-created context, 1–64 ASCII letters, digits, underscores, or hyphens |
| Batch label | Optional non-sensitive string, 1–128 Python code points; otherwise omitted |
| Excluded data | Request bodies, passwords, tokens, cookies, and arbitrary exception text |
| Record framing | One compact ASCII JSON object followed by one LF byte |
| Encoded size | At most 2,048 bytes, including the final LF |
| Submission | Through the application’s governed logging handler and collector |
A label that looks like severity=info remains a label. Never merge a request object into the event’s top-level map, accept a caller’s severity, or derive the event name from submitted text. A syntactically valid interaction ID is also insufficient: the application must create or explicitly establish that context. Validation of its alphabet does not prove its origin.
Keep original data out of routine diagnostics when it has no investigation purpose. OWASP’s data-exclusion guidance identifies access tokens, passwords, session identifiers, connection strings, and primary secrets as values that generally need removal or special handling. An allowlist of fields is easier to review than a promise to remove every possible secret afterward. Even an approved label field needs a data-classification decision; its type and length checks cannot detect a credential pasted into it.
Encode the record once
The following Python serializer illustrates that contract. It returns bytes for a logging integration to submit. It does not configure a logger or provide transport, persistence, or delivery guarantees.
import json
import re
from datetime import datetime, timezone
def rejection_record(*, interaction_id, batch_label):
if (type(interaction_id) is not str or
re.fullmatch(r"[A-Za-z0-9_-]{1,64}", interaction_id) is None):
raise ValueError("log_context_invalid")
label_recorded = type(batch_label) is str and 1 <= len(batch_label) <= 128
event = {
"schema_version": 1,
"event": "import_rejected",
"severity": "warning",
"outcome": "rejected",
"reason": "input_contract",
"logged_at": datetime.now(timezone.utc).isoformat(),
"interaction_id": interaction_id,
"batch_label": batch_label if label_recorded else None,
"batch_label_omitted": not label_recorded,
}
record = json.dumps(
event, ensure_ascii=True, allow_nan=False, separators=(",", ":")
).encode("ascii") + b"\n"
if len(record) > 2048:
raise ValueError("log_record_too_large")
return record
The Python JSON documentation
explains that ensure_ascii=True escapes non-ASCII characters and that quotation marks, backslashes, and U+0000–U+001F require escaping even when that option is false. Compact separators avoid pretty-printing line breaks. The helper adds the sole physical LF after serialization; embedded newlines remain escaped inside the JSON string. allow_nan=False makes the numeric policy explicit, although this record does not accept caller-supplied numeric fields.
Python also warns that JSON is not a framed protocol. Writing consecutive JSON objects without a boundary does not create a valid JSON document. This example deliberately defines a line-delimited stream: the collector must read one complete JSON object per physical line. If the framework already creates JSON records, supply the approved fields to its structured interface instead of wrapping this JSON string in another event.
The label’s 128-code-point limit bounds serialization work here. The final length check measures actual encoded bytes, including expansion from escapes. Never enforce a record ceiling by slicing the serialized bytes: that can remove a closing quote or brace. Omit an optional field and reserialize under an explicit policy, or report a fixed construction failure. batch_label_omitted describes this logging decision; it does not decide whether the original import was valid.
Preserve meaning after decoding
A parsed JSON string contains its original control characters again. A dashboard that prints it raw into a terminal, an HTML page, or another line-oriented export creates a new output boundary. Preserve the original typed field for analysis, and use a viewer appropriate to its destination. Do not URL-decode or repeatedly unescape log fields as a convenience step. MITRE’s guidance specifically warns about validation bypasses caused by inconsistent encoding and double decoding.
Prefer the framework’s existing governed handler over independent file writes. Complete serialization in memory does not make concurrent file or pipe writes atomic. Confirm how workers, buffering, multiline detection, and record-size limits behave in the actual collector. Automatic alerts must use trusted event fields and deliberate predicates; external label text must not become an alert instruction or a success classification.
Happy-path operator workflow
- Trace each changed logging call through its formatter, handler, collector parser, storage fields, alert rule, and viewer. Include access logs and background workers that may record the same input.
- Write the event schema, trusted-field ownership, excluded values, field limits, framing, and encoded-size limit. Identify who creates correlation context.
- Keep templates and field names fixed. Select approved scalar values rather than serializing request objects or arbitrary exceptions.
- Submit a harmless valid fixture. Assert one event, the expected event type and outcome, the correct interaction ID, and the expected optional label.
- Submit the separator and malformed-field fixtures below. Inspect raw emitted bytes and parsed collector records separately.
- Retain the fixture IDs, schema version, test commands, counts, and first field mismatch in the pull request. Use synthetic markers throughout.
Error-path operator workflow
An optional malformed label should not erase the rejection event. The helper records a null label and an omission flag without calling str() on an unknown object. OWASP’s event-collection guidance likewise recommends omitting or safely replacing malformed fields while retaining the security event and bounded validation context.
An invalid server interaction ID is a different failure: it indicates broken trusted context. The integration needs a fixed internal diagnostic and an independently created local correlation identifier, rather than copying the invalid value into the error message. Do not turn a rejected import into an accepted operation because logging construction failed.
Define logging-outage behavior separately from input handling. Test full storage, permission denial, disconnected collectors, and handler exceptions. OWASP recommends that logging failures avoid disrupting application operation or leaking information. Bound queues and retries, monitor dropped-event counts through a separate health signal, and keep the application’s established rejection behavior. If a particular action requires durable audit storage before it may proceed, document and test that transaction policy explicitly. Never add a raw stderr dump of the request as the emergency fallback, or recursively send a handler failure through the same failed handler.
Build a harmless adversarial test matrix
Run these fixtures only against a disposable local harness and an isolated collector configuration. A valid control must pass so a missing logger cannot masquerade as successful injection prevention.
| Fixture | Required observation |
|---|---|
Ordinary label such as batch-A | One record with the exact fixed event fields and label |
| Actual CR, LF, or CRLF followed by an inert fake event | One physical record; no second collected event |
| Quotes, backslashes, tabs, and NUL | Valid JSON; original value restored only inside the label field |
| Escape character and Unicode line separators | Escaped source bytes; safe rendering checked separately |
| Text resembling a success event or severity field | Trusted event remains rejected with warning severity |
| Empty, non-string, or 129-code-point label | Label omitted; rejection event retained |
| 128 supplementary Unicode code points | Final encoded record stays within the byte ceiling |
| Synthetic credential marker in a prohibited request field | Marker absent from routine logs and fallback output |
| Two concurrent worker events | Two intact collected events with their own trusted context |
| Collector or storage failure | Bounded failure behavior, visible health signal, no request dump |
For the serializer, assert record.count(b"\n") == 1, a terminal LF, no raw CR or escape byte, and successful JSON parsing. Compare the parsed event, severity, outcome, and label exactly. For the collector, check the number of events, field types, and search results; a well-formed source line alone cannot establish those results.
The displayed helper was exercised on Python 3.12.3 with five local unit tests, all passing. They covered a valid control; separator and control-character round trips; omission of malformed labels without string conversion; fixed failure for invalid context; and the byte limit for maximum-length ASCII and supplementary-character labels. No collector, concurrent writer, dashboard, secret-redaction path, or storage outage was exercised. Those rows remain acceptance checks for the repository under review.
Failure modes
Trusting parameterized messages alone. A constant message template protects template ownership, but the final text formatter may still emit embedded newlines. Inspect the encoded output.
Hand-building JSON or merging caller fields. The first can break syntax; the second can change trusted event meaning while remaining valid JSON. Use an encoder and explicit field construction.
Dropping the whole event for a bad label. This suppresses the evidence of a rejection. Omit the optional field and preserve the fixed event with an omission indicator.
Truncating after serialization. Cutting an encoded record can corrupt its structure. Apply field policy first and check the final size afterward.
Treating escaping as redaction. A correctly escaped token is still a leaked token. Exclude sensitive fields before serialization and inspect all error paths.
Testing only the serializer. Multiline collection, field remapping, raw fallback writes, or unsafe viewers can defeat an otherwise correct record. Verify the deployed logging path in an isolated environment.
FAQ
Is JSON logging enough to prevent log injection?
It provides useful structure when a real encoder escapes values and the collector honors the framing. It does not establish trusted field ownership, redact secrets, prevent concurrent-write corruption, or secure downstream viewers. Test those decisions independently.
Should newlines be rejected from every application input?
No. A product may legitimately accept multiline text. Apply its input contract, then encode approved values for each output. The example permits control characters in a bounded, non-sensitive diagnostic label to demonstrate escaping; it does not authorize that label for HTTP headers or other outputs.
Can an attacker-controlled request ID be trusted after validation?
Validation establishes syntax. It does not establish origin or uniqueness. Keep a server-created interaction ID authoritative and place any approved client correlation value in a separate, explicitly untrusted field.
Should all logging failures stop a transaction?
Use the application’s explicit durability policy. Routine diagnostic failures should have bounded handling and independent monitoring. An operation that requires durable audit evidence needs a separately tested transaction rule; the serializer does not supply it.
Sources checked
Three primary public references were fetched successfully on November 4, 2026 UTC:
- OWASP Logging Cheat Sheet , published by the OWASP Cheat Sheet Series, supports event classification, data exclusions, malformed-field handling, structured encoding, input limits, and verification of injection and logging outages. It is a maintained evergreen reference; no publication date is asserted.
- CWE-117: Improper Output Neutralization for Logs , published by the MITRE CWE Program, defines log forging and its consequences and explains validation, output encoding, and double-decoding risks. The fetched entry reports an October 28, 2026 update.
- json — JSON encoder and decoder , published by the Python Software Foundation, documents escaping, compact separators, strict handling of non-finite numbers, and the absence of built-in record framing. The fetched page identifies Python 3.15.2 and a November 4, 2026 update; the helper uses APIs also available in the tested Python 3.12.3 runtime.
These technical references support an evergreen patch-review task. The example schema, limits, handler integration advice, and test matrix are recommendations. The local checks do not establish a production vulnerability or certify a logging deployment.
Reader next step
Choose one logging call changed by the next coding-agent patch. Write its trusted fields, permitted external values, sensitive-data exclusions, framing, and size policy. Add a valid control and a harmless CRLF fake-event fixture, then follow both through the serializer and collector. Keep the review open until event count, severity, outcome, correlation context, and failure behavior match that contract.