Skip to content

fix(security): wire CORS config and add Ed25519 signature auth for cancel, accept, fill, and solver registration (#39 #40 #41 #42) - #166

Open
meetdarc-tech wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
meetdarc-tech:feature/issues-39-40-41-42-cors-signature-auth
Open

fix(security): wire CORS config and add Ed25519 signature auth for cancel, accept, fill, and solver registration (#39 #40 #41 #42)#166
meetdarc-tech wants to merge 1 commit into
stellar-vortex-protocol:mainfrom
meetdarc-tech:feature/issues-39-40-41-42-cors-signature-auth

Conversation

@meetdarc-tech

@meetdarc-tech meetdarc-tech commented Jul 28, 2026

Copy link
Copy Markdown

Summary

This PR resolves four issues spanning a dead CORS configuration and a class of authorization bypass vulnerabilities across intent cancellation, solver accept/fill, and solver registration.


Issue #39 — Wire CORS_ORIGIN config into the app

Problem: \CORS_ORIGIN\ was fully defined and Joi-validated in \env.validation.ts\ and mapped into \AppConfig.corsOrigin, but \�ootstrap()\ in \src/main.ts\ never called \�pp.enableCors(). The config was completely dead — the browser same-origin policy applied regardless of what was set in the environment.

Changes:

  • \src/main.ts: reads \configService.get('corsOrigin')\ and passes it directly to \�pp.enableCors({ origin })\
  • \ est/utils/create-test-app.ts: wires the same CORS call in the test factory so the e2e environment faithfully mirrors production
  • \ est/cors.e2e-spec.ts: new e2e test suite with three assertions:
    • \CORS_ORIGIN=*\ → \Access-Control-Allow-Origin: *\
    • Specific allowed origin → header reflects that exact origin on matching requests
    • Mismatched origin → attacker origin is never echoed back

Issue #40 — Add signature verification for intent cancellation

Problem: \IntentsController.cancel()\ only compared \intent.user !== dto.user\ as a plain string check against a client-supplied field. Anyone who knew a user's Stellar address could cancel their intent by simply including it in the request body — a real authorization bypass, not just hardening.

Changes:

  • \src/intents/dto/cancel-intent.dto.ts: added required \signature\ field (base64-encoded Ed25519 signature)
  • \src/intents/intents.controller.ts: after the address ownership check, calls \�erifyStellarSignature(dto.user, buildCancelMessage(id), dto.signature). Canonical message format: \cancel:\

Issue #41 — Add signature verification for solver accept/fill

Problem: Same class of bug as #40 but for solvers: \�ccept()\ and \ ill()\ trusted the raw \solver\ field in the request body with no proof the caller controlled that solver's keypair. Any client could impersonate any registered solver.

Changes:

  • \src/intents/dto/accept-intent.dto.ts: added required \signature\ field
  • \src/intents/dto/fill-intent.dto.ts: added required \signature\ field
  • \src/intents/intents.controller.ts:
    • \�ccept()\ verifies signature over canonical message \�ccept::\
    • \ ill()\ verifies signature over canonical message \ ill::\
    • Both reuse the shared \�erifyStellarSignature\ helper — no logic duplication
  • \scripts/solver-bot.ts: updated to require a \SOLVER_SECRET\ env var (Stellar secret key \S...), derive the solver address from the keypair, and sign every accept and fill request before sending

Issue #42 — Add authentication to solver registration

Problem: \SolversService.register()\ existed but had no controller endpoint — entirely unreachable. When the endpoint is added it must not allow anyone to claim an arbitrary address and bond amount without proof of key ownership.

Changes:

  • \src/solvers/dto/register-solver.dto.ts: new DTO covering all registration fields plus a required \signature\ field
  • \src/solvers/solvers.controller.ts: new \POST /api/v1/solvers/register\ endpoint — verifies Ed25519 ownership of the claimed address (canonical message:
    egister:) before delegating to \SolversService.register(). Solvers default to \isActive: false\ pending bond escrow verification (ties to Instrument the sweeper with duration and expired-count metrics #96)

Shared infrastructure

*\src/common/stellar-signature.ts* — new helper module, single source of truth for all signature logic across the codebase:

  • \�erifyStellarSignature(publicKey, message, signature): verifies a base64 Ed25519 signature using @stellar/stellar-sdk's \Keypair.verify(). Throws \UnauthorizedException\ on any failure so it propagates cleanly to HTTP 401
  • Canonical message builders: \�uildCancelMessage, \�uildAcceptMessage, \�uildFillMessage, \�uildRegisterMessage\ — used by both server handlers and client code to guarantee message format consistency

*\src/solvers/solvers.seed.ts* — seed solvers now use real Stellar keypairs (generated via \Keypair.random()) so signature verification works end-to-end in tests. Secrets and keypairs are exported as \SEED_SOLVER_SECRETS\ / \SEED_SOLVER_KEYPAIRS\ for use by tests and the solver-bot demo.


Tests

All existing e2e tests updated to use real Stellar keypairs and properly signed requests:

  • \ est/intents.e2e-spec.ts: full lifecycle, fill-below-minimum, unknown solver, and cancel tests now generate and verify real Ed25519 signatures
  • \ est/solvers.e2e-spec.ts: leaderboard and stats tests updated for real keypair addresses; new test for \POST /api/v1/solvers/register\ covering both rejected (invalid sig) and accepted (valid sig) paths
  • \ est/stats.e2e-spec.ts: create→accept→fill cycle updated with properly signed requests
  • \ est/cors.e2e-spec.ts: new — covers CORS header behaviour for wildcard, matching, and non-matching origins

Result: 27/27 e2e tests pass. \ sc --noEmit\ exits 0.
est build\ is clean.


Breaking changes

  • \POST /api/v1/intents/:id/accept\ — now requires \signature\ field in the request body
  • \POST /api/v1/intents/:id/fill\ — now requires \signature\ field in the request body
  • \POST /api/v1/intents/:id/cancel\ — now requires \signature\ field in the request body
  • \scripts/solver-bot.ts\ — now requires \SOLVER_SECRET\ env var (Stellar secret key \S...); \SOLVER_ADDRESS\ is no longer accepted

closes #39
closes #40
closes #41
closes #42

@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@meetdarc-tech 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

2 participants