T09 · Insecure Skill Coding Practices
- Location
scripts/place-bet.js:188- Finding
Remote Bet Payload Is Signed and Submitted Without Complete Destination and Domain Validation
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This real-money betting skill matches its stated purpose, but it needs Review because it can sign and submit wallet transactions with weak validation and includes a confirmation-bypass flag.
Install only if you are comfortable giving this skill access to a wallet private key that can spend real funds. Use a dedicated low-balance betting wallet, avoid storing a raw private key in the skill workspace if possible, review every transaction manually, do not use --yes, and treat Pinwin/Azuro remote payloads as trusted only if you accept the current validation gaps.
scripts/place-bet.js:188Remote Bet Payload Is Signed and Submitted Without Complete Destination and Domain Validation
scripts/claim-bets.js:92Remote Claim Payload Can Specify Unverified Value, Chain, and Contract Calldata
scripts/place-bet.js:46Command-Line Flag Bypasses Mandatory Confirmation for Real-Money Bets
scripts/place-bet.js:146Mandatory Active-Market Verification Is Skipped and Falsely Reported as Successful
scripts/watch-bets.js:73External Match Data Can Be Reintroduced Through the Agent Prompt Channel
scripts/package-lock.json:5Incomplete Lockfile Leaves Wallet-Signing Dependencies Unpinned
There is a material description-behavior mismatch. The code’s primary purpose is limited to discovering and displaying games and main-market odds using HTTPS calls to public Azuro/onchainfeed endpoints and local dictionary helpers. It has no wallet logic, no blockchain RPC interaction, no transaction construction, no EIP-712 signing, no user/account context, and no claim/status handling. The only part of the description that matches is browsing/searching games and odds from the Azuro feed. Because the declared purpose emphasizes placing/claiming on-chain bets and related account actions, while the actual code is just a game/odds fetcher, this should be flagged as a mismatch.
The code is clearly related to the declared domain (Pinwin/Azuro on-chain sports betting) and does implement bet placement via EIP-712 signing on Polygon, so it is not unrelated. However, the declared description presents a broader multi-capability skill: browsing games and odds, placing bets, checking bets, and claiming winnings. This code chunk only implements the bet placement path and immediate confirmation polling for that new order. It even defines constants related to claims and feed endpoints, but does not use them here. Because the supplied chunk does not actually perform several prominently declared capabilities, the description overstates what this specific code chunk does, making it a description/behavior mismatch.
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
Referenced artifact was not completely inspected
ro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles di
The skill explicitly instructs storing a live wallet private key in a .env file inside the skill workspace, while the skill metadata declares environment access and the document itself discusses model-readable config risks. In this context, any compromise of the workspace, scripts, or agent runtime could expose a high-value credential capable of authorizing irreversible on-chain transactions.
The recommended approach is a .env file with restricted permissions — the key is read only by the Node process and never stored in any config file the model can read:
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
The instruction that scripts load the .env automatically increases secret exposure by normalizing local plaintext key storage and automated secret consumption. For a betting skill that can sign blockchain transactions, compromise of that file means direct fund loss and account takeover for all supported actions.
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
# Scripts load it automatically at runtime — no manual export needed
This finding reinforces that the workspace .env contains a wallet private key used for real-money transactions. Even with chmod 600, plaintext local storage remains dangerous because the threat model includes agent-accessible files, compromised scripts, backups, logs, or other local processes.
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
# Scripts load it automatically at runtime — no manual export needed
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.
1. **Check allowance** — if `allowance(bettor, relayer) >= stake + fee`, approval was already sent. Skip Step 5.
2. **Check for pending bets** — query the bets subgraph for `status: "Accepted"` bets from this wallet placed in the last 10 minutes. If found, poll that order instead of creating a new one.
3. If no pending bet found and no clear state, start the flow from Step 1 with a fresh game fetch.
---
The skill declares high-risk capabilities including environment-variable access and unrestricted network interaction, yet does not define an explicit tool scope such as allowed-tools or permissions. In a skill that can access a private key and initiate on-chain activity, lack of least-privilege scoping increases the blast radius if the skill is invoked unexpectedly or modified later.
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
| **clientCore** (bet payload verification) | `0xF9548Be470A4e130c90ceA8b179FCD66D2972AC7` |
| **claimContract** (LP claim, redeem won/canceled bets) | `0x0FA7FB5407eA971694652E6E16C12A52625DE1b8` |
| **environment** | `PolygonUSDT` |
| **data-feed URL** | `https://api.onchainfeed.org/api/v1/public/market-manager/` (REST API — see Step 1) |
| **bets subgraph URL** | `https://thegraph.onchainfeed.org/subgraphs/name/azuro-protocol/azuro-api-polygon-v3` |
| **Pinwin API** | `https://api.pinwin.xyz` |
| **Polygonscan** | `https://polygonscan.com/tx/{txHash}` |
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
| **clientCore** (bet payload verification) | `0xF9548Be470A4e130c90ceA8b179FCD66D2972AC7` |
| **claimContract** (LP claim, redeem won/canceled bets) | `0x0FA7FB5407eA971694652E6E16C12A52625DE1b8` |
| **environment** | `PolygonUSDT` |
| **data-feed URL** | `https://api.onchainfeed.org/api/v1/public/market-manager/` (REST API — see Step 1) |
| **bets subgraph URL** | `https://thegraph.onchainfeed.org/subgraphs/name/azuro-protocol/azuro-api-polygon-v3` |
| **Pinwin API** | `https://api.pinwin.xyz` |
| **Polygonscan** | `https://polygonscan.com/tx/{txHash}` |
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.
| **clientCore** (bet payload verification) | `0xF9548Be470A4e130c90ceA8b179FCD66D2972AC7` |
| **claimContract** (LP claim, redeem won/canceled bets) | `0x0FA7FB5407eA971694652E6E16C12A52625DE1b8` |
| **environment** | `PolygonUSDT` |
| **data-feed URL** | `https://api.onchainfeed.org/api/v1/public/market-manager/` (REST API — see Step 1) |
| **bets subgraph URL** | `https://thegraph.onchainfeed.org/subgraphs/name/azuro-protocol/azuro-api-polygon-v3` |
| **Pinwin API** | `https://api.pinwin.xyz` |
| **Polygonscan** | `https://polygonscan.com/tx/{txHash}` |
Persisting BETTOR_PRIVATE_KEY in a workspace .env creates long-lived sensitive state on disk, increasing the window for theft and misuse across sessions. Because the credential directly authorizes on-chain transactions, session persistence here materially raises the chance of unauthorized betting or fund exfiltration if the environment is later accessed.
The recommended approach is a .env file with restricted permissions — the key is read only by the Node process and never stored in any config file the model can read:
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
# Scripts load it automatically at runtime — no manual export needed
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# Create .env in the skill workspace
echo "BETTOR_PRIVATE_KEY=0xyour_private_key_here" > ~/.openclaw/workspace/skills/sports-betting/.env
chmod 600 ~/.openclaw/workspace/skills/sports-betting/.env
# Scripts load it automatically at runtime — no manual export needed
Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal