Please report security vulnerabilities via GitHub Private Vulnerability Reporting.
Do not open a public GitHub issue for security vulnerabilities. Public disclosure before a fix is available puts all users at risk.
If you are unable to use GitHub's private reporting feature, you may contact the maintainers directly by opening a draft security advisory on this repository.
The following areas are considered in-scope for security reports:
- MCP server tool implementations — logic bugs, injection vulnerabilities, or unsafe behaviors in any tool handler
- Wallet management and key handling — improper key storage, exposure of private keys or mnemonics, insecure key derivation
- Transaction signing and broadcasting — incorrect signature validation, transaction malleability, replay attacks
- Authentication and authorization logic — bypasses, privilege escalation, or improper access controls
- Input validation — missing or insufficient sanitization that could lead to exploitable conditions
The following are considered out-of-scope:
- Vulnerabilities in third-party dependencies — please report these directly to the upstream project
- Issues requiring physical access to a user's machine
- Social engineering attacks
- Denial-of-service issues that require significant resources from the reporter
- Security weaknesses in underlying blockchain networks (Bitcoin, Stacks) themselves
- Issues already known and tracked in public GitHub issues
We are committed to addressing security reports promptly:
| Stage | Target |
|---|---|
| Acknowledgment of report | Within 48 hours |
| Initial severity assessment | Within 7 days |
| Status update | Every 7 days until resolved |
| Fix and release timeline | Depends on severity (critical: ASAP, high: within 30 days, medium/low: within 90 days) |
We follow coordinated disclosure. Please:
- Report the vulnerability privately using the channel above
- Allow us reasonable time to investigate and release a fix before any public disclosure
- Make a good-faith effort to avoid privacy violations, data destruction, or disruption of service during research
We will publicly acknowledge your contribution in the release notes (unless you prefer to remain anonymous) once a fix has been issued.
Security fixes are applied to the latest released version. We do not backport fixes to older versions unless the severity warrants it.
| Version | Supported |
|---|---|
| Latest release | Yes |
| Older releases | No |
A leaked seed phrase or private key is unrecoverable — once an attacker has it, they can drain the wallet, and there is no way to revoke or rotate the seed itself. The only fix is to move funds to a new wallet. So the entire strategy is prevention.
Prefer wallet_create / wallet_import. These store an AES-256-GCM-encrypted
keystore (scrypt KDF) under ~/.aibtc/ — outside any git repository — so a
plaintext seed never lives in a file that can be committed.
CLIENT_MNEMONIC is a power-user escape hatch only. The installed server takes it from the MCP client config's env block, never from a .env file; in this repo, only npm run dev / npm start read ./.env. A seed in a
plaintext file is one git add away from being burned forever. If you use it, keep
it out of version control (.gitignore blocks .env and .env.*).
This repo ships layered protection so a seed can't slip into git history:
- Pre-commit hook (
.githooks/pre-commit, auto-installed vianpm install) blocks commits containing a seed phrase assigned to a mnemonic/seed/secret field, BIP32 extended keys (xprv/zprv), or named private keys. This is the primary gate. It runs the built-in scanner with zero dependencies, and also invokesgitleaksif you have it installed. - GitHub push protection (repo setting) is the server-side backstop that no
local bypass can defeat. Because the hook is local — bypassable with
git commit --no-verifyor a commit from a machine that never rannpm install— keep push protection enabled as the net the hook can't be.
If the hook ever blocks a legitimate commit, bypass with git commit --no-verify.
The hook is best-effort: it auto-installs via the prepare npm script (which
won't override an existing core.hooksPath, so husky/lefthook users keep theirs),
but npm ci --ignore-scripts and fresh clones that skip npm install won't have
it. Keep GitHub push protection on as the enforcement those workflows can't skip.
- Spending limit (default-on). Every path that signs a spend is metered
against a cumulative per-session and per-day cap (default 50 STX + 50,000
sats): STX transfers and contract calls (direct and sponsored, fee included),
x402/L402 auto-payments,
lightning_pay_invoice, every Bitcoin broadcast (measured as the sats that actually leave the wallet, fee included) andpsbt_sign. A spend that would exceed it is blocked before it is signed or broadcast, so a single prompt-injected call (or a malicious endpoint looping sub-cap payments) can't drain the wallet. Contract calls in post-condition mode Allow must declare the most STX/sBTC they take from the caller; that bound is added as an on-chain post condition and metered. Override per wallet withSPEND_LIMIT_DAILY_USTX/SPEND_LIMIT_SESSION_USTX/SPEND_LIMIT_DAILY_SATS/SPEND_LIMIT_SESSION_SATSin the MCP client config, or disable withSPEND_LIMIT_ENABLED=false. The error message tells the agent to ask the user rather than how to change these.- What it is not. The limit runs inside this server process. It stops
mistakes and malicious endpoints that go through the tools; it does not
contain an agent that has shell access to this machine, which could edit
the client config or read the keystore once given the password. Keep spend
tools out of your client's auto-approve list, and do not enable the
walletgroup (it holdswallet_export) for an agent you do not trust with the mnemonic. schnorr_sign_digestsigns raw digests, and a signed BIP-341 sighash can spend outside every meter, so it is refused unless the user setsAIBTC_ALLOW_BLIND_SIGN=truein the server config.- The
satsledger is keyed by the active Stacks address so BTC L1, sBTC, and L402 spends share one budget.lightning_pay_invoiceuses the same key, but because the Lightning wallet unlocks in its own session, a pay made while the main STX wallet is locked is metered against a separate__lightning__bucket instead. This is intentional (an always-metered fallback rather than an unmetered pass), but it means a locked-STX user has twosatsbuckets; unlock the STX wallet before paying to keep everything on one budget. Amountless BOLT-11 invoices are refused (an unmeterable spend would defeat the cap).
- What it is not. The limit runs inside this server process. It stops
mistakes and malicious endpoints that go through the tools; it does not
contain an agent that has shell access to this machine, which could edit
the client config or read the keystore once given the password. Keep spend
tools out of your client's auto-approve list, and do not enable the
- Keep only working funds in the agent's hot wallet; hold the rest in cold storage.
- The x402 payment flow also enforces a per-transaction spend cap.
- Wallets auto-lock after an idle timeout (
wallet_set_timeout); lock manually withwallet_lockwhen stepping away.
Assume the wallet is compromised the moment the secret touches a commit, a log, a chat, or any shared surface — even briefly.
- Immediately create a fresh wallet (
wallet_create) on a clean machine. - Sweep all funds (BTC, STX, sBTC, tokens, NFTs) from the exposed wallet to the new one. Move the most valuable assets first — attackers automate this.
- Never reuse the exposed seed for anything, ever.
- Rotate any API keys (Hiro, sponsor relay, Bitflow, Pillar) that shared the
.env, in case the whole file leaked. - If the secret reached a git commit, the commit is already burned even if you delete it — rotating funds is the only real remedy, not history rewriting.