Back to skill

Security audit

Quodd

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it claims by fetching Quodd stock quotes, but users should understand that it sends Quodd credentials to Quodd and caches a short-lived token in a local plaintext file.

Install only if you are comfortable providing Quodd credentials through environment variables and storing a Quodd API token locally for up to 20 hours. On shared or less trusted systems, prefer using --no-cache or tightening permissions on ~/.openclaw/credentials and the token file.

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/quote.py:65
Finding
Authentication Token Cached Without Explicit Access Controls or Symlink Protection<![CDATA[ ## Vulnerability Details **File Location**: `scripts/quote.py:65-77` **Vulnerability Type**: Insecure credential storage and unsafe file creation **Risk Level**: Medium ### Vulnerable Code ```python def save_token_to_cache(token): """Save token to cache with expiration timestamp.""" CACHE_DIR.mkdir(parents=True, exist_ok=True) expires_at = datetime.now(timezone.utc) + timedelta(hours=TOKEN_TTL_HOURS) data = { "token": token, "expires_at": expires_at.isoformat() } with open(TOKEN_CACHE_FILE, "w") as f: json.dump(data, f, indent=2) ``` ### Technical Analysis The Skill stores a reusable Quodd authentication token at the predictable path `~/.openclaw/credentials/quodd-token.json`. Neither the credentials directory nor the token file is assigned an explicit restrictive permission mode. Consequently, effective access permissions depend on the process umask and any pre-existing permissions on `~/.openclaw/credentials`. Under a permissive configuration, other local users or processes may be able to read the cached token. The ordinary `open(..., "w")` operation also follows symbolic links. The implementation does not verify that the destination is a regular file owned by the current user. If an attacker can modify the credentials directory, the attacker may replace `quodd-token.json` with a symbolic link before the Skill writes the token. ### Attack Path A viable local token-disclosure path is: 1. The Skill runs in an environment with a permissive umask or a credentials directory accessible to another local account. 2. The user invokes the Skill, causing a Quodd token to be acquired and cached. 3. The cache file is created without an explicitly enforced `0600` mode. 4. A local attacker reads the token from `~/.openclaw/credentials/quodd-token.json`. 5. The attacker reuses the token against the Quodd quote service until it expires or is revoked. A symlink-based path requires stronger local access: 1 ...[truncated 1179 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Create and validate the credentials directory with owner-only permissions: ```python CACHE_DIR.mkdir(parents=True, exist_ok=True, mode=0o700) os.chmod(CACHE_DIR, 0o700) ``` 2. Reject cache paths that are symbolic links or are not regular files owned by the current user. 3. Write the token to a securely created temporary file in the same directory using mode `0600`, flush and synchronize it, and then atomically replace the destination. 4. Ensure the final cache file remains owner-readable and owner-writable only: ```python fd = os.open(temp_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) ``` 5. When loading the cache, validate file ownership, file type, and permission bits before reading the token. 6. Consider using an operating-system credential store instead of a plaintext JSON file. If file caching remains necessary, document that the token is sensitive and should never be copied, logged, or shared. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (5)

Tainted flow: 'req' from os.environ.get (line 183, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = Request(TOKEN_URL, data=b"", headers=headers, method="POST")

    try:
        with urlopen(req, timeout=TIMEOUT_SECONDS) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data.get("token")
    except HTTPError as e:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'req' from os.environ.get (line 183, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
req = Request(TOKEN_URL, data=b"", headers=headers, method="POST")

    try:
        with urlopen(req, timeout=TIMEOUT_SECONDS) as response:
            data = json.loads(response.read().decode("utf-8"))
            return data.get("token")
    except HTTPError as e:
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill declares access to environment variables and its documented behavior implies network access plus credential/token handling, but it does not declare an explicit tool scope such as permissions or allowed-tools. This weakens policy enforcement and reviewability because the runtime capabilities exceed what is transparently constrained in the manifest, increasing the chance of unintended secret access, file writes, or outbound requests.

Vague Triggers

Medium
Confidence
89% confidence
Finding
The manifest description says to use the skill when the user asks for 'stock prices, quotes, market data, or ticker information.' Terms like 'market data' and 'ticker information' are broad and lack clear boundaries or exclusion conditions, which could cause the skill to trigger for general finance questions beyond real-time US equity quotes.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill persists a Quodd API token to a predictable file under the user's home directory without setting restrictive permissions or warning the user. On multi-user systems or misconfigured environments, another local process or user could read the token and use it to access the Quodd account until expiry.

Static analysis

No suspicious patterns detected.