Back to skill

Security audit

FootyClaw: AI Quant Betting Agent

Security checks for vulnerabilities and agentic risk

Overview

FootyClaw is a coherent football betting assistant, but it needs review because it recommends wager amounts and tracks bankroll/shareholder ledger details without enough user safeguards.

Review before installing. Use only where sports betting is legal for you, treat recommendations as informational rather than reliable financial advice, set your own limits, and avoid storing sensitive personal finance details unless you are comfortable with the agent keeping them in session memory. Configure a narrowly scoped ODDS_API_KEY if possible and rotate it if it may appear in URL logs.

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 (2)

T09 · Insecure Skill Coding Practices

Note
Location
scripts/daily_scanner.py:33
Finding
API Credential Exposed in Query String by Daily Scanner<![CDATA[ ## Vulnerability Details **File Location**: `scripts/daily_scanner.py:33-40` **Vulnerability Type**: API credential exposure through URL query parameters **Risk Level**: Low ### Vulnerable Code ```python params = { "apiKey": API_KEY, "regions": regions, "markets": markets, "dateFormat": "iso", "oddsFormat": "decimal", } url = f"{BASE_URL}/sports/{sport_key}/odds?" + urllib.parse.urlencode(params) try: with urllib.request.urlopen(url, timeout=15) as resp: ``` ### Technical Analysis The script retrieves `ODDS_API_KEY` from the environment and inserts it into the request URL as the `apiKey` query parameter. The request uses HTTPS and is sent only to the declared host, `api.the-odds-api.com`, so this behavior is consistent with the Skill's stated odds-retrieval functionality and is not evidence of intentional credential exfiltration. However, query-string credentials are exposed to more infrastructure components than credentials sent in headers. Complete URLs may be retained by API access logs, reverse proxies, observability products, error-reporting systems, HTTP debugging tools, or process diagnostics. Anyone able to read such records could recover and reuse the key. The network destination and requested access do not exceed the Skill's declared privileges. The weakness concerns how the necessary privilege is exercised. ### Attack Path 1. The user or Skill platform injects `ODDS_API_KEY` into the process environment. 2. The script appends the key to the outgoing request URL as `apiKey=<secret>`. 3. A server-side access log, proxy, monitoring service, or diagnostic mechanism records the full URL. 4. An attacker or unauthorized operator obtains read access to that record. 5. The attacker extracts the API key from the query string. 6. The attacker reuses the key with The Odds API until it is revoked, rotated, or its quota is exhausted. This path requires access to URL-bearing logs or diagnostics; the audited code does not ...[truncated 543 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Prefer an authorization header if The Odds API supports header-based authentication: ```python query = urllib.parse.urlencode({ "regions": regions, "markets": markets, "dateFormat": "iso", "oddsFormat": "decimal", }) url = f"{BASE_URL}/sports/{sport_key}/odds?{query}" request = urllib.request.Request( url, headers={"Authorization": f"Bearer {API_KEY}"}, ) with urllib.request.urlopen(request, timeout=15) as resp: ... ``` 2. If the provider mandates the `apiKey` query parameter: - Never print or persist the complete request URL. - Configure clients, proxies, monitoring systems, and server logs to redact `apiKey`. - Restrict access to network and application logs. - Minimize log retention. - Ensure exception-reporting tools scrub URL query strings. 3. Rotate the key if it may already have appeared in logs or diagnostics. 4. Use a narrowly scoped, quota-limited API key where the provider supports such controls. 5. Add automated tests that verify application logs and raised errors do not contain the key. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/fetch_odds.py:18
Finding
API Credential Exposed in Query String by Odds Fetcher<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch_odds.py:18-25` **Vulnerability Type**: API credential exposure through URL query parameters **Risk Level**: Low ### Vulnerable Code ```python params = { "apiKey": API_KEY, "regions": regions, "markets": markets, "dateFormat": "iso", "oddsFormat": "decimal", } url = f"{BASE_URL}/sports/{sport_key}/odds?" + urllib.parse.urlencode(params) try: with urllib.request.urlopen(url, timeout=15) as resp: ``` ### Technical Analysis The odds-fetching script places the environment-provided `ODDS_API_KEY` directly in the URL query string. Although TLS protects the request while it is in transit and the destination matches the network permission declared in `SKILL.md`, URL query parameters are commonly captured by infrastructure logging and diagnostic systems. Consequently, an otherwise necessary API authentication operation creates an avoidable credential-handling risk. No evidence was found that the script sends the key to an undeclared host, prints the URL itself, or deliberately discloses the key. ### Attack Path 1. The Skill platform supplies `ODDS_API_KEY` through the process environment. 2. `fetch_odds.py` constructs a URL containing the plaintext key in its query string. 3. The request passes through, or terminates at, infrastructure that records complete request URLs. 4. An attacker gains access to those access logs, proxy records, traces, or error reports. 5. The attacker extracts the `apiKey` value. 6. The attacker submits authenticated requests to The Odds API using the recovered key. Exploitation depends on unauthorized access to URL-bearing telemetry or logs. HTTPS prevents passive network observers from reading the URL but does not prevent endpoint or infrastructure logging. ### Impact Assessment The exposed credential could allow unauthorized use of the associated The Odds API account and depletion of its request quota. This may prevent legitimate odds retri ...[truncated 278 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Use a request authorization header instead of a query parameter if supported by The Odds API. 2. If query-string authentication is a provider requirement: - Redact `apiKey` from access logs, proxy logs, traces, exception reports, and debugging output. - Avoid logging complete URLs. - Limit log access and retention. - Disable verbose HTTP debugging in production. 3. Rotate any key that may have been retained in historical URL logs. 4. Apply provider-side quota limits and least-privilege restrictions when available. 5. Centralize API request construction in one hardened helper so both network scripts enforce the same redaction and authentication policy. 6. Add a regression test that captures stdout, stderr, and exceptions and confirms that `ODDS_API_KEY` never appears. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (17)

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

Critical
Category
Data Flow
Content
}
    url = f"{BASE_URL}/sports/{sport_key}/odds?" + urllib.parse.urlencode(params)
    try:
        with urllib.request.urlopen(url, timeout=15) as resp:
            remaining = resp.headers.get("x-requests-remaining", "?")
            print(f"  [{sport_key}] quota remaining: {remaining}", file=sys.stderr)
            return json.loads(resp.read().decode())
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

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

Critical
Category
Data Flow
Content
}
    url = f"{BASE_URL}/sports/{sport_key}/odds?" + urllib.parse.urlencode(params)
    try:
        with urllib.request.urlopen(url, timeout=15) as resp:
            remaining = resp.headers.get("x-requests-remaining", "?")
            used = resp.headers.get("x-requests-used", "?")
            print(f"# API quota: {remaining} remaining / {used} used", file=sys.stderr)
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Natural-Language Policy Violations

Medium
Confidence
77% confidence
Finding
secrets 描述文本直接使用中文表述,且整份技能文档均默认中文交互,没有说明可根据用户偏好切换语言或提供语言选择。按规则,若技能强制特定语言而未提供用户选择或明确合理的地区性限定,属于自然语言层面的语言/locale 策略风险。

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The skill describes end-to-end betting recommendations, bankroll sizing, shareholder tracking, and persistent session-memory bookkeeping without clear user risk disclosures. In this context, omission of warnings increases the chance users treat the output as safe financial advice and may disclose or persist sensitive financial information without informed consent.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger list includes broad everyday phrases such as ‘查赔率’, ‘今天有什么场’, and ‘记账/更新账本’, which can unintentionally activate the skill in ordinary conversation. Because the skill performs gambling-related analysis, bankroll handling, and memory-backed ledger operations, accidental invocation can lead to inappropriate betting recommendations or unintended financial-state changes.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This content gives concrete betting instructions, confidence ratings, expected value framing, and stake sizing templates without any warning about financial loss, gambling harm, or legal/regulatory constraints. In the context of an agent skill, this can normalize risky wagering behavior and encourage users to act on financial advice-like recommendations without safeguards.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
This code file contains user-facing natural-language text in Chinese in the module docstring and later emits Chinese status/error/output strings. Under the policy rule for all file types, forcing a specific language without user opt-in is a locale-policy violation when no alternative or justification is provided.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The argparse description, help text, stderr messages, and generated HTML alt text are presented only in Chinese. Because the script provides no language selection or explicit region-specific justification, this is a natural-language locale policy issue.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The file hard-codes league names in Chinese and later prints Chinese-only status/output messages and a Beijing-time heading, which imposes a specific language/locale on all users. The policy allows locale constraints only when documented and justified or when the user can opt in, neither of which is present here.

Dynamic import via __import__()

Medium
Category
Dangerous Code Execution
Content
print(f"\n📋 共 {len(all_games)} 场,开始EV分析...\n")

    # 通过 stdin 管道把 JSON 传给 ev_calculator,零文件写入
    ev_script = str(((__import__('pathlib')).Path(__file__).parent / "ev_calculator.py"))
    proc = subprocess.run(
        [sys.executable, ev_script, "--stdin",
         "--min-ev", str(args.min_ev),
Confidence
75% confidence
Finding
Dynamic __import__() can load arbitrary modules at runtime, bypassing static analysis and potentially importing malicious code.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# 通过 stdin 管道把 JSON 传给 ev_calculator,零文件写入
    ev_script = str(((__import__('pathlib')).Path(__file__).parent / "ev_calculator.py"))
    proc = subprocess.run(
        [sys.executable, ev_script, "--stdin",
         "--min-ev", str(args.min_ev),
         "--bankroll", str(args.bankroll)],
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
This Python file contains multiple user-facing help and console messages exclusively in Chinese, such as the argparse help text and printed status/output lines. The skill does not offer a language/locale option or document that it is intentionally region-specific, which violates the policy against forcing a specific language without user opt-in.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The docstring and later user-facing usage/output strings include Chinese-only text, which imposes a specific language on users without any opt-in or alternative. This matches the language/locale policy violation category because the script does not offer a language selection or document a justified locale restriction.

External Transmission

Medium
Category
Data Exfiltration
Content
from datetime import datetime, timezone

API_KEY = os.environ.get("ODDS_API_KEY", "")
BASE_URL = "https://api.the-odds-api.com/v4"

def fetch_odds(sport_key, regions="eu", markets="h2h,spreads,totals"):
    if not API_KEY:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
from datetime import datetime, timezone

API_KEY = os.environ.get("ODDS_API_KEY", "")
BASE_URL = "https://api.the-odds-api.com/v4"

def fetch_odds(sport_key, regions="eu", markets="h2h,spreads,totals"):
    if not API_KEY:
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Low
Confidence
81% confidence
Finding
No manifest is available to justify the skill's intended scope, so capability should be inferred conservatively from the file's own stated purpose. A 'daily match scanner' fetching matches does implement remote access as documented, but reading an API key from the environment introduces credential access capability that is not itself described in the module documentation.

Context-Inappropriate Capability

Low
Confidence
82% confidence
Finding
No manifest is available, so the skill has no declared purpose or scope to justify capabilities. The code explicitly reads `ODDS_API_KEY` from the environment, which is a credential-access capability beyond simple local formatting or display logic and is not grounded in any stated intent outside the module docstring.

Static analysis

No suspicious patterns detected.