T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/ledger_archive.py:395
- Finding
- Unsanitized Archived Log Content Written to the Terminal## Vulnerability Details **File Location**: `scripts/ledger_archive.py:395-396, 501` **Vulnerability Type**: Terminal escape/control-sequence injection **Risk Level**: Medium ### Complete Code Snippet ```python a = max(0, m.start() - context // 2) snippet = line[a:a + context].decode("utf-8", "replace").replace("\n", " ") yield { "host": f["host"], "path": f["path"], "line": lineno, "ts": ts, "snippet": snippet, } ``` The returned value is subsequently printed without terminal sanitization: ```python elif a.cmd == "grep": for hit in ar.grep(a.pattern, a.host, a.since, a.until, a.path_filter, a.limit): if a.json: print(json.dumps(hit)) else: print("%s %s:%d %s\n %s" % ( hit["ts"] or "----------T--:--:--", hit["path"], hit["line"], hit["host"], hit["snippet"], )) ``` ### Technical Analysis The `grep()` method derives `snippet` directly from archived AI-agent log bytes. It removes newline characters but does not remove ANSI escape sequences, C0/C1 controls, carriage returns, tabs, or other terminal-active characters. The human-readable `grep` command then interpolates this value directly into `print()`. AI transcripts may contain untrusted content originating from prompts, repository files, tool output, remote hosts, or model responses. An attacker who can influence any archived transcript can insert escape sequences into a line that also contains a value the operator is likely to search for. The project already provides `safety.clean_for_terminal()` for this trust boundary and uses it elsewhere, including the primary `ledger_query.py` table output. The direct `ledger_archive.py grep` path bypasses that protection. ### Attack Path 1. An attacker causes an AI-agent transcript to contain a selected search term alongside terminal ...[truncated 1685 chars]
- Remediation
- ## Remediation Suggestions 1. Sanitize every untrusted field immediately before human-readable terminal output: ```python def terminal_field(value, limit): return safety.clean_for_terminal(value, limit=limit).replace("\n", " ").replace("\t", " ") print("%s %s:%d %s\n %s" % ( terminal_field(hit["ts"] or "----------T--:--:--", 64), terminal_field(hit["path"], 1000), hit["line"], terminal_field(hit["host"], 128), terminal_field(hit["snippet"], 1000), )) ``` 2. Prefer sanitization at the output boundary rather than altering archived data. The archive should retain exact bytes, while terminal rendering should always be safe. 3. Treat paths, host names, timestamps, exception messages, and snippets as untrusted output, even where separate validation normally constrains some fields. 4. Preserve machine-readable output as correctly encoded JSON rather than applying terminal-oriented transformations to its underlying values. Document that JSON output should be redirected or processed by a JSON-aware tool. 5. Add regression tests containing: - ANSI color and cursor-control sequences. - OSC title and clipboard sequences. - Carriage returns and C0/C1 controls. - Tabs, embedded newlines, and very long matching lines. - Untrusted content obtained from an SSH-host archive. 6. Assert that human-readable output contains no ESC byte or prohibited control character after sanitization.
