Skip to content

Fix: anchor api auth idor - #289

Open
Bran18 wants to merge 2 commits into
Neko-Protocol:devfrom
Bran18:fix/anchor-api-auth-idor
Open

Fix: anchor api auth idor#289
Bran18 wants to merge 2 commits into
Neko-Protocol:devfrom
Bran18:fix/anchor-api-auth-idor

Conversation

@Bran18

@Bran18 Bran18 commented Jul 30, 2026

Copy link
Copy Markdown

Closes #280

Closes an IDOR across the anchor and automation API surface by adding wallet-signed authentication, server-side session cookies, and per-resource ownership checks before any upstream anchor call.

Previously, all nine /api/anchor/** routes were unauthenticated BFF proxies: Zod validated input shape, but customerId, email, transactionId, and publicKey were caller-supplied with no proof of ownership. That exposed regulated PII (CLABE numbers, KYC records) and let anonymous traffic use our anchor credentials.

This PR implements the full auth model from the security plan:

Authentication — Stellar signed-challenge flow (POST /api/auth/challenge → sign → POST /api/auth/verify) with single-use, TTL-bounded nonces and an HttpOnly neko_session cookie (secure, sameSite=strict).
Authorization — Upstash-backed (provider, customerId) → publicKey bindings (written at customer creation) and transaction bindings for on/off-ramp GETs. Unknown bindings fail closed with 403.
Route hardening — All 9 anchor routes and 6 automation routes call requireSession before upstream work. Off-ramp validates fiatAccountId ownership; assets?wallet= is restricted to the session wallet; email-based customer lookup is removed.
Defence in depth — Middleware returns 401 for /api/anchor/* and /api/automation/* without a session cookie. Per-IP and per-session rate limits return 429 before anchor quota is consumed. Shared handleAnchorError removes raw exception/upstream text from responses.
Client — rampApi.ts sends credentials, retries on 401 via challenge/verify, and surfaces an “authenticate your wallet” state in the ramp UI.
KYC uploads — Size cap, MIME allowlist, and document count limits on multipart doc_* fields.
Admin UI gating (neko-stellar-address cookie for /dashboard/admin) is unchanged and remains a separate, UX-only concern.

Breaking changes:

Existing customerId values in localStorage without a server binding will receive 403 until the user re-onboards via authenticated POST /customers.
Client payloads no longer accept identity fields (publicKey, stellarAddress); the server derives them from the session.
GET /customers?email= is removed; lookup is customerId only, with ownership enforced.

New environment variables that mati or the team will need to add:

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=
AUTH_SESSION_SECRET=
AUTH_ENFORCEMENT=true

@vercel

vercel Bot commented Jul 30, 2026

Copy link
Copy Markdown

@Bran18 is attempting to deploy a commit to the Oppia Software Labs Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 30, 2026

Copy link
Copy Markdown

@Bran18 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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.

fix(web-app): every /api/anchor/** route is unauthenticated — KYC and bank-account data exposed by customer ID

1 participant