Automated callback-of-record verification desk for accounts payable.
Enforces out-of-band telephone verification before supplier bank details are updated in vendor master systems.
Vendor impersonation fraud (the dominant variant of Business Email Compromise) targets accounts-payable workflows. An attacker emails finance requesting payment remittance to a newly opened bank account. Because the correspondence mimics legitimate supplier inquiries, payments are issued to mule accounts during scheduled payment runs.
Every standard internal control framework (SOX §404, ISA 315, cyber-insurance underwriting guidelines) prescribes the identical countermeasure:
Before amending supplier bank details, contact the supplier using a telephone number already held on file—obtained independently of the change request—and verify verbal authorization.
In practice, manual execution of this control routinely fails:
| Failure Mode | What Happens In Practice | Software Countermeasure in knowngood |
|---|---|---|
| Number Substitution (F1) | The clerk dials the telephone number listed in the email signature. The fraudster answers and confirms their own fraudulent account. | R1 Provenance: Refuses any number appearing in the change request artifact, even if that number is already recorded in the vendor master. |
| Voicemail as Consent (F2) | Under payment-run deadlines, an unanswered desk phone or voicemail is filed as "verified." | R7 Precedence: Voicemail, IVR mazes, and uncompleted calls resolve strictly to HOLD. |
| Leading the Witness (F3) | The clerk reads the account digits aloud: "Can you confirm account ending 4271?" The fraudster says "yes". Nothing was verified. | R3 Disclosure Budget: Pre-dispatch linting blocks any task text containing requested account numbers or substrings. Recipient must state the digits. |
| Epistemic Hallucination (F4) | A voice model structured JSON output indicates "change_authorized": true, but transcript turns show only the bot spoke the digits. |
R5 Evidence Grounding: Gating requires confirmed digits to appear in transcript turns spoken strictly by speaker: "user". |
The core verification pipeline operates on a hard boundary: all business rules (R1–R9) are pure deterministic functions isolated from network, filesystem, clock, and environment side effects.
Figure 01: Trust boundaries and external system interfaces. Untrusted change requests are parsed strictly for data extraction and candidate contamination matching.
- Inbound Intake Channel: Hostile. Request artifacts are parsed exclusively for exclusion matching (phone number contamination) and requested payee digits. No artifact text is ever interpolated into agent prompts.
- Vendor Master (ERP): Trusted for historical telephone records, provided each candidate carries verifiable provenance metadata (
firstSeenAtandsource). - Telephony Provider (CALL-E): External voice agent platform. Telephony execution returns structured outputs and raw turn-by-turn transcripts;
knowngoodverifies these turns before reaching any disposition.
The verification desk enforces nine deterministic rules across the request lifecycle:
Figure 04: Algorithmic flow of rules R1 through R9. Pre-dispatch gates abort before network execution if safety constraints fail.
| Rule | Invariant | Technical Enforcement |
|---|---|---|
| R1 | Provenance | Dials strictly a number held in the vendor master before the request arrived and absent from the request artifact. If all candidates are contaminated, no call is placed (HOLD). |
| R2 | Freshness | Disqualifies any vendor phone record added within 30 days of the request arriving, preventing recently planted numbers from laundering fraud. |
| R3 | Disclosure Budget | Scans compiled task instructions prior to dispatch. Rejects prompts containing full account numbers, contiguous ≥6-digit substrings, IBAN patterns, or long digit runs. |
| R4 | Result Schema | Enforces unknown as an explicit enum value for all boolean questions, preventing forced-choice hallucinations by the voice model. |
| R5 | Evidence Grounding | Requires confirming digits to originate in a transcript turn where speaker === "user". Rejects claims where only the bot recited the digits. |
| R6 | Match | Compares recipient-spoken digits against the requested account number (match → RELEASE_RECOMMENDED, mismatch → BLOCK). |
| R7 | Fail-Closed Precedence | Evaluates eight operational failure conditions (timeout, IVR deflection, voicemail, ungrounded answer) to HOLD prior to evaluating match status. |
| R8 | Idempotency | Derives call keys from `sha256(requestId |
| R9 | Audit Trail | Appends verification outcomes to an immutable, SHA-256 hash-chained JSONL ledger citing transcript turn indices and masking phone numbers. |
The operator review console provides accounts-payable clerks and controllers with a structured four-pane review interface:
When a change request nominates a phone number that is already in the company vendor master (e.g. an attacker citing an invoice header), knowngood identifies the contamination, refuses the nominated number, and selects an independent number of record (e.g. from a signed contract).
Figure 02: The Gate pane lists candidate numbers, highlighting refused records and exact disqualification rationales.
The system never auto-releases payments. Upon positive verification, it displays the exact transcript turn spoken by the recipient and keeps the release control disabled until an authorized named approver takes action.
Figure 03: The Verdict pane shows the verbatim recipient quote with speaker attribution and disabled release action.
- Node.js >= 22.0.0
- npm or pnpm
git clone https://github.com/edish-github/Knowngood.git
cd Knowngood/app
npm install
npm test # 133 unit and end-to-end tests. Runs 100% offline with no API keys.
npm run dev # Starts operator review console on http://localhost:3000Open http://localhost:3000/request/cr_0412 to inspect an adversarial scenario where an attacker attempted telephone substitution.
To prevent unintended telephone calls during development, CI, or demonstrations, knowngood executes against recorded fixture payloads by default. The replay transport simulates realistic event latencies and returns identical response schemas as production telephony.
Placing real telephone calls requires two independent, server-side conditions:
export CALLE_API_KEY="sk_live_..."
export CALLE_LIVE=1
export ALLOWED_DESTINATIONS="+919800000001,+919800000002"CALLE_LIVE=1andCALLE_API_KEYmust both be present.- The resolved dial target must match an entry in
ALLOWED_DESTINATIONS(E.164 format). If a number is not allowlisted, the transport raisesDestinationNotAllowedand aborts before touching the telephony network.
# Run verification in replay mode
node --import tsx/esm scripts/verify-once.mts cr_0412
# Run verification in live mode redirecting dial target to a verified test handset
node --import tsx/esm scripts/verify-once.mts cr_0412 --dial +919876543210- Zero Accidental Dials: If candidate phone numbers fail provenance checks, the pipeline halts immediately without placing a call.
- Zero Digit Leakage: Pre-dispatch inspection ensures sensitive account digits never appear in prompt text delivered to telephony agents.
- Bot Attribution Defense: Fabricated or leading agent confirmations cannot produce a
RELEASE_RECOMMENDEDverdict.
- Pre-Compromised Vendor Master: If an attacker breached the vendor master months prior and altered the primary contact number, out-of-band phone callbacks to that number will reach the attacker. This residual risk (Threat Model A7) requires multi-channel controls (e.g. physical mail confirmation, ERP access anomaly detection) and cannot be solved by voice telephony alone.
- Authority Verification: A telephone callback proves that a person answering a previously recorded number stated the account digits. It does not prove that the speaker possesses corporate authority to amend banking arrangements.
knowngood/
├── app/
│ ├── src/
│ │ ├── core/ # Pure deterministic rule engine (R1–R9, phone masking, ledger math)
│ │ ├── calle/ # Telephony adapters (Live SDK, Replay transport, Task compiler)
│ │ ├── server/ # In-memory registry, evidence pack builder, orchestrator
│ │ └── app/ # Next.js App Router (Operator console, SSE event streaming, API)
│ ├── fixtures/ # Synthetic vendor master, adversarial change dockets & transcripts
│ ├── tests/ # 133 offline tests (Core purity, provenance, grounding, decision matrix)
│ └── scripts/ # CLI test harnesses (verify-once, smoke-call)
├── docs/ # Complete architectural documentation suite
│ ├── diagrams/ # C4, sequence, state, ER, and rule diagrams (src, svg, png)
│ ├── architecture.md # Full architectural specification and wire contracts
│ ├── product-concept.md # Detailed threat landscape and business case
│ ├── threat-model.md # Attacker capabilities (A1–A7) and defensive mappings
│ ├── disclosure-budget.md# Task text disclosure limits and linting regexes
│ └── what-a-call-can-establish.md # Epistemic limits of phone verification
└── research/ # Gitignored research notes, runbooks, and build logs
The test suite validates the entire rule engine without network or credentials:
cd app
npm test Test Files 8 passed (8)
Tests 133 passed (133)
Duration 1.21s
core-purity.test.ts: Mechanically inspectssrc/core/to ensure zero imports offs,net,http,process.env, or external I/O libraries.provenance.test.ts: Validates candidate rejection, source hierarchy ranking, and fail-closed behaviors.disclosure.test.ts: Tests account number detection, substring matches, IBAN shapes, and sort codes.grounding.test.ts: Verifies transcript turn attribution, word-to-digit parsing, and bot-speech rejection.shape-drift.test.ts: Pins casing differences between REST wire payloads (snake_case) and SDK models (camelCase).e2e-replay.test.ts: Tests all eight canonical change request fixtures end to end.
MIT License.