GHSA-8H6H-X5PQ-56FQ
Vulnerability from github – Published: 2026-08-26 14:28 – Updated: 2026-08-26 14:28@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):
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:
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:
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:
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:
// 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}"`);
}
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.1.4"
},
"package": {
"ecosystem": "npm",
"name": "@logtape/syslog"
},
"ranges": [
{
"events": [
{
"introduced": "2.1.0"
},
{
"fixed": "2.1.5"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@logtape/syslog"
},
"ranges": [
{
"events": [
{
"introduced": "2.0.0"
},
{
"fixed": "2.0.14"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "npm",
"name": "@logtape/syslog"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.3.11"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-54511"
],
"database_specific": {
"cwe_ids": [
"CWE-117",
"CWE-93"
],
"github_reviewed": true,
"github_reviewed_at": "2026-08-26T14:28:04Z",
"nvd_published_at": null,
"severity": "HIGH"
},
"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\u2013U+001F):\n\n```typescript\nfunction escapeStructuredDataValue(value: string): string {\n return value\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, \u0027\\\\\"\u0027)\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 \u2026`), 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\u2014for example by spreading request headers or arbitrary metadata into a log record\u2014this 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, \u0027\\\\\"\u0027)\n .replace(/]/g, \"\\\\]\");\n}\n\nconst payload =\n \u0027normal\\n\u003c134\u003e1 2026-01-01T00:00:00Z forged evil - - - INJECTED\u0027;\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\u2013U+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, \u0027\\\\\"\u0027)\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 \u0027=\u0027, \u0027]\u0027, \u0027\"\u0027, 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```",
"id": "GHSA-8h6h-x5pq-56fq",
"modified": "2026-08-26T14:28:04Z",
"published": "2026-08-26T14:28:04Z",
"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"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:H/A:N",
"type": "CVSS_V3"
}
],
"summary": "@logtape/syslog: syslog log injection via unescaped control characters and unvalidated SD-NAME keys"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.