Back to skill

Security audit

Bracketsbot Skill

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly a coherent BracketsBot helper, but its sharing workflow quietly uploads and syncs bracket picks using persisted remote URLs that are not safely scoped.

Review this skill before installing. It may upload your bracket picks to the BracketsBot service, store a draft token and API URL in the local picks file, and keep syncing later picks. Do not run it with untrusted picks files, untrusted policy modules, or arbitrary frontendBaseUrl values, and verify chainId, recipient, value, and calldata before using Bankr or any wallet to submit.

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

Error
Location
scripts/walk-next-game.mjs:34
Finding
Unvalidated Persisted Draft URL Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/lib/bracket-walk.mjs:176-182` - `scripts/walk-next-game.mjs:34-37` - `scripts/walk-apply-pick.mjs:49-55` **Vulnerability Type**: Server-Side Request Forgery (SSRF) through an attacker-controlled persisted URL **Risk Level**: High ### Vulnerable Code `scripts/lib/bracket-walk.mjs:176-182` reads the URL directly from the picks file without validation: ```js export const readWalkMeta = async (picksPath) => { try { const payload = await readJson(picksPath); return { draftToken: payload?.draftToken ?? null, draftApiUrl: payload?.draftApiUrl ?? null }; } catch { return { draftToken: null, draftApiUrl: null }; } }; ``` `scripts/walk-next-game.mjs:34-37` issues a GET request to that URL: ```js if (meta.draftToken && meta.draftApiUrl) { try { const res = await fetch(meta.draftApiUrl); if (res.ok) { ``` `scripts/walk-apply-pick.mjs:49-55` issues a PATCH request and transmits bracket state to that URL: ```js if (meta.draftToken && meta.draftApiUrl) { try { await fetch(meta.draftApiUrl, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ matchIndex: applied.gameIndex, winnerSeed }), }); ``` ### Technical Analysis The `draftApiUrl` value is loaded from a local picks-state JSON file. The CLI permits the picks file to be selected by the caller, and the metadata reader does not verify the URL's protocol, hostname, port, path, credentials, resolved IP address, or relationship to the configured BracketsBot frontend. Both `walk-next` and `walk-apply` subsequently pass this persisted value directly to `fetch()`. Consequently, anyone able to supply or modify a picks file can direct the process to send requests to arbitrary destinations, including: - Internet hosts controlled by an attacker - Loopback services such as `127.0.0.1` - Private-network services - Link-local or cloud metadata endpoints - Services reacha ...[truncated 1899 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Do not persist or trust a complete API URL. Persist only an opaque draft token and reconstruct the endpoint from a fixed, trusted origin. 2. Require HTTPS and enforce an exact hostname, port, and path allowlist before every request. 3. Reject URLs containing user information, fragments, unexpected query parameters, nonstandard ports, or non-HTTP schemes. 4. Resolve the hostname and reject loopback, private, link-local, multicast, and reserved IP ranges for both IPv4 and IPv6. 5. Disable automatic redirects or validate every redirect destination using the same policy. 6. Validate the draft token against a strict format before using it in a path. 7. Validate downloaded draft data before modifying local state. Require exactly the expected schema, bounds, and legal bracket progression. 8. Apply response-size limits and request timeouts to reduce denial-of-service exposure. 9. Consider cryptographically binding persisted metadata to the configured trusted origin if state files may cross trust boundaries. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/share-link.mjs:63
Finding
Share-Link Workflow Uploads Picks and Enables Continuing Remote Synchronization Without Clear Disclosure<![CDATA[ ## Vulnerability Details **File Locations**: - `scripts/share-link.mjs:63-89` - `scripts/walk-apply-pick.mjs:49-55` - `scripts/walk-next-game.mjs:34-75` - `README.md:67-88` - `SKILL.md:55-60` **Vulnerability Type**: Undisclosed remote data transmission and persistent synchronization metadata **Risk Level**: Medium ### Vulnerable Code `scripts/share-link.mjs:63-89` uploads the complete padded picks array and persists a token and remote API URL: ```js // Create draft session via API const apiUrl = `${baseUrl}/api/draft`; const res = await fetch(apiUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ picks: padded }), }); if (!res.ok) { const text = await res.text(); throw new Error(`Draft API returned ${res.status}: ${text}`); } const { token } = await res.json(); const shareUrl = `${baseUrl}/?draft=${token}`; const draftApiUrl = `${baseUrl}/api/draft/${token}`; // Merge draft token into the walk state file try { const raw = await readFile(predictionFile, "utf8"); const walkState = JSON.parse(raw); walkState.draftToken = token; walkState.draftApiUrl = draftApiUrl; await writeFile(predictionFile, JSON.stringify(walkState, null, 2) + "\n", "utf8"); } catch { // Best-effort — don't fail if we can't update the walk state } ``` Future calls to `walk-apply` automatically transmit updates: ```js if (meta.draftToken && meta.draftApiUrl) { try { await fetch(meta.draftApiUrl, { method: "PATCH", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ matchIndex: applied.gameIndex, winnerSeed }), }); ``` Future calls to `walk-next` automatically retrieve and apply server state: ```js if (meta.draftToken && meta.draftApiUrl) { try { const res = await fetch(meta.draftApiUrl); if (res.ok) { const draft = await res.json(); ``` ### Technical Analysis The `share-link` command is not merely a local URL encoder. It sends all bracket picks ...[truncated 2919 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require explicit, informed user consent before uploading picks. State the destination, transmitted fields, synchronization behavior, and expected retention. 2. Prefer the already documented self-contained `?p=` URL encoding when server-side storage is unnecessary. 3. Separate local link generation from remote draft creation, for example: - `share-link` for a local encoded URL - `create-remote-draft` for an explicit server upload 4. Make continuing synchronization independently opt-in rather than automatically enabling it after link creation. 5. Provide commands to disable synchronization and delete the remote draft. 6. Persist only the minimum metadata required. Avoid storing a complete remote URL. 7. Treat the draft token as a secret: - Do not print it separately when the share URL is sufficient. - Create state files with restrictive permissions. - Redact tokens from logs and errors. - Define expiration and revocation behavior. 8. Validate the API response schema and token format before constructing URLs or persisting state. 9. Update `README.md` and `SKILL.md` so the documented behavior exactly matches the implementation. 10. Do not instruct the Agent to execute `share-link` before the user has selected and consented to the browser-sharing path. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
Findings (10)

Memory Manipulation

High
Category
Memory Poisoning
Content
- lock externally around `PICKS_FILE`, or
- assign one writer process and use message passing

Without coordination, concurrent writes can overwrite state.

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

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The script resolves a module path directly from the POLICY_MODULE environment variable and then dynamically imports and executes its exported function. This gives whoever controls the environment or invocation context the ability to run arbitrary JavaScript with the script's privileges, which is effectively arbitrary code execution and exceeds the apparent purpose of selecting a bracket policy.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
This script generates a transaction payload and immediately submits it through the Bankr CLI without any explicit user review, confirmation prompt, or display of critical transaction fields. In a wallet-submission context, that is dangerous because users may sign and broadcast an unintended transaction, especially if the generated JSON is malformed, manipulated, or points to an unexpected recipient/value.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
This script is expected to prepare and print a transaction request, but it also performs an unrelated side effect by mutating the local walk-state file and deleting draft metadata. That hidden state change can disrupt user workflows, invalidate in-progress drafts, and create integrity/availability issues because a read-only preparation step is not expected to alter local state.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script silently edits a picks state file without any user-facing notice, confirmation, or dry-run mode, which makes the side effect non-obvious and hard to audit. In an agent skill context, hidden local file mutation is especially risky because users may invoke the tool expecting only transaction generation, yet lose draft session data needed for later submission or recovery.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The script performs a network PATCH as a side effect of applying a local bracket pick, even though its primary function is presented as bracket generation/validation/submission logic. Because the destination URL is taken from metadata (`meta.draftApiUrl`) rather than a fixed trusted endpoint, a tampered picks/meta file could cause outbound requests to an arbitrary host and exfiltrate pick activity or be used as an SSRF primitive.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The script silently transmits pick data (`matchIndex`, `winnerSeed`) to a remote API whenever draft metadata is present, without any visible warning or confirmation to the user. In a bracket-selection skill, this may be functionally related, but hidden transmission still creates a privacy and transparency issue and can surprise operators who expect only local file updates.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
Issuing HTTP PATCH requests to a metadata-provided URL is dangerous because the URL is effectively attacker-controlled if the metadata file can be modified. That broadens the script from local bracket processing into arbitrary outbound HTTP interaction, enabling unauthorized data transmission and potential server-side request forgery against internal or sensitive endpoints reachable from the execution environment.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"semantic:run": "pnpm run cli semantic-run"
  },
  "dependencies": {
    "incur": "^0.3.3",
    "viem": "^2.22.17"
  }
}
Confidence
94% confidence
Finding
The dependency uses a caret range (^0.3.3), which allows automatic installation of newer compatible versions under semver rules. This increases supply-chain risk because future upstream releases could introduce malicious code or breaking behavior without this package explicitly changing its manifest.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "incur": "^0.3.3",
    "viem": "^2.22.17"
  }
}
Confidence
94% confidence
Finding
The dependency uses a caret range (^2.22.17), permitting installation of newer upstream versions within the allowed semver range. In a CLI skill that may prepare transactions or interact with blockchain tooling, unreviewed dependency updates can alter transaction logic or introduce supply-chain compromise risk.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/cli.mjs:22

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/share-link.mjs:46

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/walk-apply-pick.mjs:17

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/walk-next-game.mjs:16