T09 · Insecure Skill Coding Practices
- Location
scripts/csv-inspect:174- Finding
Unescaped Terminal Control Sequences in CSV Output
- Content
View full analysis
Vulnerability Details
File Location:
scripts/csv-inspect, lines 174–205
Vulnerability Type: Terminal output injection
Risk Level: MediumVulnerable Code
python for name in info["names"]: lines.append(f" {name}") lines.append(f"types (from first {info.get('scan_rows', 0)} data rows):") for col in info["types"]: sample = ", ".join(col["sample"]) if col["sample"] else "" extra = f" sample=[{sample}]" if sample else "" lines.append( f" {col['name']}: {col['type']} empty={col['empty_in_scan']}{extra}" )python for row in rows: out.append( " " + " | ".join( (row[i] if i < len(row) else "")[: widths[i]].ljust(widths[i]) for i in range(len(names)) ) )Technical Analysis
Column names, sampled values, and displayed rows originate from the inspected file and are inserted directly into text output. The program does not escape ASCII control characters, the escape character, or ANSI/OSC terminal sequences.
Although limiting strings to 40 Python characters constrains payload length in the table, it does not neutralize terminal sequences. Samples and names outside the table are not subject to that limit. When text output is connected to a compatible terminal, a malicious CSV can therefore alter colors, move or erase terminal content, create deceptive hyperlinks, change a terminal title, or trigger other terminal-specific behavior. The exact effect depends on terminal capabilities and configuration.
JSON mode is less exposed because
json.dumpescapes control characters, but ordinary text mode is the default.Attack Path
- An attacker creates or supplies a CSV containing ANSI, OSC, or other terminal control sequences in a header or cell.
- A user or agent invokes
csv-inspecton that untrusted file in default text mode. - The parser preserves the embedded sequence as ...[truncated 730 chars]
- Remediation
View remediation
Remediation Suggestions
Introduce one centralized display-escaping function and apply it to every untrusted field before computing display widths or constructing text output.
The sanitizer should:
- Render C0 and C1 controls, ESC, DEL, carriage returns, newlines, tabs, and other non-printable characters as visible escaped forms.
- Neutralize complete ANSI CSI, OSC, DCS, APC, and related escape sequences.
- Preserve the original values only in structured internal data.
- Continue using JSON serialization for JSON mode rather than manually constructing JSON.
- Optionally provide an explicit, prominently documented raw-output mode, while keeping sanitized output as the default.
Example hardening pattern:
python def display_safe(value: str) -> str: return value.encode("unicode_escape").decode("ascii") safe_names = [display_safe(name) for name in names] safe_rows = [[display_safe(cell) for cell in row] for row in rows]A production implementation should also account for Unicode formatting and bidirectional control characters. Add regression tests using CSI color/erase sequences, OSC hyperlinks, embedded newlines, carriage returns, tabs, and Unicode bidi controls.
