Back to skill

Security audit

Simmer Resolution Tracker

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says, but it automates wallet-affecting redemptions with broad credential exposure and weak safety boundaries, so it needs careful review before installation.

Only install this in an isolated environment with a dedicated low-balance wallet and narrowly scoped API key. Prefer POLY_MODE=sim first, avoid storing unrelated secrets in ~/.env, pin and review simmer-sdk before use, and confirm that cron, DATA_DIR, and DISCORD_WEBHOOK are configured exactly as intended.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
resolution_tracker.py:28
Finding
Overbroad Loading of Secrets from the User Home Directory<![CDATA[ ## Vulnerability Details **File Location**: `resolution_tracker.py`, lines 28-40 **Vulnerability Type**: Excessive access to environment secrets **Risk Level**: High ### Vulnerable Code ```python # ── Load .env if present (development convenience) ──────────────────────────── def _load_env(): for candidate in [".env", os.path.expanduser("~/.env")]: if os.path.exists(candidate): with open(candidate) as f: for line in f: line = line.strip() if line and not line.startswith("#") and "=" in line: k, v = line.split("=", 1) os.environ.setdefault(k.strip(), v.strip()) break _load_env() ``` ### Technical Analysis The tracker reads either a working-directory `.env` file or the global `~/.env` file and imports every key-value pair into the process environment. The Skill only declares a need for a limited set of variables, principally `SIMMER_API_KEY`, `WALLET_PRIVATE_KEY`, `DISCORD_WEBHOOK`, `POLY_MODE`, and `DATA_DIR`. Reading all entries from a user-wide secrets file exceeds the minimum privileges required for resolution tracking. Once loaded, unrelated credentials are exposed to the tracker process, the imported `simmer-sdk` package, and its transitive dependencies. No allowlist restricts which variables may be imported, and no validation ensures that the file belongs specifically to this project. The current code does not itself transmit every loaded variable, and no direct exfiltration of `WALLET_PRIVATE_KEY` was identified. The vulnerability is the unnecessary expansion of the process's credential access boundary. ### Attack Path 1. The user stores credentials for unrelated services in `~/.env`. 2. The tracker is started from a directory without its own `.env`, causing it to fall back to `~/.env`. 3. `_load_env()` imports every entry into `os.environ`. 4. The tracker subsequently imports and executes `simmer-sd ...[truncated 790 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove the `~/.env` fallback entirely. 2. If local dotenv support is necessary, resolve a project-specific file relative to `resolution_tracker.py`. 3. Import only explicitly approved variables: ```python ALLOWED_ENV_KEYS = { "SIMMER_API_KEY", "WALLET_PRIVATE_KEY", "DISCORD_WEBHOOK", "POLY_MODE", "DATA_DIR", } ``` 4. Do not copy unrelated values into `os.environ`. 5. Prefer a dedicated secret manager or pass only the required variables to the scheduled process. 6. Run the tracker in a restricted service environment with an explicit environment-variable allowlist. 7. Isolate wallet signing from the tracker process so that monitoring and journal-processing dependencies never receive private signing material. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
resolution_tracker.py:94
Finding
Unrestricted Webhook Destination Can Receive Private Trading Information<![CDATA[ ## Vulnerability Details **File Location**: `resolution_tracker.py`, lines 94-108 and 377-395 **Vulnerability Type**: Unvalidated outbound network destination and financial-data disclosure **Risk Level**: Medium ### Vulnerable Code ```python def post_webhook(content): """Post a message to Discord via webhook URL (pure Python, no curl).""" if not WEBHOOK_URL: return try: body = json.dumps({"content": content[:2000]}).encode() req = urllib.request.Request( WEBHOOK_URL, data=body, headers={"Content-Type": "application/json"}, method="POST", ) urllib.request.urlopen(req, timeout=10) except Exception as e: print(f" ⚠️ Webhook error: {e}") ``` The messages sent through this unrestricted destination contain trading details: ```python post_webhook( f"{emoji} **{strategy}** | {side.upper()} | {question[:60]}\n" f"{pnl_str} | {'WIN 🎉' if won else 'LOSS'}" ) # Auto-redeem (LIVE only) if IS_SIM: redeemed_ids.add(market_id) elif pos.get("redeemable") and market_id not in redeemed_ids and _budget_ok(_start, redeemed_count): print(f" 💰 Attempting redemption...") redeemable_side = pos.get("redeemable_side") or side status, error = redeem_position(market_id, redeemable_side) if status == "already": redeemed_ids.add(market_id) elif status: redeemed_ids.add(market_id) redeemed_count += 1 total_redeemed += pos.get("current_value", 0) post_webhook(f"💰 **Auto-Redeemed** | {question[:50]}... | +${pos.get('current_value',0):.2f}") ``` ### Technical Analysis Although the configuration is documented as a Discord webhook, `WEBHOOK_URL` is passed directly to `urllib.request.Request` without validating its scheme, host, port, or redirect behavior. The destination can therefore be any endpoint accepted by the HTTP client rather than a verified Discord webhook. The payload includes stra ...[truncated 1797 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the destination with `urllib.parse.urlparse`. 2. Require the `https` scheme. 3. Allowlist the intended Discord webhook hostnames and expected path prefix. 4. Reject embedded credentials, unexpected ports, fragments, and malformed URLs. 5. Disable redirects or validate every redirect destination against the same allowlist. 6. Provide an explicit option controlling which financial fields may be transmitted. 7. Redact or pseudonymize strategy names and market details where full disclosure is unnecessary. 8. Store webhook configuration in a protected deployment secret rather than a broadly writable `.env` file. 9. Apply outbound firewall rules so the process can communicate only with required Simmer and Discord endpoints. ]]>

T08 · Insecure Dependencies

Error
Location
clawhub.json:3
Finding
Unpinned Third-Party SDK Executes in a Wallet-Privileged Process<![CDATA[ ## Vulnerability Details **File Location**: `clawhub.json`, lines 3-5; `resolution_tracker.py`, lines 66-73 **Vulnerability Type**: Unpinned security-sensitive dependency **Risk Level**: High ### Vulnerable Code The Skill declares the dependency without a version constraint or integrity information: ```json "requires": { "env": ["SIMMER_API_KEY", "WALLET_PRIVATE_KEY"], "pip": ["simmer-sdk"] }, ``` The installation documentation likewise retrieves the current package version: ```bash pip install simmer-sdk ``` The dependency is imported and executed in the process that holds the Skill's sensitive credentials: ```python def get_client(): global _client if _client is None: from simmer_sdk import SimmerClient _client = SimmerClient( api_key=API_KEY, venue="polymarket", ) return _client ``` ### Technical Analysis The project relies on `simmer-sdk` for financially sensitive redemption operations but does not pin an audited version or verify package integrity. A future package release can therefore change the effective code executed by this Skill without changes to the audited project. The process is configured to contain `SIMMER_API_KEY` and `WALLET_PRIVATE_KEY`. Although the project code does not directly read `WALLET_PRIVATE_KEY`, the documentation indicates that it is made available for SDK-driven redemption. Python dependencies execute with the full privileges of the importing process and can read process environment variables, access files available to the service account, make network requests, and alter redemption requests. No evidence shows that the current `simmer-sdk` package is malicious. The confirmed issue is the unsafe, mutable supply-chain boundary around a dependency operating with wallet-related privileges. ### Attack Path 1. An attacker compromises the package publisher account, package repository, build pipeline, or a future release of `simmer-sdk`. 2. The operato ...[truncated 1082 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin `simmer-sdk` to a reviewed exact version. 2. Use a lock file and require package hashes, for example through `pip --require-hashes`. 3. Record and verify the expected package source and artifact digest. 4. Review the pinned SDK and its transitive dependencies before deployment. 5. Use automated dependency monitoring, but require review before accepting upgrades. 6. Run the tracking and journal-processing logic without access to the wallet private key. 7. Move transaction signing into a separate, minimal service or hardware-backed signer with a narrowly defined redemption policy. 8. Restrict the signer to approved contracts, methods, networks, position identifiers, and value limits. 9. Use a dedicated low-value wallet and API credential with only the permissions required for redemption. 10. Restrict outbound network access for the tracker and signer independently. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

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

Critical
Category
Data Flow
Content
body = json.dumps(data).encode() if data else None
    req = urllib.request.Request(url, headers=headers, method=method, data=body)
    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read().decode())
    except urllib.error.HTTPError as e:
        print(f"  API error {e.code}: {e.read().decode()[:120]}")
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 100, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
headers={"Content-Type": "application/json"},
            method="POST",
        )
        urllib.request.urlopen(req, timeout=10)
    except Exception as e:
        print(f"  ⚠️  Webhook error: {e}")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Credential Access

High
Category
Privilege Escalation
Content
from datetime import datetime, timezone
from pathlib import Path

# ── Load .env if present (development convenience) ────────────────────────────
def _load_env():
    for candidate in [".env", os.path.expanduser("~/.env")]:
        if os.path.exists(candidate):
Confidence
90% confidence
Finding
Automatically loading .env files constitutes credential access because the script reads potentially sensitive secrets from local developer or home directories. In this skill context, that behavior is not strictly necessary for resolution tracking and can unintentionally consume secrets unrelated to the application, increasing exposure and making behavior dependent on ambient credentials.

Credential Access

High
Category
Privilege Escalation
Content
# ── Load .env if present (development convenience) ────────────────────────────
def _load_env():
    for candidate in [".env", os.path.expanduser("~/.env")]:
        if os.path.exists(candidate):
            with open(candidate) as f:
                for line in f:
Confidence
90% confidence
Finding
The specific inclusion of ~/.env broadens the blast radius from project-local configuration to the user's general secret store, which is especially sensitive. A cron-executed trading skill should not implicitly harvest secrets from a home-directory file because it can ingest unrelated credentials and create hard-to-audit secret dependencies.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill describes capabilities that can access environment secrets, read/write local data, use the network, and execute shell-adjacent setup/run commands, yet it declares no explicit tool scope or permission boundaries. In a skill that handles API keys, a wallet private key, journal files, Discord webhooks, and on-chain redemption flows, missing scope declarations materially increases the risk of over-privileged execution and unintended secret exposure or asset-affecting actions.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The skill prominently advertises that it will automatically redeem winning positions on-chain and update a trade journal every 5 minutes, but it does not present a strong upfront warning that it will modify financial assets and local data. Because these actions are autonomous and recurring, a user could enable the skill without fully appreciating that it can trigger irreversible blockchain transactions and alter records.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The setup instructions ask the user to export a wallet private key directly into an environment variable without any security warning or handling guidance. In the context of an automated trading skill that performs on-chain redemptions, compromise or mishandling of this key could lead to direct theft or unauthorized movement of funds, making the omission especially dangerous.

Missing User Warnings

Medium
Confidence
82% confidence
Finding
The module documentation states that a wallet private key is required for redemptions, but it does not provide a prominent user-facing warning about secure storage, least privilege, or the consequences of live mode. In a trading automation context, weak credential-handling guidance materially increases the chance of unsafe deployment practices.

Tainted flow: 'tmp' from os.environ.get (line 179, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
def save_journal(trades):
    tmp = JOURNAL_PATH + ".tmp"
    Path(tmp).parent.mkdir(parents=True, exist_ok=True)
    with open(tmp, "w") as f:
        for t in trades:
            f.write(json.dumps(t) + "\n")
    os.replace(tmp, JOURNAL_PATH)
Confidence
88% confidence
Finding
JOURNAL_PATH is derived from DATA_DIR, which is environment-controlled, and the code writes to tmp files without constraining the destination to a safe base directory. If an attacker can influence DATA_DIR, they can cause the skill to overwrite arbitrary writable files via direct path control and the later os.replace call.

Tainted flow: 'RESOLVED_PATH' from os.environ.get (line 56, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
return
                except Exception:
                    pass
    with open(RESOLVED_PATH, "a") as f:
        f.write(json.dumps(trade) + "\n")
Confidence
88% confidence
Finding
RESOLVED_PATH is also derived from the environment-controlled DATA_DIR and is opened for append with no path validation. In environments where attackers can affect configuration, this enables unauthorized file creation or modification in arbitrary writable locations.

Tainted flow: '_COOLDOWN_PATH' from os.environ.get (line 223, credential/environment) → open (file write)

Medium
Category
Data Flow
Content
s["consecutive_losses"] = 0
        else:
            s["consecutive_losses"] = s.get("consecutive_losses", 0) + 1
        with open(_COOLDOWN_PATH, "w") as f:
            json.dump(state, f, indent=2)
    except Exception as e:
        print(f"  ⚠️  Cooldown update error: {e}")
Confidence
86% confidence
Finding
_COOLDOWN_PATH is built from DATA_DIR and then written without validating the path. As with the other state files, hostile control of DATA_DIR can redirect writes to unintended filesystem locations and tamper with local files accessible to the process.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
In live mode, the script automatically performs on-chain redemption transactions without an explicit per-run confirmation or strong runtime warning. Because this skill directly manages trading outcomes and may be scheduled via cron, silent autonomous transaction execution increases the risk of unintended financial actions if configuration is wrong or the operator misunderstands the mode.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The skill description says it monitors resolutions and auto-redeems winning positions, but the code also performs a portfolio-wide sweep for any redeemable positions not yet claimed. That expands operational scope beyond newly resolved positions and can trigger additional on-chain actions the operator may not expect from the manifest.

Context-Inappropriate Capability

Low
Confidence
93% confidence
Finding
The skill reads .env files from the current directory and the user's home directory, which is broader credential access than needed for a cron-style resolution tracker. In a sensitive workstation or shared environment, this can ingest unrelated secrets and silently alter runtime behavior based on ambient developer files.

Static analysis

No suspicious patterns detected.