T09 · Insecure Skill Coding Practices
- Location
src/cli.js:372- Finding
Unsanitized Terminal Control Sequences in Analysis Output
- Content
View full analysis
Vulnerability Details
File Location:
src/cli.js:372-380; additional affected rendering paths includesrc/cli.js:414-436andsrc/humanizer.js:340-362
Vulnerability Type: Terminal escape-sequence injection
Risk Level: MediumVulnerable Code
js for (const match of finding.matches) { const loc = match.line ? `L${match.line}` : ''; const preview = typeof match.match === 'string' ? match.match.substring(0, 80) + (match.match.length > 80 ? '...' : '') : ''; lines.push(` ${color.dim(loc)}: "${preview}"`); if (match.suggestion) { lines.push(` ${color.green('→')} ${match.suggestion}`); } }The grouped suggestion formatter similarly prints matched input without sanitization:
js for (const s of result.critical) { lines.push(` ${color.red('●')} L${s.line}: ${color.bold(s.pattern)}`); lines.push(` ${color.dim(truncate(s.text, 60))}`); lines.push(` ${color.green('→')} ${s.suggestion}`); }The non-colored humanization formatter also includes the original match directly:
js for (const s of result.critical) { lines.push(` L${s.line}: [${s.pattern}] "${truncate(s.text, 60)}" [${s.confidence}]`); lines.push(` → ${s.suggestion}`); }Technical Analysis
The CLI accepts text from an explicitly selected file or standard input. Pattern matches derived from that potentially untrusted text are interpolated directly into terminal reports. Truncating a string does not neutralize embedded C0/C1 control characters, ANSI Control Sequence Introducer sequences, or Operating System Command sequences.
Some detectors can return matches containing attacker-controlled trailing text rather than only a fixed vocabulary token. For example, the superficial
-ingdetector captures text through the remainder of a sentence. Consequently, an escape sequence placed within a detected passage can reach the terminal ...[truncated 2113 chars]- Remediation
View remediation
Remediation Suggestions
- Add a dedicated terminal-safe escaping function and apply it to every value derived from input text before terminal interpolation.
- Remove ANSI CSI, OSC, DCS, APC, PM, and SOS sequences, not merely the ESC byte.
- Escape remaining non-printable C0 and C1 characters into visible notation such as
\x1b, while retaining only deliberately supported whitespace. - Sanitize before truncation so truncation cannot split an escape sequence and leave a dangerous fragment.
- Apply the same protection to
formatColoredReport,formatGroupedSuggestions, andformatSuggestions. - Keep machine-readable JSON output separate from terminal-rendering functions. Document that consumers must treat analyzed text as untrusted.
- Add regression tests using CSI screen-clearing sequences, OSC 8 hyperlinks, OSC 52 clipboard sequences, carriage returns, backspaces, and embedded null bytes.
- Consider refusing terminal rendering of raw matches unless explicitly requested, or provide a safe mode that displays escaped byte representations.
A suitable design is:
js function escapeTerminalText(value) { return String(value) .replace(/\x1B(?:\][^\x07\x1B]*(?:\x07|\x1B\\)|\[[0-?]*[ -/]*[@-~])/g, '') .replace(/[\x00-\x08\x0B-\x1F\x7F-\x9F]/g, (char) => `\\x${char.charCodeAt(0).toString(16).padStart(2, '0')}`, ); } const preview = escapeTerminalText(match.match).substring(0, 80);
