Skip to main content

SEC015-log-injection — Log Injection

PropertyValue
DimensionSecurity
Default severityMedium
ConfidenceMedium
CWECWE-117
LanguagesRust, Python, JavaScript, TypeScript

What it detects​

Flags logging calls that pass user-controlled input without sanitization into log format strings. An attacker who can inject newlines or CRLF sequences into logs can:

  • Forge or spoof log entries.
  • Inject new log lines that look like legitimate events.
  • Confuse log aggregation and alerting tools.
  • Potentially exploit log parsers or SIEM systems.

A finding fires when all of:

  1. The call is a known logging function (logger.info, logging.debug, console.log, log::warn!, tracing::info!, etc.).
  2. The first argument is a format string containing a placeholder ({}, %s, %d, %r, %v) or is a template literal with at least one substitution expression (${...}).
  3. A subsequent argument's leading identifier is either in the request-style allowlist (req, request, params, body, query, ctx, context, input, user_input, payload, headers, cookies, args, kwargs, event, data) or appears in the immediately enclosing function's parameter list.

Examples — flagged​

Python:

def view(req):
logger.info("user said {}".format(req.body)) # FLAGGED

def handle(req):
logger.info("user: %s", req.body) # FLAGGED

def run(user_input):
logging.debug("received: %s", user_input) # FLAGGED

Rust:

fn handler(req: Request) {
log::info!("user: {}", req); // FLAGGED (macro-body regex parse)
}

fn view(req: Request) {
info!("received: {}", req); // FLAGGED (macro-body regex parse)
}

JavaScript/TypeScript:

function view(req) {
logger.info(`user: ${req.body}`); // FLAGGED
}

function handle(req) {
console.log("user: %s", req.body); // FLAGGED
}

Examples — not flagged​

Python:

def startup():
logger.info("startup complete") # no placeholder, no user arg

def report():
total = 42
logger.info("user count: %d", total) # total is not request-style or a param

Rust:

fn startup() {
log::info!("startup complete"); // no placeholder
}

fn report() {
let total = 42;
log::info!("count: {}", total); // total is not request-style or a param
}

JavaScript/TypeScript:

function startup() {
logger.info("startup complete"); // no placeholder, no user arg
}

function report() {
logger.info("user count", 42); // no placeholder + non-user arg
}

Fix guidance​

  1. Sanitize before logging: Strip or escape newlines (\n, \r) and other control characters from user-supplied values before passing them to logging calls.
  2. Use structured logging: Pass user data as structured key-value fields rather than interpolating it into format strings (e.g., slog, tracing, Python's extra={} parameter, or a JSON logger).
  3. Validate input early: Enforce format and character-set constraints on input at the entry point so malformed data is rejected before reaching logging code.

Limitation — Rust (macro-body regex parse)​

For Rust, the macro body is parsed via regex over the token-string rather than via a full syntactic AST. This is a known limitation — the finding message includes the phrase (macro-body regex parse) to make this explicit. Complex macro invocations (e.g., using target: labels or nested macros) may produce false positives or negatives.

References​

Implementation​