VDB
KO
HIGH 8.6

GHSA-8h6h-x5pq-56fq

@logtape/syslog: syslog log injection via unescaped control characters and unvalidated SD-NAME keys

Quick fix

GHSA-8h6h-x5pq-56fq — @logtape/syslog: upgrade to the fixed version with the command below.

npm install @logtape/syslog@2.1.5

Details

`@logtape/syslog` contains two related output-encoding bugs in the structured data formatting code. Both only affect deployments with `includeStructuredData: true`, which is non-default.

## 1. Unescaped C0 control characters in structured data values

`escapeStructuredDataValue()` in `packages/syslog/src/syslog.ts` escapes `\`, `"`, and `]` per RFC 5424 but does not escape newline (`\n`), carriage return (`\r`), or any other C0 control characters (U+0000–U+001F):

```typescript function escapeStructuredDataValue(value: string): string { return value .replace(/\\/g, "\\\\") .replace(/"/g, '\\"') .replace(/]/g, "\\]"); // \n, \r, and other C0 control characters are not escaped } ```

TCP syslog commonly uses `\n` as a frame delimiter (RFC 6587, non-transparent framing). If an attacker-controlled value contains a literal newline, that newline terminates the current syslog frame. Bytes following the newline begin a new frame, and if they form a valid RFC 5424 header (`<PRI>1 …`), a downstream collector will accept them as a separate, authentic-looking syslog record.

## 2. Unvalidated SD-NAME keys

Structured data parameter keys are inserted into the message without validation or escaping:

```typescript elements.push(`${key}="${escapedValue}"`); ```

RFC 5424 defines SD-NAME as printable US-ASCII characters excluding `=`, `]`, `"`, and space, with a maximum length of 32. A key containing any of those characters, control characters, or exceeding the length limit will produce malformed structured data. If the key itself contains an embedded `]`, it can prematurely close the structured-data element.

In typical usage, property keys are developer-defined string literals and therefore safe. However, if an application forwards attacker-controlled keys as log properties—for example by spreading request headers or arbitrary metadata into a log record—this becomes a second injection path.

## Proof of concept

The following Node.js snippet (no dependencies, no network required) demonstrates that the escaped value still contains a literal newline:

```javascript function escapeStructuredDataValue(value) { return value .replace(/\\/g, "\\\\") .replace(/"/g, '\\"') .replace(/]/g, "\\]"); }

const payload = 'normal\n<134>1 2026-01-01T00:00:00Z forged evil - - - INJECTED';

const result = escapeStructuredDataValue(payload); console.log("Newline present after escape:", result.includes("\n")); // true ```

Tested with Node.js 22.17.1.

## Impact

An attacker who controls log property values can:

- forge syslog records attributed to arbitrary hosts, applications, or process IDs; - insert records with arbitrary severity or facility levels; - obscure malicious activity by injecting misleading entries around legitimate ones; - break downstream log parsers or SIEM correlation rules that rely on log integrity.

Affected downstream collectors include rsyslog, syslog-ng, Splunk, Elastic Stack, and any other system using RFC 6587 non-transparent framing.

## Suggested fix

### Structured data values

Escape all C0 control characters (U+0000–U+001F) in addition to `\`, `"`, and `]`. RFC 5424 does not define an escape sequence for control characters in PARAM-VALUE; the most interoperable approach is to strip or replace them:

```typescript function escapeStructuredDataValue(value: string): string { return value .replace(/\\/g, "\\\\") .replace(/"/g, '\\"') .replace(/]/g, "\\]") .replace(/[\x00-\x1f]/g, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}` ); } ```

Alternatively, strip them entirely: `.replace(/[\x00-\x1f]/g, "")`. The right choice depends on whether downstream consumers need some representation of the original value.

### SD-NAME keys

Validate each key against the RFC 5424 SD-NAME grammar before including it. Keys that fail validation should be skipped or sanitized:

```typescript // SD-NAME: printable US-ASCII, excluding '=', ']', '"', SP; max 32 chars const SD_NAME_RE = /^[!-<>-Z\\^-z|~]{1,32}$/;

for (const [key, value] of Object.entries(record.properties)) { if (!SD_NAME_RE.test(key)) continue; const escapedValue = escapeStructuredDataValue(String(value)); elements.push(`${key}="${escapedValue}"`); } ```

Are you affected?

Enter the version of the package you're using.

Affected packages

npm / @logtape/syslog
Introduced in: 2.1.0 Fixed in: 2.1.5
Fix npm install @logtape/syslog@2.1.5
npm / @logtape/syslog
Introduced in: 2.0.0 Fixed in: 2.0.14
Fix npm install @logtape/syslog@2.0.14
npm / @logtape/syslog
Introduced in: 0 Fixed in: 1.3.11
Fix npm install @logtape/syslog@1.3.11

References