Back to skill

Security audit

Prediction Market Creator

Security checks for vulnerabilities and agentic risk

Overview

This skill is openly designed to create prediction markets, but it gives unattended AI output authority to sign blockchain transactions and publish records without enough safeguards.

Install only after review. Use a dedicated low-balance test wallet, do not reuse a primary wallet key, add manual approval before each transaction, validate all LLM output strictly, set spend and rate limits, and avoid cron operation until there is a dry-run or review workflow.

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

Error
Location
skill.py:145
Finding
Untrusted AI Output Controls Unattended Value-Bearing Blockchain Transactions<![CDATA[ ## Vulnerability Details **File Location**: `skill.py:145-173`, `skill.py:211-235`, `skill.py:289-299`; `skill.md:159-165` **Vulnerability Type**: Unvalidated AI output used in an automatically signed blockchain transaction **Risk Level**: High ### Complete Code Snippets The skill incorporates untrusted social-media content directly into the model prompt and accepts the model response after JSON syntax parsing: ```python def analyze_with_claude(tweets, recent_predictions): recent_qs = [p['question'] for p in recent_predictions] recent_str = json.dumps(recent_qs) if recent_predictions else "None" prompt = f"""From these X posts: {json.dumps(tweets, default=str)} Avoid these recent questions: {recent_str} Pick a NEW debatable hot topic and create a yes/no prediction market proposal in valid JSON: {{ "question": "Will [event] happen by [date]?", "duration_days": 1-14, "resolution_criteria": "How to resolve (sources)", "score": 8.0-10.0, "reasoning": "Why hot", "sources": ["link1", "link2"], "category": "One of: Politics, Elections, Sport, Gaming, Crypto, Price, Tech, People, Personal, music, Pop, other" }} Return ONLY JSON, no extra text, no markdown.""" message = client.messages.create( model="claude-sonnet-4-20250514", max_tokens=500, temperature=0.8, messages=[{"role": "user", "content": prompt}] ) text = message.content[0].text.strip() ``` Only JSON parsing is performed. No schema, type, or range validation is applied to transaction-relevant fields: ```python try: return json.loads(text) except Exception as e: print(f"JSON parse error: {str(e)}") print(f"Raw text: {text}") raise ``` The model-provided duration is passed directly to a payable contract call. The transaction is then signed and broadcast without user confirmation: ```python def get_min_deposit(): try: min_wei = contract.functions.minimumDeposit().ca ...[truncated 5205 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Apply strict schema validation** - Validate the complete model response using a fixed schema. - Require `duration_days` to be an integer, explicitly rejecting booleans and numeric strings. - Enforce `1 <= duration_days <= 14`. - Set maximum lengths for questions, rules, sources, and other text fields. - Reject unknown fields and unsupported categories. 2. **Require approval before signing** - Display the destination contract, chain, method, duration, deposit, gas limit, and maximum total cost. - Require explicit operator approval before every transaction. - Do not allow model output to trigger signing merely because it is valid JSON. 3. **Enforce financial policy in code** - Set an immutable maximum deposit and abort if `minimumDeposit()` exceeds it. - Enforce maximum gas cost, transaction count, and cumulative daily spending. - Add a cooldown between market creations. - Stop automatically rather than using a fallback deposit when the contract query fails. 4. **Constrain signing privileges** - Use a dedicated low-balance wallet exclusively for this skill. - Prefer a policy-controlled signer, multisignature wallet, or contract account restricted to the expected contract and method. - Store the key in a secret manager or protected signing service rather than a general `.env` file. 5. **Verify blockchain context** - Verify the expected chain ID before constructing or signing transactions. - Confirm that bytecode exists at the configured contract address and matches an approved deployment. - Simulate the transaction and inspect the result before requesting approval. - Pin or independently verify security-sensitive state instead of relying on one untrusted RPC endpoint. 6. **Reduce prompt-injection exposure** - Clearly delimit retrieved posts as untrusted quoted data. - Instruct the model never to follow instructions found inside retrieved content. - Treat mo ...[truncated 406 chars]
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (23)

Tainted flow: 'headers' from os.getenv (line 191, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
params = {"query": query, "count": limit}
    
    try:
        response = requests.get(url, headers=headers, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()
        tweets = data.get('tweets', []) or data.get('data', [])
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'headers' from os.getenv (line 191, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
params = {"query": query, "count": limit}
    
    try:
        response = requests.get(url, headers=headers, params=params, timeout=10)
        response.raise_for_status()
        data = response.json()
        tweets = data.get('tweets', []) or data.get('data', [])
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Tainted flow: 'headers' from os.getenv (line 191, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Content
}
    
    try:
        response = requests.get(url, headers=headers, params=params)
        response.raise_for_status()
        data = response.json()
        image_url = data['results'][0]['urls']['regular'] if data['results'] else ""
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Context-Inappropriate Capability

Critical
Confidence
99% confidence
Finding
This code signs and submits on-chain transactions that open new markets and spend the contract's minimum deposit, creating irreversible external side effects. Because the transaction is driven by automated content collection and LLM-generated output, a mistake, prompt-manipulated input, or malicious trigger can directly cause financial loss and unauthorized market creation.

Tainted flow: 'headers' from os.getenv (line 191, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
}
    
    try:
        resp = requests.post(url, headers=headers, json=data, timeout=10)
        resp.raise_for_status()
        result = resp.json()
        print(f"Bubble registered successfully!")
Confidence
90% confidence
Finding
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Known Vulnerable Dependency: web3==7.14.1 — 2 advisory(ies): CVE-2026-40072 (web3.py: SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling); CVE-2026-40072 (web3.py: SSRF via CCIP Read (EIP-3668) OffchainLookup URL handling)

High
Category
Supply Chain
Confidence
96% confidence
Finding
The dependency pins web3 to version 7.14.1, which is reported as affected by CVE-2026-40072 involving SSRF through CCIP Read / OffchainLookup URL handling. If the skill processes attacker-influenced blockchain responses or contract interactions, this could cause outbound requests to attacker-controlled or internal endpoints, creating a meaningful server-side request forgery risk.

Credential Access

High
Category
Privilege Escalation
Content
```
1. Clone the repository
2. Install dependencies: pip install -r requirements.txt
3. Create .env file with your API keys (see .env.example)
4. Run: python skill.py
```
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The skill loads a blockchain private key and prepares for wallet-controlled actions despite lacking any documented trust boundary, authorization model, or user approval flow. In an agent context, possession of a signing key is highly sensitive because compromise or misuse can directly spend funds and create irreversible on-chain effects.

Credential Access

High
Category
Privilege Escalation
Content
UNSPLASH_ACCESS_KEY = os.getenv("UNSPLASH_ACCESS_KEY")

if not all([TWITTERAPI_IO_KEY, ANTHROPIC_API_KEY, RPC_URL, PRIVATE_KEY, UNSPLASH_ACCESS_KEY]):
    print("ERROR: Missing keys in .env")
    exit(1)

client = Anthropic(api_key=ANTHROPIC_API_KEY)
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

High
Confidence
99% confidence
Finding
The skill executes a fund-spending blockchain transaction automatically with no user confirmation, preview, or explicit consent. In an agent setting this is especially dangerous because it allows external data and model output to trigger irreversible financial actions without human review.

Known Vulnerable Dependency: python-dotenv==1.0.0 — 2 advisory(ies): CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)

Medium
Category
Supply Chain
Confidence
90% confidence
Finding
The dependency pins python-dotenv to 1.0.0, which is reported as affected by CVE-2026-28684 involving symlink following in set_key that can lead to arbitrary file overwrite. This becomes dangerous if the skill ever writes .env data to a path influenced by an attacker or operates in directories where an attacker can place symlinks.

Known Vulnerable Dependency: requests==2.32.3 — 4 advisory(ies): CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs); CVE-2026-25645 (Requests has Insecure Temp File Reuse in its extract_zipped_paths() utility func); CVE-2024-47081 (Requests vulnerable to .netrc credentials leak via malicious URLs) +1 more

Medium
Category
Supply Chain
Confidence
92% confidence
Finding
The dependency pins requests to 2.32.3, which is associated with advisories including a .netrc credential leak via malicious URLs and other unsafe file-handling issues. In an agent skill context, where URLs or remote resources may be influenced by external input, this can expose credentials or enable unsafe local file interactions depending on how the library is used.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill explicitly describes automatic blockchain market creation and automatic account registration on an external service, but it does not warn users that running it will trigger real outbound actions affecting third-party systems and potentially on-chain state. This increases the risk of users executing the skill without understanding that it can create accounts, submit transactions, and generate unintended operational or financial consequences.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The documentation instructs users to provide an Ethereum wallet private key in a .env file without clearly warning that this is highly sensitive secret material that grants transaction authority over the wallet. Even though no hardcoded key is present, normalizing private-key entry without handling guidance can lead to credential theft, misuse, accidental commits, or use of an overprivileged wallet.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill recommends running on a schedule to continuously create new prediction markets, but it does not warn that unattended execution will repeatedly perform autonomous external actions. In this context, scheduled operation can amplify mistakes, spam third-party platforms, create duplicate or low-quality markets, and repeatedly spend operational resources without human review.

External Transmission

Medium
Category
Data Exfiltration
Content
def get_hot_topics(category="crypto", limit=10):
    since = (datetime.now() - timedelta(days=2)).strftime("%Y-%m-%d")
    query = f"{category} (debate OR controversy OR hot OR trending) min_faves:100 since:{since} filter:has_engagement"
    url = "https://api.twitterapi.io/twitter/tweet/advanced_search"
    headers = {"x-api-key": TWITTERAPI_IO_KEY}
    params = {"query": query, "count": limit}
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
def get_hot_topics(category="crypto", limit=10):
    since = (datetime.now() - timedelta(days=2)).strftime("%Y-%m-%d")
    query = f"{category} (debate OR controversy OR hot OR trending) min_faves:100 since:{since} filter:has_engagement"
    url = "https://api.twitterapi.io/twitter/tweet/advanced_search"
    headers = {"x-api-key": TWITTERAPI_IO_KEY}
    params = {"query": query, "count": limit}
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The skill sends collected tweet content and recent prediction history to an external LLM service without any disclosure, review, or filtering. While the source data is largely public, this still creates privacy, data-governance, and prompt-injection risks because untrusted content is used to drive later decisions and external actions.

External Transmission

Medium
Category
Data Exfiltration
Content
raise

def get_professional_image(question, category):
    url = "https://api.unsplash.com/search/photos"
    headers = {"Authorization": f"Client-ID {UNSPLASH_ACCESS_KEY}"}
    params = {
        "query": f"{category} finance betting prediction market professional illustration",
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The code explicitly relies on a public unauthenticated workflow to create remote records. Any party who learns or guesses this endpoint can likely submit arbitrary market registrations, leading to spam, tampering, fraudulent entries, or operational abuse of the downstream system.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The code sends wallet address, market number, generated question, and related metadata to a public external API without any user-facing notice or consent flow. This can leak operational details and bind on-chain activity to an external service unexpectedly.

External Transmission

Medium
Category
Data Exfiltration
Content
}
    
    try:
        resp = requests.post(url, headers=headers, json=data, timeout=10)
        resp.raise_for_status()
        result = resp.json()
        print(f"Bubble registered successfully!")
Confidence
80% confidence
Finding
This line performs external transmission of generated market data to a third-party service that is described as public and unauthenticated. The risk is not mere network use, but the combination of public write access, weak trust boundaries, and sensitive operational metadata being sent off-platform.

Tainted flow: 'data' from requests.get (line 201, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
}
    
    try:
        resp = requests.post(url, headers=headers, json=data, timeout=10)
        resp.raise_for_status()
        result = resp.json()
        print(f"Bubble registered successfully!")
Confidence
84% confidence
Finding
Data derived from untrusted external sources and LLM output is forwarded to a public remote workflow without strong validation or authentication. This creates a confused-deputy style risk where manipulated upstream content can cause unintended record creation, garbage data insertion, or downstream abuse of the public endpoint.

Static analysis

No suspicious patterns detected.