T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/updates.py:157
- Finding
- Untrusted Steam Announcement Content Is Emitted Without an AI-Safe Trust Boundary<![CDATA[ ## Vulnerability Details **File Location**: `scripts/updates.py`, lines 157–169 and 207–233 **Vulnerability Type**: Indirect prompt injection and unsafe Markdown rendering **Risk Level**: Medium ### Vulnerable Code ```python def clean_content(raw): """Strip BBCode, Steam tokens, and HTML from news content.""" text = re.sub(r"<br\s*/?>", "\n", raw, flags=re.IGNORECASE) text = re.sub(r"<[^>]+>", "", text) text = re.sub(r"\[/?(?:p|list|b|i|u|strike|spoiler)\]", "", text, flags=re.IGNORECASE) text = re.sub(r"\[\*\]", "- ", text) text = re.sub(r"\[/\*\]", "", text) text = re.sub(r"\[url=[^\]]*\]", "", text, flags=re.IGNORECASE) text = re.sub(r"\[/url\]", "", text, flags=re.IGNORECASE) text = re.sub(r"\[img[^\]]*\][^\[]*\[/img\]", "", text, flags=re.IGNORECASE) text = re.sub(r"\[/?h[0-9]\]", "", text, flags=re.IGNORECASE) text = re.sub(r"\{STEAM[^}]*\}", "", text) text = re.sub(r"https?://\S+\.(?:png|jpg|gif|jpeg|webp)", "", text, flags=re.IGNORECASE) text = re.sub(r"\n{3,}", "\n\n", text) return text.strip()[:500] ``` ```python for item in items: if should_exclude(item["title"], appid, config): continue url = item.get("url", "") if url in existing: continue record = { "game": name, "appid": appid, "title": item["title"], "url": url, "date": datetime.fromtimestamp( item["date"], tz=timezone.utc ).strftime("%Y-%m-%d"), "content": clean_content(item.get("contents", "")), "discovered_at": now_iso, "status": "active", } existing[url] = record game_entries.append(record) new_count += 1 if game_entries: lines = [f"### {name}\n"] for entry in game_entries: date_d ...[truncated 3464 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Establish an explicit trust boundary** - Label all Steam-supplied fields as untrusted external content. - Update `SKILL.md` to instruct consuming agents to summarize announcement data only and never follow commands, policies, tool requests, or behavioral instructions embedded in it. 2. **Prefer structured output** - Return JSON objects rather than instruction-like prose or Markdown. - Keep external fields in dedicated properties such as `untrusted_title`, `untrusted_content`, and `source_url`. - Ensure the calling agent treats those properties as data rather than executable instructions. 3. **Escape Markdown-controlled fields** - Escape Markdown metacharacters in titles and other interpolated text. - Do not directly place an upstream URL inside Markdown syntax without validation. 4. **Validate announcement URLs** - Parse URLs using `urllib.parse.urlparse`. - Require HTTPS. - Apply an explicit allowlist of expected Steam-owned hostnames if links are expected to remain on Steam. - Reject URLs containing credentials, malformed hostnames, unexpected schemes, or disallowed domains. 5. **Constrain displayed content** - Preserve existing length limits, but do not rely on truncation or markup stripping as protection against prompt injection. - Add conspicuous delimiters around remote content, for example: ```text BEGIN UNTRUSTED STEAM ANNOUNCEMENT ... END UNTRUSTED STEAM ANNOUNCEMENT ``` 6. **Add security tests** - Test titles containing closing brackets, parentheses, backticks, headings, and embedded links. - Test bodies containing instruction-like phrases that request tool use, secret disclosure, policy changes, or file access. - Test URLs with non-HTTPS schemes, user-info components, deceptive subdomains, and non-Steam destinations. ]]>
