T09 · Insecure Skill Coding Practices
Note
- Location
- scripts/search.py:121
- Finding
- Unsanitized API-Controlled URLs Can Inject Terminal Control Characters## Vulnerability Details **File Location**: `scripts/search.py:22-26` and `scripts/search.py:121-124` **Vulnerability Type**: Incomplete output sanitization / terminal output injection **Risk Level**: Low The formatted output path sanitizes titles but prints API-provided URLs directly to the terminal. ```python def sanitize_output(text: str) -> str: """Strip ANSI control codes and other potentially dangerous characters""" # Remove ANSI escape sequences ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') return ansi_escape.sub('', text) ``` ```python for i, result in enumerate(results["results"], 1): title = sanitize_output(result.get('title', 'No title')) url = result.get('url', 'N/A') print(f"{i}. {title}") print(f" URL: {url}") ``` ### Technical Analysis Search-result fields originate from a remote API and ultimately from potentially attacker-controlled web content. The `title` and `snippet` fields pass through `sanitize_output()`, but the `url` field is printed without any sanitization or scheme validation. A URL containing terminal control characters could manipulate terminal presentation, overwrite displayed lines, or spoof adjacent output. Furthermore, `sanitize_output()` only removes recognized ANSI escape sequences; it does not comprehensively remove unsafe C0/C1 control characters such as carriage return, backspace, or other display-affecting characters. This makes the documentation's broad claim that output sanitization prevents terminal injection inaccurate. ### Attack Path 1. An attacker publishes or controls web content containing a crafted URL or metadata with terminal control characters. 2. The content is indexed and returned by the Perplexity Search API as a result. 3. The Skill decodes the API response without validating the result fields. 4. In formatted output mode, `result.get('url', 'N/A')` is assigned directly to `url`. 5. `print(f ...[truncated 631 chars]
- Remediation
- ## Remediation Suggestions 1. Apply output sanitization to every untrusted formatted field, including URLs: ```python url = sanitize_output(str(result.get("url", "N/A"))) ``` 2. Strengthen `sanitize_output()` to remove unsafe C0 and C1 control characters while explicitly preserving only required whitespace: ```python def sanitize_output(value: object) -> str: text = str(value) text = ansi_escape.sub("", text) return "".join( char for char in text if char in "\t" or (ord(char) >= 0x20 and ord(char) != 0x7F) ) ``` 3. Validate result URLs with `urllib.parse.urlparse` and permit only expected schemes such as `http` and `https`. Reject or visibly label malformed and unsupported URLs. 4. Add regression tests covering ANSI escape sequences, carriage returns, line feeds, backspaces, null bytes, DEL, C1 controls, and unexpected non-string API values. 5. Update `SKILL.md`, `README.md`, and `SECURITY_AUDIT.md` so their security claims accurately describe the sanitizer's scope instead of asserting complete prevention of terminal injection.
