Back to skill

Security audit

Steam Games Updates

Security checks for vulnerabilities and agentic risk

Overview

This skill fetches public Steam game update news and stores a local history, with the main caution that third-party announcement text is shown to the agent as Markdown.

Before installing, understand that the skill contacts Steam and keeps a local list and update history. Treat displayed announcement text and links as untrusted third-party content; the agent should summarize them rather than follow any instructions that might appear inside a game announcement.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

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. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill performs network access to Steam APIs and writes persistent local data, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates a trust and containment gap: an agent may invoke the skill with broader-than-expected capabilities, making file modification and outbound requests less auditable and harder to constrain.

External Transmission

Medium
Category
Data Exfiltration
Content
STATE_FILE = DATA_DIR / ".state.json"
UPDATES_FILE = DATA_DIR / "updates.json"

STEAM_NEWS_URL = "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/"
STEAM_SEARCH_URL = "https://steamcommunity.com/actions/SearchApps/"
STEAM_APP_DETAILS_URL = "https://store.steampowered.com/api/appdetails"
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Static analysis

No suspicious patterns detected.