Back to skill

Security audit

Sports Betting

Security checks for vulnerabilities and agentic risk

Overview

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.

Vulnerability Patterns
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • 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
Findings (6)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/place-bet.js:188
Finding
Remote Bet Payload Is Signed and Submitted Without Complete Destination and Domain Validation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/place-bet.js:188-291` **Vulnerability Type**: Insufficient validation of a remotely supplied EIP-712 payload and submission URL **Risk Level**: High ### Vulnerable Code ```js const payload = JSON.parse(Buffer.from(betRes.encoded, 'base64').toString('utf8')) const cd = payload.signableClientBetData const payloadStake = BigInt(cd.bet?.amount ?? cd.bets?.[0]?.amount ?? cd.amount ?? 0) const payloadCondId = cd.bet?.conditionId ?? cd.bets?.[0]?.conditionId const payloadOutcome = cd.bet?.outcomeId ?? cd.bets?.[0]?.outcomeId const coreAddr = cd.clientData?.core?.toLowerCase() if (String(payloadStake) !== String(stakeAmount)) { console.error(`❌ Payload stake mismatch: expected ${stakeAmount}, got ${payloadStake}`) process.exit(1) } if (String(payloadCondId) !== String(conditionId)) { console.error(`❌ conditionId mismatch: expected ${conditionId}, got ${payloadCondId}`) process.exit(1) } if (String(payloadOutcome) !== String(outcomeId)) { console.error(`❌ outcomeId mismatch: expected ${outcomeId}, got ${payloadOutcome}`) process.exit(1) } if (coreAddr.toLowerCase() !== CLIENT_CORE.toLowerCase()) { console.error(`❌ Core address mismatch: expected ${CLIENT_CORE}, got ${coreAddr}`) process.exit(1) } const primaryType = payload.types.ClientComboBetData ? 'ClientComboBetData' : 'ClientBetData' const bettorSignature = await walletClient.signTypedData({ account, domain: payload.domain, types: payload.types, primaryType, message: payload.signableClientBetData, }) const submitUrl = new URL(payload.apiUrl) const submitHost = submitUrl.hostname const submitPath = submitUrl.pathname + submitUrl.search const submitRes = await postJson(submitHost, submitPath, { environment: payload.environment, bettor, betOwner: bettor, clientBetData: payload.apiClientBetData, bettorSignature, }) ``` ### Technical Analysis The Pinwin API supplies the EIP-712 domain, type definitions, signed ...[truncated 1647 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Allowlist the exact HTTPS submission host and permitted order paths. - Reject URLs containing unexpected ports, credentials, fragments, or redirects. - Require Polygon chain ID `137` in the EIP-712 domain. - Pin the expected domain name, version, and verifying contract. - Define the EIP-712 schema locally instead of trusting `payload.types`. - Validate every signed field, including relayer, expiration, fee, environment, affiliate, sponsorship, owner, and nonce. - Enforce an explicit maximum relayer fee approved by the user. - Display all validated fields before confirmation. - Fail closed if any expected field is absent or has an unexpected type. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/claim-bets.js:92
Finding
Remote Claim Payload Can Specify Unverified Value, Chain, and Contract Calldata<![CDATA[ ## Vulnerability Details **File Location**: `scripts/claim-bets.js:92-120` **Vulnerability Type**: Insufficient validation of a remotely supplied blockchain transaction **Risk Level**: High ### Vulnerable Code ```js const payload = JSON.parse(Buffer.from(body.encoded, 'base64').toString('utf8')) console.log('\n📋 Decoded claim payload:', JSON.stringify(payload, null, 2)) if ((payload.to || '').toLowerCase() !== CLAIM_CONTRACT.toLowerCase()) { throw new Error( `❌ Claim contract mismatch! payload.to=${payload.to} expected=${CLAIM_CONTRACT}` ) } const rl = createInterface({ input: process.stdin, output: process.stdout }) const answer = await new Promise(resolve => { rl.question( `\n🛑 CONFIRM CLAIM\n Bet IDs: ${betIds.join(', ')}\n Proceed? (type "yes" to confirm): `, ans => { rl.close() resolve(ans.trim().toLowerCase()) } ) }) const value = payload.value != null ? BigInt(payload.value) : 0n const hash = await walletClient.sendTransaction({ to: payload.to, data: payload.data, value, chainId: Number(payload.chainId), }) ``` ### Technical Analysis The script verifies only that `payload.to` equals the expected claim contract. It does not require: - `payload.chainId` to equal Polygon chain ID `137`. - `payload.value` to equal zero. - `payload.data` to invoke the expected claim function. - The calldata bet IDs to match the IDs displayed to the user. - The calldata to avoid unrelated contract operations. This also conflicts with the Skill documentation, which states that no POL value is sent during a claim. ### Attack Path 1. An attacker compromises or impersonates `https://api.pinwin.xyz/agent/claim`. 2. The service returns a payload whose `to` field is the expected contract. 3. The payload contains a nonzero native-token value, unexpected chain ID, or calldata for an unintended contract operation. 4. The confirmation prompt displays only the locally requested bet IDs and does not decode or explain the ...[truncated 499 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require `Number(payload.chainId) === 137`. - Require `BigInt(payload.value ?? 0) === 0n`. - Define the expected claim ABI locally. - Decode `payload.data` before confirmation. - Verify the function selector and exact ordered set of bet IDs. - Reject trailing, malformed, or unexpected calldata. - Verify that directly supplied bet IDs belong to the wallet and are redeemable. - Present decoded contract, function, bet IDs, chain, and zero value in the confirmation prompt. - Abort if any field cannot be independently verified. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/place-bet.js:46
Finding
Command-Line Flag Bypasses Mandatory Confirmation for Real-Money Bets<![CDATA[ ## Vulnerability Details **File Location**: `scripts/place-bet.js:46, 237-251` **Vulnerability Type**: Authorization and safety-control bypass **Risk Level**: High ### Vulnerable Code ```js const skipConfirm = args.includes('--yes') // skip interactive confirmation if (!skipConfirm) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }) const answer = await new Promise(resolve => { rl.question( `\n🛑 CONFIRM BET\n ${Number(payloadStake) / 1e6} USDT on ${marketName} — outcome "${selectionLabel}" @ ${currentOdds.toFixed(2)}\n Proceed? (type "yes" to confirm): `, ans => { rl.close() resolve(ans.trim().toLowerCase()) } ) }) if (answer !== 'yes' && answer !== 'y') { console.log('\n❌ Bet cancelled by user.') process.exit(0) } } else { console.log('\n⚠️ [--yes flag] Skipping interactive confirmation.') } ``` ### Technical Analysis The Skill documentation declares that every bet requires fresh user confirmation and that there are no exceptions. The production script nevertheless supports `--yes`, which bypasses the only in-script confirmation gate. The flag is described as intended for CI or testing, but it is not restricted to a mocked wallet, test chain, test environment, or special build. It therefore works with the real Polygon wallet and real USDT. ### Attack Path 1. An Agent, wrapper script, or operator invokes `place-bet.js` with valid bet arguments and `--yes`. 2. The interactive confirmation is skipped. 3. The script may approve USDT for the relayer. 4. It signs and submits the bet using the configured private key. 5. Real funds are committed without contemporaneous in-script user authorization. ### Impact Assessment This can authorize real USDT allowance changes and betting transactions from the configured wallet. The scope is the requested stake plus fees and approval buffer for each invocation. Repeated invocations could place ...[truncated 33 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove the `--yes` option from production code. - If automated tests need noninteractive execution, use a separately built test entry point. - Require a mocked wallet client and a non-production chain in test mode. - Bind confirmation to a canonical summary containing the exact stake, selection, odds floor, fee, contract, and chain. - Record a short-lived confirmation token tied to those exact parameters. - Never allow an Agent-generated command-line argument alone to substitute for explicit user authorization. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/place-bet.js:146
Finding
Mandatory Active-Market Verification Is Skipped and Falsely Reported as Successful<![CDATA[ ## Vulnerability Details **File Location**: `scripts/place-bet.js:146-162` **Vulnerability Type**: Missing transaction precondition validation and misleading status output **Risk Level**: Medium ### Vulnerable Code ```js // The REST conditions-by-game-ids endpoint requires gameId, not conditionId. // Since place-bet only receives conditionId, we use the --odds flag value and // rely on Pinwin /agent/bet to reject if the condition is no longer active. // The 2% minOdds slippage covers normal odds movement between fetch and submit. console.log('\n⏳ Checking odds and preparing bet...') if (!oddsArg) { console.error('❌ --odds is required. Always pass --odds from the get-games.js JSON output.') process.exit(1) } const currentOdds = parseFloat(oddsArg) const SLIPPAGE = 0.02 const minOdds = BigInt(Math.round(currentOdds * (1 - SLIPPAGE) * 1e12)) console.log( ` ✅ Condition Active. Current odds: ${currentOdds.toFixed(4)} → minOdds (2% slip): ${minOdds}` ) ``` ### Technical Analysis The documented preflight process requires the exact condition to be rechecked immediately before calling `/agent/bet`. The implementation performs no network request at this stage. It relies on a stale odds argument and on the remote betting API to reject an inactive market. The message `Condition Active` is therefore emitted without verifying the condition state. Odds slippage and market activity are separate properties; applying a 2% odds tolerance does not establish that the market remains open. ### Attack Path 1. The user fetches a game while its market is active. 2. The market closes, suspends, or resolves before bet execution. 3. The script receives the previously fetched condition and odds. 4. It skips the required state lookup and reports that the condition is active. 5. It requests a bet payload using stale market data. 6. The request may fail, or downstream behavior may differ from the state represented to the user. ### Impact Assessment The issue primar ...[truncated 262 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pass the selected `gameId` to `place-bet.js`. - Query the fixed `conditions-by-game-ids` endpoint immediately before requesting the bet payload. - Locate the exact condition and verify `state === "Active"`. - Validate that the selected outcome remains part of that condition. - Refresh the current odds from the same response rather than trusting a command-line value. - Abort and require a new user selection and confirmation if the state or odds exceed the approved tolerance. - Never print a successful validation message unless that validation actually occurred. ]]>

T01 · Skill Instruction Hijacking

Warning
Location
scripts/watch-bets.js:73
Finding
External Match Data Can Be Reintroduced Through the Agent Prompt Channel<![CDATA[ ## Vulnerability Details **File Location**: `scripts/place-bet.js:371-390`; `scripts/watch-bets.js:73-76, 166-209` **Vulnerability Type**: Indirect prompt injection through asynchronous notifications **Risk Level**: Medium ### Vulnerable Code ```js const watchArgs = [ require('path').join(__dirname, 'watch-bets.js'), '--bettor', bettor, '--starts-at', startsAtArg, '--match', matchArg || 'Unknown match', '--selection', `${marketName} — ${selectionLabel}`, '--stake', String(Number(payloadStake) / 1e6), '--odds', String(currentOdds), ] const watcher = spawn(process.execPath, watchArgs, { detached: true, stdio: ['ignore', 'pipe', 'pipe'], }) ``` ```js function notify(message) { if (typeof sendPrompt === 'function') { sendPrompt(message) } else { console.log('\n📣 NOTIFICATION:\n' + message) } } ``` ```js const msg = [ `🎉 ¡APUESTA GANADA! 🎉`, ``, `🏟 ${matchTitle}`, `🎯 Selección: ${selection} @ ${odds}`, `💰 Stake: ${stake} USDT`, `✅ Payout: ${payout} USDT (+${profit} USDT)`, ``, `¿Quieres reclamar tus ganancias ahora? Di "sí, reclama" y lo gestiono.`, ].join('\n') notify(msg) ``` ### Technical Analysis `matchArg` is derived from game information returned by an external sports-data service. It is passed to a detached watcher and later interpolated directly into a string delivered through `sendPrompt`. If `sendPrompt` treats its argument as new model input rather than inert display text, attacker-controlled game titles can become instructions in a future Agent turn. No validation, escaping, provenance marker, or separation between trusted instructions and external data is applied. The watcher itself does not execute shell commands, and `spawn` is used without a shell. The risk is specifically the semantic re-entry of untrusted network content into an Agent prompt channel. ### Attack Path 1. An attacker compromises or influences sports-feed game metadata. 2. A game title is crafted to contain inst ...[truncated 712 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Use a structured user-notification API that does not invoke the language model. - Treat match titles, participant names, and selections as untrusted display data. - Apply strict length and character validation before storing or forwarding external labels. - Clearly delimit external fields and attach provenance metadata. - Configure generated notifications so they cannot authorize transactions or override safety policy. - Require fresh user confirmation for every claim or subsequent bet regardless of notification content. - Prefer correlating the watcher to the exact bet or order ID rather than selecting the most recent wallet bet. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/package-lock.json:5
Finding
Incomplete Lockfile Leaves Wallet-Signing Dependencies Unpinned<![CDATA[ ## Vulnerability Details **File Location**: `scripts/package.json:2-5`; `scripts/package-lock.json:5-8` **Vulnerability Type**: Non-reproducible dependency installation **Risk Level**: Medium ### Vulnerable Code `scripts/package.json` declares: ```json { "dependencies": { "@azuro-org/dictionaries": "^3.0.28", "dotenv": "^16.0.0", "viem": "^2.0.0" } } ``` The lockfile root records only: ```json { "packages": { "": { "dependencies": { "@azuro-org/dictionaries": "^3.0.28" } } } } ``` ### Technical Analysis The committed lockfile does not contain `viem`, `dotenv`, or their transitive dependency trees, despite their presence in `package.json`. In particular, `viem` handles private-key derivation, signing, RPC communication, and transaction submission. A normal installation may therefore resolve dependency versions that were not represented in or reviewed through the committed lockfile. The caret ranges also permit future compatible-version updates. No malicious package was identified in the reviewed files; the finding is the loss of reproducibility and reviewability. ### Attack Path 1. A user installs dependencies from `scripts/package.json`. 2. Because the lockfile is incomplete, the package manager resolves current registry versions for missing packages. 3. A compromised, malicious, or unexpectedly changed release is selected. 4. The dependency executes in the same Node.js process or installation environment as wallet-related code. 5. It may access process environment variables, including `BETTOR_PRIVATE_KEY`, or alter transaction behavior. ### Impact Assessment A compromised wallet dependency could access the private key and obtain full control of the associated wallet. This audit did not establish that the named dependencies are malicious; the risk arises because installed versions are not completely pinned and reproducible. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Regenerate `package-lock.json` from the current `package.json`. - Verify that `viem`, `dotenv`, and all transitive dependencies are present with integrity hashes. - Commit the complete lockfile. - Install with `npm ci` rather than a mutable `npm install`. - Pin security-reviewed versions where feasible instead of broad caret ranges. - Run dependency vulnerability and provenance checks in CI. - Review lifecycle scripts for every dependency with access to the wallet execution environment. - Note that the scripts currently do not call `dotenv.config()`; either implement the documented restricted `.env` loading safely or remove the inaccurate dependency and documentation claim. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Rogue AgentSelf-Modification, Session Persistence
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (36)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
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.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
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.

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
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
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
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:

```bash
# 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
Confidence
97% confidence
Finding
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.

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 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
Confidence
97% confidence
Finding
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.

Credential Access

High
Category
Privilege Escalation
Content
```bash
# 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
```
Confidence
96% confidence
Finding
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.

Memory Manipulation

High
Category
Memory Poisoning
Content
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.

---
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
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.

External Transmission

Medium
Category
Data Exfiltration
Content
| **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}` |
Confidence
50% 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
| **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}` |
Confidence
50% 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
| **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}` |
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Session Persistence

Medium
Category
Rogue Agent
Content
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:

```bash
# 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
Confidence
93% confidence
Finding
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.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
# 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
```
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
```bash
# 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
```
Confidence
80% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access, suspicious.exposed_secret_literal

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/place-bet.js:386

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/claim-bets.js:36

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
scripts/claim-bets.js:41