x402 Bazaar processes real USDC payments. Security is not an afterthought — it's the product.
This page documents what we do to protect users, providers, and their funds.
If you find a security issue, please email robin.fuchs57@hotmail.com with subject [SECURITY]. Do not open a public GitHub issue for security-sensitive bugs.
We commit to acknowledging reports within 48 hours. For critical vulnerabilities (RCE, wallet drain, credential leak), we aim to patch within 24h of confirmation.
We're in the process of setting up a formal bug bounty program on Immunefi — stay tuned.
We never see your private keys. Ever.
- Agents sign transactions locally in the MCP server, SDK, or CLI — the signed transaction is broadcast directly to the chain RPC, never through our backend.
- The MCP local wallet is encrypted at rest with AES-256-GCM. If you set
WALLET_PASSPHRASE, the key is derived via PBKDF2 (100k iterations, per-wallet random salt). Without a passphrase, a legacy machine-bound key is used and a loud warning is logged. export_private_key(MCP tool) never returns the key in its response — it only shows where the encrypted file lives on disk.
Providers who need upstream auth (Bearer, API key, Basic) give us their secret once. We:
- Encrypt it with AES-256-GCM (random 12-byte IV, 16-byte auth tag) using a key stored only in Render env (
CREDENTIALS_ENCRYPTION_KEY) - Decrypt only in-memory at request time
- Never log, never return the plaintext
- Sanitize every
services.*API response to replaceencrypted_credentialswithhas_credentials: true
Every transaction hash can only be consumed once:
- A compound key
{chain}:{txHash}(or{chain}:split_provider:{txHash}for split mode) is inserted intoused_transactionsatomically via anINSERTthat fails on duplicate - Before the INSERT, an in-memory
_pendingClaimsset prevents two concurrent requests on the same process from reaching the DB race - On refund failure, the row is rolled back to
status: rolled_backrather than deleted, so we keep the audit trail
Provider URLs are validated via lib/safe-url.js:
- DNS resolution forced to IPv4 with
family: 4 - Private ranges blocked:
10.*,172.16–31.*,192.168.*,127.*,169.254.*,::1,fc00::,fe80:: - 10-second TTL cache on resolved IPs to mitigate DNS rebinding
- Also applied to ai.js, intelligence.js, web.js wrappers
- Authorizations carry a
validAfterofnow - 30s,validBeforeofnow + maxTimeoutSeconds(capped at 300s) - Nonces are 32 random bytes, kept in an in-memory set for the session
- Signatures are bound to the caller's chain (
chainIdin EIP-712 domain) — cross-chain replay impossible
- Legacy:
X-Admin-Tokenheader compared viacrypto.timingSafeEqualon equal-length padded buffers (avoids timing leak on length) - Modern:
admin_sessioncookie,httpOnly+Secure+SameSite=Strict. XSS on any frontend page cannot read this cookie. - 12-hour session lifetime, sliding window
- Failed login attempts logged with IP
Three tiers (via express-rate-limit):
- General: 500 req / 15 min / IP
- Paid endpoints: 120 req / min / IP (skipped if
X-Payment-TxHashpresent) - Registration: 10 req / hour / IP
- Admin auth: 10 req / min / IP with
skipSuccessfulRequests: false
Plus a per-wallet rate limit (walletRateLimitStore) to prevent a single signer from DoS-ing the proxy.
- IPs are hashed with HMAC-SHA256 (
IP_HASH_SECRETin env, 32 bytes) — never stored plaintext - Increment uses an atomic Postgres RPC (
increment_free_usage) withINSERT … ON CONFLICT DO UPDATE SET count = count + 1 RETURNING count— no TOCTOU window - Retention: 7 days, then the hash row is deleted (GDPR-friendly)
If the upstream API returns a 5xx, empty body, or content that fails schema validation:
- The payment is automatically refunded on-chain within 60 seconds
- The provider does NOT receive their 95% share for that call
- The transaction is marked
used_transactions.status = rolled_back - Your wallet balance is restored; you can retry with the same tx hash
See lib/refund.js, routes/proxy-execute.js.
All user-supplied objects (required_parameters, body, query params) are filtered to strip __proto__, constructor, prototype keys before any assignment. See routes/proxy-execute.js.
- Strict origin whitelist (x402bazaar.org, www.x402bazaar.org, Vercel preview URLs, localhost for dev)
- Non-browser requests (no
Originheader — CLI, MCP, server-to-server) are allowed (no browser policy to enforce) credentials: truefor admin cookie
Via helmet():
Strict-Transport-Security: max-age=63072000; includeSubDomains; preloadX-Content-Type-Options: nosniffX-Frame-Options: DENYReferrer-Policy: strict-origin-when-cross-originContent-Security-Policywith strict whitelist of script-src, connect-src, img-src
Proxied upstream responses additionally receive Content-Security-Policy: default-src 'none' to neutralize any malicious HTML/JS an upstream could try to inject.
All tables with user data have RLS enabled:
activity,monitoring_checks,used_transactions,pending_payouts: service-role onlyservices: public read withstatus NOT IN ('quarantined', 'pending_validation')policy (enforced at DB level, not just in the backend code)reviews,budgets: service-role write, public read
npm audit --audit-level=highblocks PRs on the backend CI- Python SDK:
ruff+mypy+safetyin CI - Dependabot (or equivalent) scheduled weekly for all 7 repos
- Solidity 0.8.24 (built-in overflow protection)
ReentrancyGuardondistribute()andemergencyWithdraw()SafeERC20for every token transfer- 66 Hardhat tests covering nominal + edge cases (
amount = 1,amount = 0, rounding drift) - Deployed on Polygon mainnet:
0x820d4b07D09e5E07598464E6E36cB12561e0Ba56— verified on Polygonscan
A third-party audit (Trail of Bits or ConsenSys Diligence) is on the roadmap.
| Date | Action |
|---|---|
| 2026-04-17 | Hardened mathjs sandbox (mitigates GHSA-jvff-x2qm-6286 class) |
| 2026-04-17 | Fixed EIP-3009 header swap that burned USDC on upstream retries |
| 2026-04-17 | Migrated admin auth from sessionStorage to httpOnly cookie |
| 2026-04-17 | Added WALLET_PASSPHRASE (PBKDF2) for MCP wallet encryption |
| 2026-04-17 | HMAC-SHA256 IP hashing with server-side salt (was SHA-256 plain) |
| 2026-04-17 | Atomic free-tier increment (closed TOCTOU bypass) |
| 2026-04-17 | RLS policies tightened: anon cannot read quarantined services |
| 2026-04-17 | Wider URL scrubbing in access logs (token, key, apikey, sig, auth, password) |
| 2026-04-17 | Removed decryptCmd leak from export_private_key MCP tool |
| 2026-04-14 | Quarantine system for bare-402 APIs |
| 2026-04-10 | Webhook SSRF check |
| 2026-04-06 | Payment protection audit + 6 fixes |
| 2026-04-02 | Pre-investor full platform audit + 15 fixes |
Full audit report in AUDIT_COMPLET_2026-04-17.md.
- Internal audit (10 specialized agents in parallel, 2026-04-17):
AUDIT_COMPLET_2026-04-17.md - Triangulation audit (Claude + Codex + Gemini consensus, 2026-04-14):
docs/triangulations/2026-04-14-complete-platform-audit.md
External audits planned:
- Trail of Bits / ConsenSys Diligence — FeeSplitter contract (Q2 2026)
- SOC 2 Type II certification process — starting Q3 2026