Back to skill

Security audit

vinci-tarot

Security checks for vulnerabilities and agentic risk

Overview

The tarot skill is mostly coherent, but its image renderer can fetch arbitrary URLs or read local image paths from input JSON, so it should be reviewed before installation.

Install this only if you are comfortable with a tarot skill that runs local Python and may make network requests for card art. Use a vetted local --images-dir, avoid processing user-supplied or edited reading JSON, run it in a sandbox with restricted filesystem and network access, and prefer pinned local npm tooling if you ever re-export card data.

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
tarot_skill/scripts/generate_spread_image.py:89
Finding
Unrestricted Image Source Handling Enables SSRF and Local File Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `tarot_skill/scripts/generate_spread_image.py`, lines 89–95 and 138–173 **Vulnerability Type**: Server-Side Request Forgery and arbitrary local image-file read **Risk Level**: Medium ### Vulnerable Code ```python def fetch_image(url: str) -> bytes: if "://" in url and not url.startswith("file:"): req = urllib.request.Request(url, headers=BROWSER_HEADERS) with urllib.request.urlopen(req, timeout=TIMEOUT) as r: return r.read() path = url.replace("file:", "").strip() return Path(path).read_bytes() ``` The attacker-controlled value reaches this function through the reading JSON: ```python raw = Path(args.input).read_text(encoding="utf-8") if args.input else sys.stdin.read() reading = json.loads(raw) if not reading.get("ok") or not reading.get("spreadType") or not reading.get("spread"): print("Invalid reading: need ok=true, spreadType, and spread array.", file=sys.stderr) sys.exit(1) spread_type = reading["spreadType"] layout = SPREAD_LAYOUTS.get(spread_type) if not layout or len(layout["slots"]) != len(reading["spread"]): print( f'Layout for "{spread_type}" has {len(layout["slots"]) if layout else 0} slots, ' f"but spread has {len(reading['spread'])} cards.", file=sys.stderr, ) sys.exit(1) cw = layout["canvas_width"] ch = layout["canvas_height"] bg_hex = layout["background_color"].lstrip("#") bg = tuple(int(bg_hex[i : i + 2], 16) for i in (0, 2, 4)) base = Image.new("RGB", (cw, ch), bg) images_dir = Path(args.images_dir) if args.images_dir else None if images_dir is None: default_cards = _ROOT / "cards" _ensure_card_images_once(default_cards) if default_cards.is_dir(): images_dir = default_cards for i, pos in enumerate(reading["spread"]): slot = layout["slots"][i] card_id = pos["card"]["id"] fallback_url = pos["card"].get("image") or get_card_image_url(card_id) url = get_card_url(card_i ...[truncated 3346 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not trust `card.image` from reading JSON. Resolve artwork exclusively from a validated card ID and a server-controlled mapping. 2. Validate card IDs against the known 78-card dataset before constructing an image location. 3. If remote retrieval remains necessary, allow only HTTPS URLs under the exact approved artwork hostname and expected path prefix. 4. Resolve DNS and reject loopback, private, link-local, multicast, reserved, and metadata-service addresses for both IPv4 and IPv6. 5. Disable redirects or validate every redirect destination using the same scheme, hostname, path, and IP-address restrictions. 6. For local artwork, resolve the candidate path and verify with `Path.is_relative_to()` or an equivalent containment check that it remains beneath the configured image directory. 7. Reject `file:` URLs, absolute paths, traversal components, and arbitrary fallback paths. 8. Apply a strict maximum download size while streaming instead of calling `read()` without a limit. 9. Verify the response content type and reject non-image responses before decoding. 10. Configure Pillow pixel limits, catch decompression-bomb errors, and enforce maximum source dimensions. 11. Run image generation in a sandbox with restricted filesystem access and no internal-network access. 12. Add regression tests covering loopback URLs, metadata addresses, redirects, IPv6 private addresses, `file:` URLs, absolute paths, and directory traversal. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The skill’s actual behavior includes image rendering, JSON/file I/O, subprocess-style command execution, and possible network downloads, which is materially broader than a simple conversational tarot reader. This hidden operational breadth increases attack surface and can lead to agents executing code or handling files without users understanding that capability.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill’s actual behavior includes image rendering, JSON/file I/O, subprocess-style command execution, and possible network downloads, which is materially broader than a simple conversational tarot reader. This hidden operational breadth increases attack surface and can lead to agents executing code or handling files without users understanding that capability.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
The skill’s actual behavior includes image rendering, JSON/file I/O, subprocess-style command execution, and possible network downloads, which is materially broader than a simple conversational tarot reader. This hidden operational breadth increases attack surface and can lead to agents executing code or handling files without users understanding that capability.

Ae1

High
Category
analysis-evasion
Content
rt-cards` or `npx tsx scripts/export_cards_from_tarot_game.ts`; Python deps in `requirements.txt` (Pillow).
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Env Variable Harvesting

High
Category
Data Exfiltration
Content
return
    print("First run: downloading card images to", cards_dir, "...", file=sys.stderr)
    cards_dir.mkdir(parents=True, exist_ok=True)
    env = {**os.environ, "PYTHONPATH": str(_ROOT)}
    r = subprocess.run(
        [sys.executable, "-m", "tarot_skill.scripts.download_card_images", "--output-dir", str(cards_dir)],
        cwd=str(_ROOT),
Confidence
60% confidence
Finding
Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Rp1

Medium
Category
MCP Rug Pull
Confidence
94% confidence
Finding
The install guide instructs users to run `npx tsx ...` without pinning a specific package version. `npx` may resolve and execute whatever version is currently available, which can introduce supply-chain risk if a malicious or compromised release is published or if dependency resolution changes over time. In this skill, the command is only for optional card-data export, which lowers exposure somewhat, but it still results in execution of unpinned code on the user's machine.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The README instructs automatic network download of 78 card images on first run, even though remote fetching is not essential to the core tarot-reading logic. This expands the skill's attack surface by introducing external network dependency, possible tracking, and supply-chain or content tampering risk during ordinary use.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill advertises executable capabilities via Python, image generation, filesystem interaction, and possible network fetching, but does not declare any explicit tool/permission scope. That creates an unnecessary trust gap where an agent may grant broader shell, file, or network access than the tarot-reading function actually requires. In this context, the absence of scoped permissions is more dangerous because the instructions explicitly tell the agent to run code and handle files.

Unrestricted Tool Access

Medium
Category
Excessive Agency
Content
- Keep it brief; the goal is to move from distraction to the question and the present, then draw.

4. **Announce the draw, then run it**  
   - **Send a short message to the user that you are drawing now** (e.g. “I’ll draw the cards now.” / “Drawing for you…”) **before** you call `perform_reading` or run any code.  
   - **Only after** that message is sent, call `perform_reading`, save the result as JSON, run the spread-image script, and **send the image** to the user.  
   - Correct order: say you’re drawing → (then) run reading + generate image → send image → (then) write interpretation.
Confidence
89% confidence
Finding
The skill explicitly instructs the agent to 'run any code', call Python functionality, save JSON, and execute an image-generation script, but it does not constrain what tools or commands are permissible. In an agent environment, that pattern can normalize arbitrary code execution for a simple content skill and widen the path to abuse if the implementation or environment is tampered with.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The skill directs the agent to run a Python image-generation command that may fetch remote card images, creating unnecessary network and code-execution exposure for a nonessential presentation feature. Remote asset retrieval can leak environment metadata, consume untrusted content, or expand the blast radius if the script or fetched resources are compromised.

Rp1

Medium
Category
MCP Rug Pull
Confidence
91% confidence
Finding
The use of `npx tsx` without a pinned version introduces supply-chain risk because package resolution may pull a different or malicious version at execution time. Since the skill also instructs running external tooling from a sibling project, this increases the chance of executing unreviewed code in the local environment.

Natural-Language Policy Violations

Medium
Confidence
91% confidence
Finding
This JSON file provides nearly all descriptive content and meanings only under the "zh" locale, while English is limited to card names. That effectively hard-codes a Chinese-language experience without any opt-in, language fallback, or documented region-specific justification.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The meaning selection hard-codes `en` whenever English text exists, otherwise falling back to Chinese. This imposes a language preference on users without any visible opt-in, choice mechanism, or documented locale constraint, which matches the language/locale policy violation criteria.

Natural-Language Policy Violations

Medium
Confidence
92% confidence
Finding
The card interpretation path uses the same hard-coded language selection pattern, preferring English whenever present. This is a natural-language policy issue because the skill does not let the user choose their language or clearly document a justified locale restriction.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The script automatically downloads external card images on first run, which introduces undeclared network behavior and causes code execution flow to depend on remote resources. In a skill described primarily as tarot reading and interpretation, this broadens the trust boundary and can leak network metadata or create supply-chain and availability risks.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
print("First run: downloading card images to", cards_dir, "...", file=sys.stderr)
    cards_dir.mkdir(parents=True, exist_ok=True)
    env = {**os.environ, "PYTHONPATH": str(_ROOT)}
    r = subprocess.run(
        [sys.executable, "-m", "tarot_skill.scripts.download_card_images", "--output-dir", str(cards_dir)],
        cwd=str(_ROOT),
        env=env,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
The use of browser-mimicking headers to retrieve remote images is a red flag because it is specifically designed to bypass server-side access restrictions such as 403 responses. That behavior is unnecessary for a simple tarot-reading skill and increases concern that the script is intentionally evading normal resource-access controls.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The script fetches card images from runtime-provided URLs taken from input data or fallback sources, allowing untrusted JSON to trigger outbound requests or local file reads. This creates SSRF-style behavior and unexpected file access because any value containing a scheme or local path is passed to the fetch routine without validation.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The README says the script will try to download images on first run, but does not present a clear up-front warning that executing the feature causes network activity. Silent or unexpected outbound requests are risky in agent environments because they may violate user expectations, leak environment metadata, or bypass policies for offline-safe skills.

Context-Inappropriate Capability

Low
Confidence
81% confidence
Finding
The skill's purpose is to act as a professional tarot reader, but the documentation instructs the agent/operator to run npm/npx export commands from another project ("tarot_game") and arrange repository layout accordingly. That external project interaction is not part of the user-facing tarot-reading purpose and adds an unjustified capability/dependency surface.

Known Vulnerable Dependency: esbuild==0.27.3 — 1 advisory(ies): GHSA-g7r4-m6w7-qqqr (esbuild allows arbitrary file read when running the development server on Window)

Low
Category
Supply Chain
Confidence
93% confidence
Finding
The lockfile pins esbuild to 0.27.3, and that version is associated with GHSA-g7r4-m6w7-qqqr. This advisory affects esbuild's development server on Windows by permitting arbitrary file read, so the issue is real even though it appears only in a dev dependency and requires a specific feature and platform to be in use.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"keywords": ["tarot", "divination", "skill", "openclaw", "clawhub"],
  "license": "MIT",
  "devDependencies": {
    "tsx": "^4.19.2"
  }
}
Confidence
93% confidence
Finding
The devDependency uses a caret range (^4.19.2), which allows newer minor/patch versions to be installed over time. This can introduce supply-chain risk and reduce build reproducibility if an upstream release is compromised or contains breaking behavior, though the dependency appears limited to a development/export script rather than runtime execution in the skill itself.

Static analysis

No suspicious patterns detected.