Inspiration

Someone once called my grandparents and told them they had to send money to an address, and that it was for me. They were scared. A call like that needs one thing to work: that the person acts alone, before anyone they trust can check.

I audit payment systems at a bank. Second Key adapts two controls from that work: a second person's check and a call-back to a number already known. In France, manipulation fraud reached €516 million in 2025, up 34% in a year (Banque de France, OSMP annual report 2025, printed page 24).

The parent waits while the second key reviews the request

The interactive browser simulation runs both roles.

What it does

  1. A parent and trusted family member pair once, together, by QR code. Both screens show the same three check words.
  2. After ending a suspicious call, the parent enters the amount and payee, chooses the payee category, and answers four yes/no questions. They send the request by text message or copy its link.
  3. The second key sees the request, the call briefing it resembles, and questions to ask. Every request needs the family call, even when no known script matches.
  4. The second key calls the number saved at pairing. If they agree, their screen displays a six-digit code to read aloud. The parent enters it to record the check. If they refuse, each person records the refusal on their own phone. If the trusted person cannot answer, the instruction is to wait and not send money.

The code is HMAC-SHA-256 over the request fields, keyed with the shared secret. Three wrong checks lock a request, including when checks arrive from multiple tabs. The app does not connect to a bank or hold a transfer; the family still checks the actual recipient's bank details before deciding to pay.

How the release code travels

The request travels by message; the code is intended to be read on the call initiated by the second key. Both phones compute the code, but only the second key's interface displays it.

Who it's for

The adult child or grandchild of an older person who pays by bank app and wants a second opinion before paying someone new. It works alongside any bank when the family agrees to use the procedure.

Evidence

  • On 22 authored scenarios adapted from public scam warnings, the fixed table selected a compatible primary call briefing for 21. Eight of ten constructed ordinary payments raised no briefing. These are development examples: they represent 15 distinct questionnaire inputs. The report publishes every source, the scoring rubric, duplicate-input limits and source fingerprints.
  • Automated validation passed 35 unit tests, 25 Chromium browser tests including a mobile axe scan, and 24 procedure/security tests in each of Chromium, Firefox and WebKit. These repeated engine runs cover pairing, approval, refusal, tampered requests, receipt persistence, the copy-link route, storage failure, malicious names, and concurrent or stale tabs. A separate test checks the call-attestation barrier for all 80 possible questionnaire inputs.
  • A privacy regression checks that the tested journey makes no HTTP requests after initial loading, including an extended-character payee name. The production Content Security Policy blocks network APIs. The site has no application backend, analytics or runtime model service; messages still use the family's messaging provider.

Results on the development examples

A relevant briefing is guidance, not a precise identification of every scam family. The missed investment scam has the same recorded answers as several ordinary payments. Every request still requires the call.

How I built it

TypeScript and Vite, Web Crypto, qrcode, Atkinson Hyperlegible, Vitest, Playwright and axe-core. GitHub Actions checks the build and browser regressions before GitHub Pages deployment. Runtime decisions are deterministic; AI assisted the planning, implementation and audit.

Challenges I ran into

  • Changed requests. A browser test showed that the second key accepted a changed copy of an already-seen request. The app now rejects that copy.
  • A second browser tab. The initial tests passed, but an adversarial check found that a stale tab could approve a locked request. Decisions now reread state under a shared browser lock, and regressions cover refusal during verification and simultaneous attempts.
  • Evidence that matches the claim. Committing rules before examples supports the history, but does not prove independent validation. I kept the original report, clarified the rubric, exposed repeated inputs, and separated software verification from claims about real-world effectiveness.

What I learned

Turn an attack into an acceptance criterion, then test the uncomfortable state: another tab, a pending calculation, a refused request. And report what a measurement actually measures. “21 of 22” describes relevant guidance on authored examples; it does not tell me how many scams a family would avoid.

What's next

A bank-side integration could hold the transfer itself. A separately authored evaluation and observation of families using real phones would test the assumptions that automated browser checks cannot establish.

Built With

Share this project:

Updates

Submission history