{"id":"GHSA-8h6h-x5pq-56fq","summary":"@logtape/syslog: syslog log injection via unescaped control characters and unvalidated SD-NAME keys","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.\n\n## 1. Unescaped C0 control characters in structured data values\n\n`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):\n\n```typescript\nfunction escapeStructuredDataValue(value: string): string {\n  return value\n    .replace(/\\\\/g, \"\\\\\\\\\")\n    .replace(/\"/g, '\\\\\"')\n    .replace(/]/g, \"\\\\]\");\n  // \\n, \\r, and other C0 control characters are not escaped\n}\n```\n\nTCP 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 (`\u003cPRI\u003e1 …`), a downstream collector will accept them as a separate, authentic-looking syslog record.\n\n## 2. Unvalidated SD-NAME keys\n\nStructured data parameter keys are inserted into the message without validation or escaping:\n\n```typescript\nelements.push(`${key}=\"${escapedValue}\"`);\n```\n\nRFC 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.\n\nIn 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.\n\n## Proof of concept\n\nThe following Node.js snippet (no dependencies, no network required) demonstrates that the escaped value still contains a literal newline:\n\n```javascript\nfunction escapeStructuredDataValue(value) {\n  return value\n    .replace(/\\\\/g, \"\\\\\\\\\")\n    .replace(/\"/g, '\\\\\"')\n    .replace(/]/g, \"\\\\]\");\n}\n\nconst payload =\n  'normal\\n\u003c134\u003e1 2026-01-01T00:00:00Z forged evil - - - INJECTED';\n\nconst result = escapeStructuredDataValue(payload);\nconsole.log(\"Newline present after escape:\", result.includes(\"\\n\")); // true\n```\n\nTested with Node.js 22.17.1.\n\n## Impact\n\nAn attacker who controls log property values can:\n\n- forge syslog records attributed to arbitrary hosts, applications, or process IDs;\n- insert records with arbitrary severity or facility levels;\n- obscure malicious activity by injecting misleading entries around legitimate ones;\n- break downstream log parsers or SIEM correlation rules that rely on log integrity.\n\nAffected downstream collectors include rsyslog, syslog-ng, Splunk, Elastic Stack, and any other system using RFC 6587 non-transparent framing.\n\n## Suggested fix\n\n### Structured data values\n\nEscape 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:\n\n```typescript\nfunction escapeStructuredDataValue(value: string): string {\n  return value\n    .replace(/\\\\/g, \"\\\\\\\\\")\n    .replace(/\"/g, '\\\\\"')\n    .replace(/]/g, \"\\\\]\")\n    .replace(/[\\x00-\\x1f]/g, (c) =\u003e\n      `\\\\x${c.charCodeAt(0).toString(16).padStart(2, \"0\")}`\n    );\n}\n```\n\nAlternatively, strip them entirely: `.replace(/[\\x00-\\x1f]/g, \"\")`. The right choice depends on whether downstream consumers need some representation of the original value.\n\n### SD-NAME keys\n\nValidate each key against the RFC 5424 SD-NAME grammar before including it. Keys that fail validation should be skipped or sanitized:\n\n```typescript\n// SD-NAME: printable US-ASCII, excluding '=', ']', '\"', SP; max 32 chars\nconst SD_NAME_RE = /^[!-\u003c\u003e-Z\\\\^-z|~]{1,32}$/;\n\nfor (const [key, value] of Object.entries(record.properties)) {\n  if (!SD_NAME_RE.test(key)) continue;\n  const escapedValue = escapeStructuredDataValue(String(value));\n  elements.push(`${key}=\"${escapedValue}\"`);\n}\n```","aliases":["CVE-2026-54511"],"modified":"2026-08-26T14:56:26.895872Z","published":"2026-08-26T14:28:04Z","database_specific":{"cwe_ids":["CWE-117","CWE-93"],"severity":"HIGH","github_reviewed":true,"github_reviewed_at":"2026-08-26T14:28:04Z","nvd_published_at":null},"references":[{"type":"WEB","url":"https://github.com/dahlia/logtape/security/advisories/GHSA-8h6h-x5pq-56fq"},{"type":"WEB","url":"https://github.com/dahlia/logtape/commit/7a6e5b9ddf7915edfff78fa129bc17c979b2a623"},{"type":"PACKAGE","url":"https://github.com/dahlia/logtape"},{"type":"WEB","url":"https://github.com/dahlia/logtape/releases/tag/1.3.11"},{"type":"WEB","url":"https://github.com/dahlia/logtape/releases/tag/2.0.14"},{"type":"WEB","url":"https://github.com/dahlia/logtape/releases/tag/2.1.5"}],"affected":[{"package":{"name":"@logtape/syslog","ecosystem":"npm","purl":"pkg:npm/%40logtape/syslog"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.1.0"},{"fixed":"2.1.5"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 2.1.4","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-8h6h-x5pq-56fq/GHSA-8h6h-x5pq-56fq.json"}},{"package":{"name":"@logtape/syslog","ecosystem":"npm","purl":"pkg:npm/%40logtape/syslog"},"ranges":[{"type":"SEMVER","events":[{"introduced":"2.0.0"},{"fixed":"2.0.14"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-8h6h-x5pq-56fq/GHSA-8h6h-x5pq-56fq.json"}},{"package":{"name":"@logtape/syslog","ecosystem":"npm","purl":"pkg:npm/%40logtape/syslog"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.3.11"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-8h6h-x5pq-56fq/GHSA-8h6h-x5pq-56fq.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N"}]}