| Variable | Location | Risk if leaked |
|---|---|---|
GROQ_API_KEY |
Server-side only | Cost abuse — HIGH |
STACKS_PRIVATE_KEY |
Scripts only, never in Next.js | Fund loss — CRITICAL |
EVM_PRIVATE_KEY |
Scripts only, never in Next.js | Fund loss — CRITICAL |
PAYMENT_ADDRESS |
Server-side only | Misdirected payments — MEDIUM |
X402_FACILITATOR_API_KEY |
Server-side only | Facilitator account abuse — MEDIUM |
Please report security issues privately via GitHub private issue or email. Do NOT open public issues for security vulnerabilities.
- Agent routes are rate limited to 10 requests/minute per IP (in-memory, resets on server restart)
- MCP server tools marked as premium require x402 payment header (X-Payment) on Base Network
- Bridge and swap actions require user to sign their own transactions — non-custodial
- In-memory rate limiter is per-instance; not shared across Vercel serverless function instances
create-puzzle / createPuzzle store the puzzle answer in a public map/struct
(stacks-quest.clar, stacks-quest-v2.clar, QuestGame.sol). Anyone can read it back via
get-today-puzzle / puzzles(dayId) — a plain read-only call, no payment or signature
required — immediately after the owner posts the day's puzzle, well before reveal-answer
is called. This means a player who queries the contract directly (bypassing the app's hint
UI) can submit a guaranteed-correct guess and drain that day's reward pool.
This is a design flaw in the contracts themselves, not something that can be patched
without a redeploy: Clarity and Solidity contracts here are not upgradeable/proxied, so
fixing the already-deployed mainnet/testnet contracts requires shipping a v3 with a
commit-reveal scheme (store hash(answer + salt) at create-puzzle time, reveal
answer + salt only at reveal-answer time, verify the hash before accepting claim-reward)
and migrating reward pools to it. Until that ships, keep reward pool sizes small relative to
the cost of running a script that reads get-today-puzzle before playing.
The /api/mcp premium-tool gate does two layers of checking, both in app/api/mcp/route.ts:
- Structural validation (
isPaymentPayloadValid): theX-Paymentheader must base64/JSON-decode into anexact-scheme, Base-network payload addressed toPAYMENT_ADDRESS, for at leastPRICE_USDC, within itsvalidAfter/validBeforewindow, carrying a signature-shaped value. Blocks trivial/garbage headers. - Facilitator verify + settle (
facilitatorVerifyAndSettle): calls a real x402 facilitator's/verifythen/settleendpoints so the request must carry an actually-valid EIP-3009 signature and the USDC transfer must actually settle on Base before a premium tool is served. Configured viaX402_FACILITATOR_URL/X402_FACILITATOR_API_KEY(defaults to the public reference facilitator at https://x402.org if unset).
Fail-open by default: if the facilitator is unreachable (network error, timeout, 5xx),
the gate falls back to structural-only validation rather than blocking all premium traffic —
set X402_STRICT_FACILITATOR=true to fail closed instead once a funded, reliable production
facilitator is wired up. Not live-tested: this sandbox has no outbound network access to
x402.org, so facilitatorVerifyAndSettle has only been verified for syntax/type-correctness,
not against a real signed payment. Test against a real facilitator before relying on this for
revenue protection in production.
/api/hiro proxies only whitelisted Hiro API paths. All other paths return 403.
Allowed prefixes: /v2/info, /v2/accounts/, /v2/fees/, /v2/contracts/call-read/,
/extended/v1/address/, /extended/v1/tx/, /extended/v1/block/, /metadata/v1/.
STACKS_PRIVATE_KEY and EVM_PRIVATE_KEY in .env are for deployment scripts only.
They must NEVER be loaded by Next.js at runtime (NEXT_PUBLIC_ prefix is forbidden for these keys).
x402 payment verification: the current implementation trusts the presence of the X-Payment header.
Production deployments should verify the payment proof on-chain via the x402 settlement network.