Skip to content

fix: require shared secret for withdraw endpoint (#29) - #30

Open
erhnysr wants to merge 1 commit into
circlefin:masterfrom
erhnysr:fix/withdraw-auth
Open

fix: require shared secret for withdraw endpoint (#29)#30
erhnysr wants to merge 1 commit into
circlefin:masterfrom
erhnysr:fix/withdraw-auth

Conversation

@erhnysr

@erhnysr erhnysr commented Jul 25, 2026

Copy link
Copy Markdown

Problem

POST /api/gateway/withdraw has no authentication. It moves the seller's Gateway balance using SELLER_PRIVATE_KEY and accepts an arbitrary destinationAddress, so anyone who can reach the endpoint can drain the seller's funds to their own wallet:

curl -X POST https://<host>/api/gateway/withdraw \
  -H 'Content-Type: application/json' \
  -d '{"amount":"100","destinationChain":"arcTestnet","destinationAddress":"0xAttacker..."}'

Fix

Adds an optional shared-secret guard in app/api/gateway/withdraw/route.ts, right after the SELLER_PRIVATE_KEY check. When WITHDRAW_SECRET is set, every request must present a matching x-withdraw-secret header or it is rejected with 401 Unauthorized:

const withdrawSecret = process.env.WITHDRAW_SECRET;
if (withdrawSecret) {
  const provided = req.headers.get("x-withdraw-secret");
  if (provided !== withdrawSecret) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
}

The guard is opt-in so it does not break the existing demo flow when the secret is unset — but the default is now documented as unsafe for production, giving operators a clear, secure path.

Why not wire the secret through the frontend?

The only caller is components/dashboard/withdraw-dialog.tsx, a client component running in the browser. Injecting the secret there would require NEXT_PUBLIC_, which ships it in the client bundle and defeats the purpose. This guard is therefore intended for server-to-server or env-gated use; the README recommends calling the endpoint from a trusted server-side context for hosted deployments. There is no existing session/auth middleware in the repo to reuse.

Changes

  • app/api/gateway/withdraw/route.ts — optional x-withdraw-secret guard
  • .env.exampleWITHDRAW_SECRET= with a warning that leaving it unset exposes the endpoint
  • README.md — "Protecting the withdraw endpoint" note under Security & Usage Model

Testing

npm run build passes (route compiles and builds; the endpoint is server-rendered on demand).

Closes #29

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Security: Unauthenticated withdrawal endpoint can spend the server-controlled seller wallet

1 participant