Security logs are useful only if responders can trust what an event means. That trust can fail when an application builds log records by joining trusted text with untrusted values.

A username, request parameter, filename, or external error message may contain line breaks, delimiters, terminal control characters, or text that resembles the application’s own log format. If that value is inserted directly into a text record, it can make one event appear to be several events, hide where fields begin and end, or mislead a person reading the log.

This problem is commonly called log forging or log injection. The defensive goal is not to remove every unusual character from application input. It is to preserve a stronger rule: untrusted data may become a field value, but it must not be able to redefine the structure of the log event.

This article develops that rule from a small example, shows why structured events help, and explains the limits that still matter in production logging systems.

Separate the event structure from its values

Consider an application that records failed sign-in attempts with string concatenation:

"login failed user=" + username + " source=" + sourceAddress

For an ordinary username, the output is easy to read:

login failed user=mina source=192.0.2.40

The problem is that the application has mixed two different kinds of data in one string:

trusted structure: login failed user=        source=
untrusted values:             mina            192.0.2.40

If a supplied value contains a newline or text that resembles another field, a line-oriented consumer may no longer know which characters came from the application and which came from the user.

The important mistake happened before the log reached storage. The application converted an event with distinct fields into an ambiguous text format without defining how field values are encoded.

A safer mental model keeps the event structured:

event type: authentication_failure
user:       <untrusted value>
source:     <validated or derived value>

The logging library or event encoder then serializes those fields according to one defined format. The username remains data inside the user field instead of becoming part of the syntax that separates records or fields.

State the threat model

Safe log encoding reduces the risk that attacker-controlled input can alter the apparent structure or meaning of application-generated log events.

The attacker needs an input path whose value is recorded. They do not need write access to the log store itself. If the application records their input unsafely, the application’s own logging authority can write misleading output on their behalf.

This control helps preserve event boundaries and field meaning. It can make automated parsing more reliable and reduce opportunities to confuse responders with fabricated-looking text.

It does not make logs trustworthy against every threat. Structured logging does not stop a compromised application from intentionally emitting false events. It does not stop an attacker with permission to modify the log store. It does not guarantee that important events are logged, that timestamps are correct, or that sensitive values are absent. Those are separate logging and monitoring problems.

The narrow guarantee is useful: under the chosen serialization format, an untrusted field value is encoded as a value rather than interpreted as event structure.

Start with the smallest useful change

The smallest useful improvement is to stop constructing security events as ad hoc strings.

Instead of conceptually doing this:

log("password reset requested for " + account + " from " + address)

emit an event with explicit fields:

event = "password_reset_requested"
account = accountIdentifier
source_address = address

The exact API depends on the language and logging framework. The security property does not: the application passes values separately from the event format, and one trusted encoder serializes the result.

A JSON representation might look like this after serialization:

{
  "event": "password_reset_requested",
  "account": "user-1842",
  "source_address": "192.0.2.40"
}

If the account field contains a newline, a correct JSON serializer represents that character inside the JSON string rather than turning it into a literal record boundary. Downstream software that parses the JSON according to the format can recover the original value without confusing it with another event.

The production recommendation is not “manually escape strings until they look like JSON.” Use a maintained serializer or structured-logging facility that implements the format. Hand-built escaping is easy to make incomplete, especially once Unicode, quotes, backslashes, control characters, and nested data are involved.

Choose one record boundary and enforce it

Structured fields solve only part of the problem. A logging pipeline also needs an unambiguous way to tell where one event ends and the next begins.

This matters because logs often pass through several components:

application
    |
logging library
    |
agent or collector
    |
transport
    |
central store
    |
search and alerting

Each boundary needs a defined representation. If the application emits JSON objects but a collector simply splits incoming bytes on newline characters, the application must use a serialization compatible with that framing. If a transport protocol carries length-delimited records, the receiver should use those lengths rather than rediscover boundaries from field contents.

The general rule is to define framing and encoding together. A field value must not be able to produce bytes that a downstream component interprets as a new record outside the rules of that format.

Test the complete path, not only the application’s in-memory event object. A safe event can become ambiguous later if an intermediary converts it back into an unsafe text template.

Keep security meaning in trusted fields

Structured logging works best when fields with security meaning are produced by trusted application logic rather than copied from user-controlled text.

Suppose a client can send a parameter called role. Recording that parameter can be useful for debugging, but it should not become the authoritative actor_role field in a security event unless the server has independently established that role.

Prefer this separation:

actor_id:       server-resolved identity
actor_role:     server-resolved authorization role
requested_role: client-supplied value

Now a responder can distinguish what the system believed from what the requester claimed.

Apply the same idea to outcome fields. Let application control flow set values such as result=denied or result=allowed. Do not derive the authoritative outcome by copying a message supplied by a client or remote dependency.

This is not only a log-forging defense. It makes the event schema explain the trust boundary: which facts were established by the application and which values merely describe external input.

Do not solve the problem by deleting useful evidence

A common reaction to dangerous log characters is to strip punctuation, spaces, or all non-alphanumeric characters before logging. That can reduce some formatting problems, but it also changes the evidence.

Imagine investigating a malformed request. If the logged value has been aggressively rewritten, the record may no longer show what the application actually received. Two distinct inputs can even collapse into the same sanitized value.

Prefer reversible encoding over destructive rewriting when the original value is safe and appropriate to retain. The serializer should represent control characters and delimiters safely while preserving their meaning as data.

Validation still has a role. If a field is supposed to be an IP address, integer, identifier, or enumerated value, validate it according to that application’s data contract. But validation and log encoding solve different problems:

validation: is this value acceptable for this application field?
encoding:   can this value be represented without changing log structure?

A valid free-form display name can still contain characters that need encoding. An invalid request value may still need to be logged safely for investigation.

Treat terminal output as another rendering context

Even well-framed stored events may eventually be displayed in a terminal, web interface, ticket, or chat notification. Those destinations have their own interpretation rules.

For example, terminal control characters can affect how text is rendered even when they did not create a new stored log record. A web viewer has HTML and browser security considerations that differ from a terminal. A notification system may interpret Markdown or another markup language.

Do not expect the storage encoding to make a value safe for every later display context. Preserve the structured value in storage, then apply appropriate output handling when rendering it.

This follows the same defensive principle used elsewhere in secure software: encode for the interpreter that is about to consume the data. Log serialization protects log structure; terminal or web rendering needs its own safe presentation behavior.

Keep sensitive data out of logs

Safe encoding does not make a secret appropriate to record.

Passwords, session identifiers, recovery secrets, API credentials, private keys, and other authentication material can remain dangerous even inside perfectly valid structured JSON. If a logging system receives those values, everyone and everything allowed to read the logs may gain access to them.

Design the event schema so that sensitive values are omitted, replaced with non-secret identifiers, or deliberately transformed when a specific operational need justifies it. Apply this decision before serialization rather than relying only on a later redaction pipeline.

Later redaction can be useful defense in depth, but it has a failure window: the sensitive value may already have reached process buffers, local files, collectors, or another destination before redaction occurs.

The two rules therefore belong together:

record only appropriate data
then encode recorded data safely

Neither rule replaces the other.

Make the schema stable enough for detection

Security monitoring becomes fragile when every component invents its own text sentence for the same event.

A stable schema gives detection logic fields it can depend on. An authentication failure might consistently contain an event type, actor or account identifier, source information, outcome, timestamp, and correlation identifier where appropriate. The exact fields depend on the system, but their meaning should not change casually.

Avoid putting important machine-readable facts only inside a prose message field. Humans may appreciate a summary message, but alerts should prefer typed fields whose values have documented meanings.

For example:

event = authentication_failure
account_id = user-1842
reason = invalid_credential

is easier to reason about than searching arbitrary sentences for words such as “failed”. It also prevents an untrusted display value from becoming the only source of information used by a detector.

Schema changes are operational changes. If a field is renamed or its meaning changes, update collectors, dashboards, retention rules, and alerts together. Otherwise a perfectly encoded event may still become invisible to monitoring.

Verify the defense through the whole pipeline

Testing should demonstrate the security property rather than merely confirm that a logging call executed.

Create test values containing the characters your chosen format treats specially: record separators, quotes, backslashes, delimiters, and relevant control characters. Pass them through the same logger, collector, transport, and parser used in production-like environments.

Then verify three things:

  1. one application event is still parsed as one event;
  2. the untrusted value is recovered as one field value rather than new structure;
  3. trusted fields such as event type and outcome keep the values assigned by application logic.

Also test failure behavior. What happens when a field is too large, serialization fails, the collector is unavailable, or an event does not match the expected schema? Security logging should not normally turn an attacker-controlled logging error into an application outage. At the same time, silently discarding every malformed security event can remove visibility. The right behavior depends on the application’s availability requirements and the importance of the event.

Operational limits matter too. Bound the size of fields and events where appropriate so that a single request cannot create disproportionate logging cost. Keep those limits explicit and test how truncation is represented so responders know when evidence is incomplete.

Know what structured logging does not provide

Structured events improve integrity of interpretation, not integrity of storage.

If your threat model includes attackers who may gain application-host or log-store privileges, use complementary controls. Restrict who can write, alter, and delete centralized logs. Separate operational duties where justified. Protect log transport according to its trust boundary. Define retention deliberately. Monitor the logging pipeline itself so loss of expected events can be detected.

For higher-assurance environments, additional mechanisms may be used to make unauthorized modification or deletion more detectable. Those mechanisms address a different question: whether stored history has been altered after emission. Structured encoding addresses whether an untrusted value can masquerade as structure during emission and processing.

Keeping those guarantees separate prevents a common mistake: calling a log “tamper-proof” merely because it is structured or centrally stored.

Conclusion

Log forging becomes possible when attacker-controlled data and trusted event syntax are mixed without a reliable encoding boundary. The durable fix is not a larger list of forbidden characters. It is to represent security events as structured data, keep untrusted values in explicit fields, serialize them with a defined encoder, and preserve unambiguous record boundaries through the logging pipeline.

Keep authoritative security meaning in server-derived fields, retain useful evidence through reversible encoding, omit secrets before they reach the logger, and test special values through the same collectors and parsers used in production.

The practical rule is straightforward: a user-controlled value may appear in a security log, but it should never get to decide where an event begins, ends, or what the trusted fields mean.