Back to skill

Security audit

DeFi Yield Scout

Security checks for vulnerabilities and agentic risk

Overview

The skill generally does what it claims, but its shared temporary cache could let a local attacker tamper with yield results or overwrite writable files.

Review this before installing if you run on a shared machine or would use the output for financial decisions. Verify APYs and pool details independently before moving funds, and prefer a version that stores its cache in a private per-user directory with symlink and ownership checks.

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/yield_scout.py:22
Finding

Predictable Shared Temporary Cache Enables Symlink Overwrite and Cache Poisoning

Content
View full analysis

Vulnerability Details

File Location: scripts/yield_scout.py, lines 22 and 76–88
Vulnerability Type: Unsafe temporary-file handling
Risk Level: Medium

Vulnerable Code

python
CACHE_PATH = os.path.join(tempfile.gettempdir(), "yield_scout_pools.json")
python
def load_pools(force=False):
    if not force and os.path.exists(CACHE_PATH):
        age = time.time() - os.path.getmtime(CACHE_PATH)
        if age < CACHE_TTL:
            with open(CACHE_PATH, "r") as f:
                return json.load(f)

    print("Fetching pool data from DeFiLlama (this may take a moment)...", file=sys.stderr)
    data = fetch_json(POOLS_URL, timeout=120)
    pools = data.get("data", data) if isinstance(data, dict) else data

    with open(CACHE_PATH, "w") as f:
        json.dump(pools, f)

    return pools

Technical Analysis

The pool cache uses a fixed, predictable filename in the system temporary directory. On multi-user systems, that directory is commonly shared and writable by untrusted local users.

The implementation:

  • Does not ensure that the cache is owned by the current user.
  • Does not verify that the cache is a regular file.
  • Does not prevent symbolic-link traversal.
  • Separately checks and opens the path, creating a time-of-check/time-of-use window.
  • Writes directly to the destination rather than securely creating a temporary file and atomically replacing it.
  • Trusts cached JSON without validating its structure or provenance.

An attacker with local access can pre-create the cache as attacker-controlled JSON. If its modification time falls within the 15-minute TTL, scan and breakeven will trust that content instead of retrieving current DeFiLlama data. This can falsify pool APYs, TVL figures, protocol names, risk ratings, and breakeven recommendations.

The attacker can alternatively create the predictable path as a symbolic link. When the cache is refreshed, open(CACHE_PATH, "w") follows that link and truncates or ov ...[truncated 1881 chars]

Remediation
View remediation

Remediation Suggestions

  1. Store the cache in a private per-user cache directory rather than the shared temporary directory. For example, use an appropriate platform-specific user cache directory and ensure that the directory has mode 0700.

  2. Before trusting an existing cache:

    • Use os.lstat() rather than following links.
    • Reject symbolic links and non-regular files.
    • Verify that the file is owned by the current user.
    • Reject files with group or world write permissions.
    • Validate the decoded JSON against the expected pool schema.
  3. Create cache files securely with restrictive permissions. On supported systems, use flags equivalent to:

python
os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW

Set the file mode to 0600.

  1. Avoid writing directly to the final cache path. Securely create a temporary file inside the same private directory, write and flush the complete payload, call os.fsync() where durability matters, and atomically install it with os.replace().

  2. Handle malformed or untrusted cache data safely. If validation fails, discard the cache and retrieve fresh data rather than terminating or using partially trusted records.

  3. If a shared cache is operationally required, use a dedicated directory with controlled ownership and permissions, unpredictable filenames, and locking to prevent races between concurrent processes.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The intent mapping uses broad financial trigger phrases like "best USDC yields," "top rates," and "safe yields," which can cause the skill to activate on generic finance questions without sufficiently confirming user intent. In a user-invocable skill with Bash access, overbroad routing increases the chance of unintended tool execution, misleading financial guidance, or the skill taking action when the user only wanted general discussion.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
80% confidence
Finding

The natural-language instruction mandates a "n0ir-style verdict" and to present that verdict prominently. This imposes a specific presentation style on all users without offering a choice or explaining why that locale/style constraint is required.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.