Back to skill

Security audit

Piadas Reddit

Security checks for vulnerabilities and agentic risk

Overview

The skill fetches Reddit jokes as described, but it uses unsafe shared temporary files that could be abused to overwrite user-writable files.

Install only if you are comfortable with a skill that contacts Reddit and prints untrusted Reddit posts. The package should ideally be revised to store state in a private user-owned directory or securely created temporary file before routine use, because the current fixed /tmp paths can be abused on shared systems.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
index.mjs:6
Finding
Predictable Temporary Files Allow Symlink-Based File Overwrite<![CDATA[ ## Vulnerability Details **File Location**: `index.mjs:6,13-15`; `get_joke.sh:13,19` **Vulnerability Type**: Predictable temporary-file path and unsafe file creation **Risk Level**: Medium ### Vulnerable Code `index.mjs:6,13-15`: ```js const LAST_JOKE_FILE = '/tmp/last-joke.txt'; function setLastJoke(id) { writeFileSync(LAST_JOKE_FILE, id); } ``` `get_joke.sh:13,19`: ```bash # Salva em temp file e processa com python echo "$CONTENT" > /tmp/reddit_tiodopave.json PIADA=$(python3 << 'PYEOF' import json, random try: with open('/tmp/reddit_tiodopave.json', 'r') as f: data = json.load(f) ``` ### Technical Analysis Both implementations use fixed, attacker-predictable paths inside the shared `/tmp` directory. Neither implementation securely creates the destination with exclusive semantics nor verifies that the path is a regular file owned by the current user. The Node.js `writeFileSync` operation and the shell redirection both follow symbolic links. A local attacker who can create one of these predictable paths before the skill runs may replace it with a symbolic link to another file writable by the victim account. Running the skill then truncates or overwrites the symlink target. The shell implementation has an additional time-of-check/time-of-use exposure because it writes the response and subsequently reopens the same shared path from Python. Another local process could replace or modify the file between those operations. ### Attack Path 1. A local attacker predicts that the victim will execute the skill. 2. The attacker creates `/tmp/last-joke.txt` or `/tmp/reddit_tiodopave.json` as a symbolic link to a file writable by the victim. 3. The victim invokes the Node.js or shell implementation. 4. The skill opens the predictable path while following the symbolic link. 5. The linked target is truncated and overwritten with a Reddit post ID or downloaded JSON. 6. For the shell implementation, an attacker may alternatively replace the J ...[truncated 984 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Do not place persistent state in a globally predictable `/tmp` path. Store the last-post ID in a private application state directory owned by the user, such as a directory beneath `XDG_STATE_HOME`. - Create the state directory with restrictive permissions, such as mode `0700`, and the state file with mode `0600`. - If temporary storage is required, create a private temporary directory using `fs.mkdtemp()` in Node.js or `mktemp -d` in the shell. - Use exclusive file creation where applicable and reject symbolic links. In Node.js, use appropriate `open` flags and validate the resulting file with `fstat`. - Write state atomically by creating a securely named file in the same private directory and renaming it over the destination. - Remove temporary files and directories using a cleanup handler. - Avoid the intermediate JSON file in `get_joke.sh`. Pipe the `curl` response directly into Python or perform the HTTP request and parsing in one process. - If an intermediate file remains necessary, pass its securely generated name as an argument rather than embedding a fixed path. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
index.mjs:72
Finding
Attacker-Controlled Reddit Content Is Forwarded to Agent Output<![CDATA[ ## Vulnerability Details **File Location**: `index.mjs:72-85`; `get_joke.sh:25-34,49` **Vulnerability Type**: Indirect prompt injection through untrusted remote content **Risk Level**: Medium ### Vulnerable Code `index.mjs:72-85`: ```js setLastJoke(chosen.id); const title = cleanText(chosen.title); const selftext = cleanText(chosen.selftext || ''); if (selftext) { return `${title}\n\n${selftext}`; } return title; } fetchJoke() .then(joke => { console.log(joke); ``` `get_joke.sh:25-34,49`: ```python posts = [p['data'] for p in data['data']['children'] if p['data'].get('is_self') and p['data'].get('selftext')] if posts: p = random.choice(posts) title = p.get('title', '') text = p.get('selftext', '')[:500] if text: print(f'{title}\n\n{text}') else: print(title) ``` ```bash echo "$PIADA" ``` ### Technical Analysis Reddit post titles and bodies are controlled by external Reddit users. Both implementations select this content and print it without establishing a trust boundary between remote data and instructions intended for an AI agent. The `cleanText` function in the Node.js implementation removes Markdown links, URLs, and selected HTML entities, but it does not identify or neutralize instruction-like language. The shell implementation prints remote content without equivalent cleanup. If the skill's standard output is inserted into an AI agent's context, a malicious post can include text that resembles system instructions, tool-use requests, requests to disclose information, or directions to ignore prior constraints. Ordinary string escaping alone does not prevent semantic prompt injection because the harmful component is the natural-language instruction itself. Exploitability depends on how the calling agent treats skill output. If output is displayed only as inert text, impact is limited. If it is interpreted as trus ...[truncated 1648 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Treat every Reddit field as untrusted external data and preserve that classification through the entire calling pipeline. - Return structured data rather than an undifferentiated text block, for example: ```json { "source": "reddit", "trusted": false, "title": "...", "body": "..." } ``` - Ensure the calling agent is explicitly instructed that the returned title and body are quoted content and must never be interpreted as instructions. - Render remote content inside clearly marked delimiters and prevent it from being concatenated into system, developer, or tool instruction messages. - Do not allow fetched content to directly determine tool names, tool arguments, file paths, URLs, commands, or authorization decisions. - Apply length and control-character limits to titles and bodies. Normalize unusual Unicode and remove terminal control sequences before display. - Consider detecting and rejecting content containing common instruction-hijacking patterns. Such filtering should be treated as defense in depth rather than the primary control. - Require user confirmation before any privileged or side-effecting action that could be influenced by skill output. - Align `get_joke.sh` with the Node.js implementation's validation controls, including score filtering, NSFW filtering, cleanup, and repetition handling. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (5)

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
Comments and user-facing output strings are written in Portuguese, and the script does not provide any opt-in or language selection mechanism. This can violate language/locale policy when a skill forces a specific language without user choice or documented justification.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
This code emits user-visible status text in Portuguese ('Buscando piadas do Reddit...') and also returns Portuguese error messages elsewhere, indicating the skill forces a specific language. The policy allows locale constraints only when justified or when the user is offered a choice, which is not present here.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
The thrown error 'Nenhuma piada encontrada' is a user-visible natural-language string in Portuguese. Combined with other Portuguese strings in the file, this indicates the skill enforces a specific locale rather than allowing user choice.

Missing User Warnings

Low
Confidence
85% confidence
Finding
This code file makes an outbound HTTP request to reddit.com to retrieve content, which transmits system/user network metadata such as IP address and user-agent. While the script comment explains the purpose for developers, there is no user-facing warning, prompt, or runtime disclosure before the network call.

Missing User Warnings

Low
Confidence
81% confidence
Finding
The script saves API response content to /tmp/reddit_tiodopave.json, which is a filesystem write operation. There is no confirmation, visible warning, or runtime disclosure to the user that downloaded content will be written to local storage.

Static analysis

No suspicious patterns detected.