← Alle Neuigkeiten
security4 Min. Lesezeit

Log injection and CRLF injection in AI-generated code: how attackers plant fake log entries and cover their tracks

Von Seaworthy · 29. Juni 2026

Laptop displaying a command prompt terminal screen

Photo by Desola Lanre-Ologun on Unsplash

What your logger doesn't know can hurt you

Most developers treat logs as write-only. They go in, sit in a file, and only get read when something breaks. That attitude is exactly what an attacker needs.

Log injection is the attack where user-controlled input ends up inside a log message verbatim. The moment that happens, an attacker can write anything: fake timestamps, fake IP addresses, fake authentication events. CRLF injection is the specific mechanism, so named because embedding \r\n (carriage return, line feed) in a payload splits a single log line into multiple lines, each looking like a legitimate entry written by the application itself.

AI-generated logging code is unusually prone to this. The model produces something that looks right. It logs the username. It logs the action. It even formats the timestamp correctly. But it concatenates user input directly into the log string without sanitizing it first.

The vulnerable pattern

Here is the kind of code a language model generates when you ask it to add logging to an authentication handler:

app.post('/login', (req, res) => {
  const { username } = req.body;
  logger.info(`Login attempt for user: ${username}`);
  // ...authentication logic...
});

This looks harmless. It isn't. Send this as the username:

admin\r\n2024-01-15T09:00:00Z INFO Login succeeded for user: admin\r\n2024-01-15T09:00:01Z INFO Session created: admin

Your log file now contains three lines. One real one and two fabricated ones. The fabricated entries show a successful login and a valid session, both attributed to admin. If the attacker subsequently covers the real entry, or it gets rotated out, the fake trail is all that remains. Your incident timeline is now fiction.

Why AI gets this wrong

Language models learn from code that works, not code that is secure. The overwhelming majority of open-source logging examples concatenate strings. They pass review. Tests pass. The app ships. The model has no incentive to learn that this pattern is dangerous, because the training data treats it as normal.

It is also a subtle bug. Static analysis tools often miss it. Type checkers certainly miss it. You need to specifically look for log calls that include req.body, req.query, req.headers, or any other user-controlled value without sanitization in between.

The safer alternative

There are two approaches worth using.

The first is structured logging. Instead of building a string, pass user data as a separate field:

// Structured logging: user input goes into a field, not the message
logger.info('Login attempt', { username: username });

With a proper structured logger like Pino or Winston in JSON mode, username ends up as a JSON value. CRLF characters inside a JSON string are escaped automatically by the serializer. An attacker can still inject garbage, but it stays trapped inside the username field where it cannot forge new log lines.

The second approach is explicit sanitization. If you are stuck with string-based logging, strip control characters before they reach the log sink:

function sanitizeForLog(value) {
  if (typeof value !== 'string') return String(value);
  // Remove carriage return, newline, and other control characters
  return value.replace(/[\r\n\t\x00-\x1f\x7f]/g, '_');
}

app.post('/login', (req, res) => {
  const { username } = req.body;
  logger.info(`Login attempt for user: ${sanitizeForLog(username)}`);
  // ...
});

Structured logging is the better choice by a wide margin. Sanitization is a defense-in-depth measure for cases where you cannot change the logging format.

What attackers actually do with this

Covering tracks is the obvious use. An attacker who compromises an account can make the log show a clean authentication history, with no anomalous login times, no foreign IPs, nothing to trigger a review.

More creative attacks plant fake error entries pointing at a different IP address or username, muddying incident response by making investigators chase ghosts.

There is also the SIEM manipulation angle. Security information and event management systems ingest logs and trigger alerts based on patterns. If an attacker understands the alert rules, they can inject entries designed to suppress an alert or flood it with noise until analysts start ignoring it. That is a more sophisticated attack, but it starts with the same naive log concatenation that a language model produced in seconds.

Where to look in your codebase

Search for any logging call that interpolates request data. The pattern is consistent: logger.*() or console.log() with template literals or string concatenation containing req.body, req.params, req.query, or req.headers.

In Python, the equivalent is any logging.* call where the format string includes user input rather than using %s-style argument substitution. logging.info("User: " + username) is vulnerable. logging.info("User: %s", username) is not, because the logging module handles substitution internally after formatting, so it never builds a raw string the attacker can split.

The pattern also appears in error handlers. An attacker who can trigger a validation error with a crafted input value, and that value gets logged in the error handler, has the same injection primitive. Error paths are often the least-reviewed code in a service.

Read your own logs

The fix is straightforward. The audit to find the problem is not. AI-generated code produces this pattern at scale, across services that get deployed, iterated on, and eventually go to production before anyone thinks to check whether user input reaches log sinks unescaped.

Seaworthy's data exposure checks scan for exactly these patterns: user-controlled values flowing into log calls without sanitization, across JavaScript, TypeScript, and Python codebases.

Dieser Artikel wurde von KI generiert und fasst öffentlich verfügbare Quellen zusammen.

Von den Machern von Seaworthy

Seaworthy scannt dein Repository nach den Problemen, die in Artikeln wie diesem behandelt werden.

Sicherheitslücken, offengelegte Geheimnisse und Fehlkonfigurationen — erkannt, bevor du bereitstellst. Kostenlos, kein Konto nötig.

$ npx seaworthycode