mirror of
https://github.com/hanzoai/idv.git
synced 2026-10-30 18:33:39 +00:00
LexisNexis reported "approved" out of InitiateVerification for any non-4xx reply and again from CheckStatus with no request at all; Intellicheck issued its request and then ignored the response, including its status code, so a 500 and a rejected document both read as approved. None of the three read what the provider decided. A caller gating on any of them would admit an unverified subject. An integration that does not parse the response now refuses instead of answering, and the refusal names the provider so an operator can tell it apart from a network fault. LexisNexis loses the query builder nothing called; implementing it means parsing the FlexID verdict and mapping it to a status. Two properties are now table-driven over every constructible provider, so one added later inherits both: a freshly created verification is never already approved, and an unread status read refuses. The tests point at a server that ANSWERS — an unreachable base URL would pass for the wrong reason, leaving the synthesized approval downstream of a return no test reaches. scripts/mutants.py drives cloud's strict-scored engine over both properties and the webhook guards: 6 rows, 6 KILLED on a green baseline. LLM.md now states per provider whether it can read a verdict and whether it can prove a webhook, and carries the retirement runbook — sequenced on the route, since an orphaned Ingress publishes idv.hanzo.ai today despite its CR disabling ingress. Co-authored-by: Hanzo Dev <[email protected]>
57 lines
2.9 KiB
Python
57 lines
2.9 KiB
Python
# Mutation table for hanzoai/idv, scored by cloud's scripts/mutate.py (strict: a
|
|
# mutant that does not compile, whose anchor drifted, or whose -run matched nothing
|
|
# is a hard FAILURE, never a kill).
|
|
#
|
|
# MUTATE_ROOT=<idv tree> MUTATE_TABLE=<this file> <cloud>/scripts/mutate.py [filter]
|
|
#
|
|
# Two properties, both about not manufacturing a verdict:
|
|
# a webhook is read only after it is proven to come from the provider, and
|
|
# a provider that does not parse the response reports no decision.
|
|
|
|
W = "provider/webhook.go"
|
|
O = "provider/onfido.go"
|
|
L = "provider/lexisnexis.go"
|
|
I = "provider/intellicheck.go"
|
|
P = "./provider/"
|
|
|
|
MUTANTS = [
|
|
# An empty secret makes the HMAC computable by anyone, so "verifying" against it
|
|
# admits any forged verdict. Skipping the refusal is the plausible bug.
|
|
("idv: an empty webhook secret is allowed to verify", [
|
|
(W, 'if secret == "" || signature == "" {',
|
|
'if signature == "" {')],
|
|
"TestProviderWebhookAuth", P),
|
|
|
|
# The comparison itself. If it always matches, every tampered body is accepted.
|
|
# (Comparing against a constant false leaves `got` unused, which does not
|
|
# compile — and a NO-COMPILE is not a kill — so the digest is compared with
|
|
# itself instead: same always-matches effect, still typechecks.)
|
|
("idv: the signature comparison always matches", [
|
|
(W, "if !hmac.Equal(got, mac.Sum(nil)) {",
|
|
"if !hmac.Equal(got, got) {")],
|
|
"TestProviderWebhookAuth", P),
|
|
|
|
# Onfido's verification, absent until now: decode without proving the sender.
|
|
("idv: onfido decodes a webhook without verifying it", [
|
|
(O, '\tif err := verifyHMAC(body, o.cfg.WebhookToken, header(headers, "X-SHA2-Signature")); err != nil {\n\t\treturn nil, err\n\t}\n',
|
|
"")],
|
|
"TestProviderWebhookAuth", P),
|
|
|
|
# The auto-approve that was live in both LexisNexis entry points and in
|
|
# Intellicheck's. Each of these restores a pass the provider never gave.
|
|
("idv: lexisnexis check synthesizes an approval", [
|
|
(L, 'return nil, fmt.Errorf("lexisnexis: no verdict is parsed — status cannot be reported")',
|
|
"return &VerificationStatusResult{Status: StatusApproved}, nil")],
|
|
"TestUnreadCheckRefuses|TestInitiateIsNeverApproved", P),
|
|
|
|
("idv: lexisnexis initiate synthesizes an approval", [
|
|
(L, 'return nil, fmt.Errorf("lexisnexis: the FlexID response carries the verdict and is not parsed — no decision can be reported")',
|
|
"return &VerificationResponse{Provider: ProviderLexisNexis, Status: StatusApproved}, nil")],
|
|
"TestUnreadCheckRefuses|TestInitiateIsNeverApproved", P),
|
|
|
|
("idv: intellicheck check synthesizes an approval", [
|
|
(I, 'return nil, fmt.Errorf("intellicheck: the results response is not parsed — no decision can be reported")',
|
|
"return &VerificationStatusResult{Status: StatusApproved}, nil")],
|
|
"TestUnreadCheckRefuses|TestInitiateIsNeverApproved", P),
|
|
]
|