Skip to content

feat(#535): Guardian-Based Social Recovery for Account Access - #574

Open
CHKM001 wants to merge 2 commits into
Neurowealth:mainfrom
CHKM001:feat/535-guardian-social-recovery
Open

CHKM001 wants to merge 2 commits into
Neurowealth:mainfrom
CHKM001:feat/535-guardian-social-recovery

Conversation

@CHKM001

@CHKM001 CHKM001 commented Sep 30, 2026

Copy link
Copy Markdown
Contributor

PR #535 Guardian-Based Social Recovery for Account Access

Summary

Adds time-delayed social recovery for a locked-out account owner. A user nominates trusted guardians (other platform users or external email/phone contacts) who must explicitly accept. Later, a claimant who has lost their authentication method can reopen the account only when a quorum of those guardians independently approves and a mandatory cooling-off delay elapses uncontested. No single party — including the platform — can unilaterally take over an account.

This recovers account access (the owner's sessions and ability to authenticate), not the custodial wallet's key material, which the platform already holds and which is a separate problem (src/keys/registry.ts). Friction is the point: the design optimises for making unauthorized recovery hard, not for making legitimate recovery frictionless.

What's Included

Data Model (prisma/schema.prisma + migration)

  • RecoveryGuardian — a nominated guardian (platform user or external email/phone), with explicit-accept status and a SHA-256 invite-token digest.
  • RecoveryPolicy — one per account: requiredApprovals, recoveryDelayHours, maxGuardians.
  • RecoveryRequest — a recovery attempt; carries the executeAfter deadline stamped once at quorum.
  • RecoveryApproval — one row per guardian decision; unique per (requestId, guardianId).
  • Migration 20260930140000_add_guardian_recovery (+ rollback.sql) encodes the invariants Prisma cannot express: a CHECK that quorum is >= 2, a CHECK that the delay is >= 24h, and a partial unique index allowing at most one live recovery request per account.

Service (src/guardians/service.ts, src/guardians/notifications.ts)

  • Guardian nomination + explicit-acceptance flow (no silent enrollment).
  • Recovery initiation, independent per-guardian approval, quorum detection, mandatory-delay stamping, and unilateral owner cancellation.
  • Multi-channel alerting: socket, registered email, and registered WhatsApp.

Routes (src/routes/recovery.ts)

Method Path Auth
GET/PUT /api/v1/recovery/policy owner
GET/POST /api/v1/recovery/guardians owner
DELETE /api/v1/recovery/guardians/{guardianId} owner
POST /api/v1/recovery/guardians/{guardianId}/accept platform guardian
POST /api/v1/recovery/invitations/respond external token
POST /api/v1/recovery/initiate none (public)
GET /api/v1/recovery/requests/{requestId} owner
POST /api/v1/recovery/requests/{requestId}/cancel owner
GET /api/v1/recovery/guardian/requests platform guardian
POST /api/v1/recovery/guardian/requests/{id}/decide platform guardian
POST /api/v1/recovery/guardian/decide external token

Execution (src/jobs/guardianRecoverySweep.ts)

  • A scheduled sweep is the only caller of executeRecovery — there is deliberately no HTTP execute endpoint (a caller would revoke its own session mid-request).
  • Two passes per tick: expire aged-out requests, then execute requests whose executeAfter has passed (batch-capped).
  • On success: revokes every live session with reason account_recovery. No password reset, no wallet-key touch — the owner recovers by signing in again.

Wiring

  • Router mounted in src/index.ts; sweep scheduled on boot and cleared on graceful shutdown.
  • Config + a dedicated recoveryRateLimiter (src/config/env.ts, src/middleware/rateLimiter.ts).
  • Socket-only recovery events (src/events/types.ts) and payload allowlists (src/utils/api-formatters.ts).
  • RevocationReason extended with account_recovery (src/services/refresh-token.service.ts).

Docs

  • docs/ACCOUNT_RECOVERY.md — full threat model, controls table, and stated limits.
  • docs/openapi.yaml — Account Recovery tag, schemas, and all eleven endpoints.

Security & Threat Model

  • Quorum is never 1-of-N — enforced at three layers (Zod, service, DB CHECK).
  • Mandatory delay (24–168h, default 48h) stamped once at quorum from the policy in force then, and never recomputed — editing or deleting the policy mid-flight cannot pull a live deadline forward.
  • Loud, multi-channel alerting fires the moment a request is initiated, to the account owner's own registered contacts (the one channel a claimant does not control) as well as every guardian. The owner alert is unconditional and emitted before any guardian notification.
  • Cancellation is deliberately easier than approval — the owner can stop a pending request unilaterally, with no guardian consensus, at any point before execution.
  • Anti-enumeration — POST /recovery/initiate is unauthenticated and returns an identical 202 for every valid body (account exists or not, guardians or not, live request or not). A partial unique index, not a racy read-then-write, guarantees one live request per account; a P2002 maps to the same generic response.
  • Token hygiene — invite and per-request decision tokens are returned once and stored only as SHA-256 digests; each decision token is bound to its request.
  • Every recovery event (initiate / approve / cancel / complete) is audit-logged via the append-only chain.

Known Limitations (documented, not bugs)

  • Account access only — does not recover wallet key material or undo on-chain transactions.
  • Primary accounts only (v1); sub-account requests are refused.
  • Fails safe: if too few guardians have accepted, recovery cannot complete.
  • Not instantaneous — the delay is the window in which a real owner notices and cancels.
  • Social engineering of guardians is the residual risk of any social-recovery scheme, mitigated (not eliminated) by the delay + alerting.

Testing

  • 69 unit tests pass across 5 suites: guardians/service, guardians/notifications, guardians/structural (asserts the migration's CHECK constraints and partial index survive), jobs/guardianRecoverySweep, and routes/recovery.
  • Full-project tsc --noEmit introduces no new type errors from this feature.

Base & Scope Notes

Closes #535

Adds time-delayed social recovery for a locked-out account owner. A user
nominates trusted guardians (platform users or external contacts) who must
explicitly accept; a claimant can reopen the account only when a quorum of
guardians independently approves AND a mandatory cooling-off delay elapses
uncontested. No single party — including the platform — can unilaterally
take over an account.

Structural defences, each enforced in more than one layer:
- Quorum is never 1-of-N: Zod schema, service check, and a DB CHECK (>= 2).
- Mandatory 24-168h delay (default 48h), stamped ONCE onto the request at
  quorum from the policy in force then, so editing the policy mid-flight
  cannot pull a live deadline forward.
- Loud multi-channel alerting on initiation to the account owner's own
  registered contacts plus every guardian; socket-only events so a recovery
  alert can never be suppressed by a misconfigured webhook.
- The owner can cancel unilaterally at any point before execution.
- Execution has no HTTP endpoint: a scheduled sweep is the only caller,
  revoking every live session with reason `account_recovery`.

Anti-enumeration: the public, unauthenticated POST /recovery/initiate
returns an identical 202 for every valid body and relies on a partial unique
index (one live request per account) rather than a racy read-then-write.

Includes: RecoveryGuardian/RecoveryPolicy/RecoveryRequest/RecoveryApproval
models + migration with CHECK constraints and partial unique index (and
rollback); service, routes, notifications, and the sweep job; env config and
a dedicated recovery rate limiter; docs/ACCOUNT_RECOVERY.md threat model and
docs/openapi.yaml; 69 unit tests across service, notifications, routes,
sweep, and migration-structure suites.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Sep 30, 2026

Copy link
Copy Markdown

@CHKM001 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

@Abidoyesimze

Copy link
Copy Markdown
Contributor

Fix conflict

…ial-recovery

# Conflicts:
#	prisma/schema.prisma
#	src/events/types.ts
#	src/index.ts
#	src/services/refresh-token.service.ts
@CHKM001

CHKM001 commented Oct 3, 2026

Copy link
Copy Markdown
Contributor Author

Pls review!

@Abidoyesimze

Copy link
Copy Markdown
Contributor

Fix conflict

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.

Guardian-Based Social Recovery for Account Access

2 participants