T09 · Insecure Skill Coding Practices
- Location
ask-council.sh:75- Finding
Unsanitized API and Model Output Allows Terminal Escape-Sequence Injection
- Content
View full analysis
&2; exit 1; } ``` The synthesized model response is also printed without sanitization: ```bash CHAIRMAN_ANSWER=$(echo "$STATUS" | python3 -c " import sys, json data = json.load(sys.stdin) stage3 = data.get('stage3', {}) if stage3.get('status') == 'complete': result = stage3.get('data', {}) print(result.get('response', 'No answer found')) else: print('Chairman did not complete synthesis') ") ``` It is subsequently emitted to the terminal: ```bash echo "$CHAIRMAN_ANSWER" ``` ### Technical Analysis The script treats the local API response and model-generated chairman answer as trusted terminal text. Python's JSON parser converts encoded control characters such as `\u001b` into actual control bytes. Those bytes are then written directly to the user's terminal. The error path is additionally exposed because `error()` uses `echo -e`, which interprets backslash escape sequences in the supplied error message. Consequently, even text containing encoded escape notation may be transformed into terminal control characters. Although the API is bound to `127.0.0.1`, its output is not inherently trustworthy. It incorporates content produced by language models and could also be controlled by a compromised, replaced, or malicious local backend. ### Attack Path 1. An attacker influences a council model response through crafted question content, compromised model output, or control of the local backend. 2. The backend returns a `stage3.data.response` or `error` value c ...[truncated 1153 chars]- Remediation
View remediation
&2 exit 1 } ``` 2. Sanitize all API and model-generated output before writing it to a terminal. Remove C0/C1 control characters while preserving only explicitly required characters such as newline and tab. 3. Perform sanitization after JSON decoding, because encoded JSON sequences are converted into actual control characters during parsing. For example, process the response with Python before printing: ```python import re safe_text = re.sub(r'[\x00-\x08\x0b-\x1f\x7f-\x9f]', '', untrusted_text) print(safe_text) ``` 4. Use `printf '%s\n' "$CHAIRMAN_ANSWER"` rather than `echo "$CHAIRMAN_ANSWER"` for predictable literal output. 5. Apply the same handling to backend error strings, conversation identifiers, and any future API-controlled values displayed in the terminal. 6. If formatting must be retained, use an allowlist-based renderer rather than permitting arbitrary ANSI sequences. ]]>
