Skip to content

critical: signature challenge is not domain-bound — raw-nonce and SEP-0043 fallback accept cross-service signature replay #118

Description

@EmeditWeb

Problem

AuthService.verifySignature() (src/modules/auth/auth.service.ts, lines 112–132) accepts TWO different message formats:

  1. Raw Ed25519 over Buffer.from(dto.nonce) — the nonce alone, hex bytes.
  2. SEP-0043 fallback: 'Stellar Signing Key: ' + dto.nonce — string concatenation, no domain, no URI, no timestamp.

Neither challenge binds the signature to StepFi. A signature over a raw 64-char hex nonce is trivially obtainable in other contexts (any dApp that asks a user to sign arbitrary data, phishing prompts, other services using the identical raw scheme). An attacker who captures one (nonce, signature) pair valid under either format can authenticate as the victim on StepFi as long as they can insert the same nonce into the nonces table — which they control via generateNonce(): generate a nonce, harvest a matching signature elsewhere, then self-issue the challenge here. The server picks which format to try, so the weakest accepted format defines the security floor.

SEP-0043 exists precisely to fix this: its envelope includes domain, URI, version, issued-at, and expiration. The current implementation uses none of that structure.

Ground Rules

  1. Read context/architecture-context.md, context/code-standards.md, context/progress-tracker.md in full
  2. Read the full auth flow: generateNonce, verifySignature, generateTokens (auth.service.ts)
  3. Preserve mobile-client compatibility OR provide an explicit, documented migration window — state your choice in the PR

What To Build

  1. Define a canonical StepFi challenge envelope containing: domain (API origin), wallet, nonce, issued-at, expires-at — signed as UTF-8 text.
  2. Implement strict SEP-0043 verification for browser wallets (validate domain == our host, URI, not-expired).
  3. For native clients, adopt the canonical envelope and deprecate the raw-hex format behind a config flag with a sunset date.
  4. Bind nonce rows to their challenge content: store a hash of the exact expected message alongside the nonce and verify the signature against the stored message only — never against client-supplied alternatives.
  5. Tests: legacy-format signatures fail once flag off; SEP-0043 happy path passes; wrong-domain SEP-0043 rejected; expired envelope rejected; replay across environments impossible.

Files To Touch

  • src/modules/auth/auth.service.ts
  • src/modules/auth/dto/verify-request.dto.ts
  • src/config/env.ts / config plumbing for the flag
  • tests
  • relevant docs/progress tracker

Acceptance Criteria

  • Every accepted signature provably signs a StepFi-bound challenge
  • Nonce row stores and enforces the exact expected message
  • Migration/deprecation path documented and tested
  • Suite green

Mandatory Checks Before Opening PR

Standard checklist applies. PRs failing any check will be closed without review.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions