feat: reverse phone lookup (provider-adapter, fail-closed) - #156
Merged
Merged
Conversation
…ormalization and fail-closed handler
…sed, auth) + lookup.phone vitest
…rom hlr transport errors; add MSISDN-leak regression tests
Owner
Author
|
Verification defect fixed — MSISDN log leak via Fix ( resp, err := p.client.Do(req)
if err != nil {
// *url.Error embeds the request URL, which carries the full MSISDN
// query parameter — strip it so chained/logged errors never leak the
// phone number.
var urlErr *url.Error
if errors.As(err, &urlErr) {
return nil, fmt.Errorf("hlr http call: %w", urlErr.Err)
}
return nil, fmt.Errorf("hlr http call: %w", err)
}Audited the other error paths in the provider: Regression tests added (both pass):
Re-run results: Pushed files verified byte-identical to the locally tested copies (post-push branch tarball diff). |
munisp
added a commit
that referenced
this pull request
Sep 14, 2026
…gration) (#160) - WP1 (#154): entitySearchRouter import + appRouter registration - WP2 (#158): monitoringRouter import + appRouter registration - WP3 (#155): subjectPortalRouter + computeDataCompleteness imports; subjectPortal registration; removed routers.ts-local getFallbackSuggestion (now shared in server/dataCompleteness.ts); getDataCompleteness delegates to computeDataCompleteness; consentPurposeEnum gains consumer_self_check; subjectAccessTokens/subjectDisputes pgTable declarations (matches drizzle/0023_subject_portal.sql) - WP4 (#157): shareableReportsRouter + selfServiceBillingRouter imports + registrations; reportShareLinks/planSignups pgTable declarations (matches drizzle/0024_share_links_and_plan_signups.sql) - WP5 (#156): lookup.phone procedure (gatewayFetch /v1/phone/:number, validated input) Co-authored-by: bis-integration <integration@bis.local>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
WP5 — reverse phone lookup (Intelius reverse-phone analog), provider-agnostic and fail-closed.
Go gateway (
services/gateway/):phone_lookup.go(new):PhoneProviderinterface (Lookup(ctx, msisdn) (*PhoneRecord, error));PhoneRecord{number, e164, carrier, lineType(mobile|landline|voip|unknown), subscriberName, country, source, checkedAt}.PHONE_LOOKUP_PROVIDERS(comma-separated). Implemented provider:hlr— generic HLR-lookup HTTP API (HLR_API_URL,HLR_API_KEY,HLR_TIMEOUT_MS, default 8s);GET {base}/lookup?msisdn={e164}with Bearer + X-API-Key; strict JSON decoding (DisallowUnknownFields); per-provider consecutive-failure circuit breaker (no cockatiel equivalent exists in the Go service — threshold 3, 30s cooldown, single half-open probe).503 phone lookup not configured(PHONE_LOOKUP_NOT_CONFIGURED). No stub/deny providers, no synthetic records. All-providers-failed →503 PHONE_PROVIDER_UNAVAILABLE.GET /v1/phone/{number}registered inmain.gonewRouterwith the sameprotected()middleware chain as/v1/nin(cors + logging + authMiddleware).go.mod— custom strict normalizer): accepts+234…,234…(13 digits),0-trunk national, bare 10-digit NSN, punctuation()-.; NG numbering plan enforced (mobile: 10-digit NSN starting 7/8/9; landline: 8–9 digit NSN starting 1/2). Invalid → 400. Non-NG numbers rejected unlessPHONE_LOOKUP_ALLOW_INTERNATIONAL=true(then strict ITU E.164 shape).+2348031234567→+2348*******67); full numbers never logged; no secrets logged. Redis cache 6h, outbox eventbis.gateway.phone_lookup(masked).Node BFF (
server/routers.ts): newphoneprocedure inlookupRoutermirroringcacexactly — see "NOT PUSHED" below.Why
Closes the Intelius-style reverse-phone gap: given an MSISDN, return carrier/line-type/subscriber identity from authoritative HLR sources, never synthesized.
How tested (real output)
Go (
cd services/gateway && go test -v -run 'Phone|NormalizePhone|MaskMSISDN' .):Full suite:
go test ./...→ all packagesok(bis/gateway 0.106s, dapr, insider, kafka, opensearch, permify, redis, retry, verify).go vet .clean;gofmt -lclean for new/changed files.Node (
pnpm vitest run server/lookup-phone.test.ts):Covers: proxy path
/v1/phone/...+X-BIS-Keyheader, URL-encoding, zod rejection (BAD_REQUEST, gateway untouched), UNAUTHORIZED for anonymous.pnpm check(tsc --noEmit) clean with the routers.ts edit applied locally.server/routers.ts(374KB, too large for MCP push; integration commit needed)main.go(54KB) WAS pushed successfully — the route registration is on this branch (verified: branch-vs-main diff = exactly+ mux.HandleFunc("/v1/phone/", protected(handlePhoneLookup))after the/v1/cac/line). The only missing piece is this block inserver/routers.tslookupRouter, immediately after thecacprocedure (anchor below):The companion test
server/lookup-phone.test.tsIS on this branch and passes against the edited routers.ts.Risks
hlrwire contract (GET {base}/lookup?msisdn=, fieldsmsisdn/e164/carrier/line_type/subscriber_name/country) is generic; a concrete vendor may need a mapping tweak — strict decoding will fail closed (fall through / 503), never mis-decode silently.phoneChainis built once at gateway startup; env changes require restart (consistent with other gateway providers)./v1/phone/(mirrors/v1/cac/;/v1/nin|bvnadd Permify). Follow-up if policy requiresread:phone.Ops follow-up
Live vendor credentials: set
PHONE_LOOKUP_PROVIDERS=hlr,HLR_API_URL,HLR_API_KEY(and optionallyHLR_TIMEOUT_MS,PHONE_LOOKUP_ALLOW_INTERNATIONAL=true). Until then the endpoint fail-closes with 503.