ABDM Health Locker implementation validation

Audit date: 18 September 2026
Repositories: orvo-abha and orvo-phr
Scope: Static implementation and test review against the published ABDM Health Locker, PHR, HIU, HIP, consent, subscription, FHIR, and encrypted data-transfer flows. This is not a substitute for the current ABDM certification test pack or a sandbox trace captured with Orvo's production-bound credentials.

Executive summary

Orvo has the main Health Locker protocol chain in place. The platform is represented as both HIP and HIU, can set itself up as the patient's locker, receives LINK and DATA subscription events, creates PATRQT consent requests, receives encrypted FHIR data as an HIU, serves stored documents as a HIP, and creates/link care contexts for patient uploads. orvo-phr exposes setup, status, pause/resume, and self-uploaded document management.

The implementation is functionally substantial but not yet complete enough to treat “Active” as guaranteed automatic synchronization. The most important gaps are:

  1. A fallback auto-approval policy created after locker setup is not durably associated with the locker subscription, so it may not be disabled later.
  2. Re-enabling a subscription does not re-enable or recreate the auto-approval policy disabled by the pause operation.
  3. The DATA handler reuses a granted consent based only on patient, HIP, grant status, and expiry; it does not prove that purpose, HI types, care contexts, or data period cover the notification.
  4. LINK/DATA follow-up operations are process-local fire-and-forget work after the ABDM event is acknowledged. A crash can lose the work while preventing ABDM redelivery.
  5. The PHR presents a single active/paused boolean and promises automatic synchronization even when auto-approval is off, failed, expired, or unknown.
  6. The deletion lifecycle for an already-linked self-uploaded care context is not defined.

Remediation update — 18 September 2026

The first implementation pass after this audit resolved or materially mitigated the following:

The remaining external/operational work is a production malware/CDR integration, current-sandbox execution of the scenarios below, and NHA confirmation of the linked-care-context deletion rule.

Source baseline

The validation uses these published sources:

Some public pages retain older NDHM/v0.5 terminology while Orvo targets HIE-CM v3. Architectural requirements are stable, but exact payloads, callbacks, error codes, and status values must also be checked against the current NHA collection supplied during Orvo onboarding and real sandbox traces.

Canonical flow and validation matrix

Stage ABDM expectation Backend PHR Result
Entity onboarding Locker registered as HEALTH_LOCKER, HIP, and HIU with callback and launch URLs Configuration support exists; NHA-side registration cannot be proven from code Not applicable Operational evidence required
Patient setup Authenticated patient selects locker and grants subscription POST /v1/phr/health-locker/setup; system locker ID resolved server-side Setup modal exists Implemented
Subscription scope LINK + DATA, HIP scope, HI types, purpose, validity period Generic approval/edit schemas support the scope Generic subscription UI supports selections; locker setup hides actual resulting scope Partial
Auto-approval Separate, explicit patient choice with bounded policy Optional policy setup exists Explicit checkbox exists, but defaults on and does not display scope/expiry Partial
Subscription callback Correlate, persist, acknowledge, handle duplicates Request/subscription correlation and ACK logic exist Status is refreshed by API Implemented with durability risk
LINK event Request matching PATRQT consent for new contexts Implemented with care-context parsing and deduplication Consent pages can show/manage grants Implemented with durability risk
DATA event Reuse only a consent whose complete scope covers the event, otherwise request consent Existing lookup checks only HIP/status/expiry Not visible to patient Gap
HIU receive Request under granted artefact, decrypt, normalize/store FHIR, notify CM Fidelius request/receive and recovery services exist My Records renders normalized FHIR Implemented; sandbox E2E evidence required
Self upload Validate, wrap as FHIR, create/link locker-owned care context Implemented for PDF/JPEG/PNG with byte-signature check Upload/view/delete UI exists Mostly implemented
HIP onward share Validate consent scope, encrypt eligible FHIR, push to requesting HIU, notify CM HIP health-information request/push pipeline exists and uses stored documents Consent UI exists Implemented; locker-specific E2E test required
Pause/resume Stop/resume events; manage auto-approval separately and consistently Pause disables both best-effort; resume enables subscription only One switch implies one state Gap
External revoke Reconcile subscription and auto-approval changed outside Orvo Subscription callback updates local state; policy cannot be disabled without patient token Refresh can show remote state Partial
Delete Enforce ownership, storage deletion, retention/audit, and linked-context behavior Ownership and object deletion exist Delete has no confirmation or lifecycle explanation Partial
Observability Durable idempotency, retry/replay, correlation and dead-letter handling Rich logs and sync recovery exist; event branches are fire-and-forget Generic error toasts only Partial

What is correctly implemented

1. Orvo's own locker identity is resolved on the server

The PHR never supplies an arbitrary locker ID for the one-click flow. The backend resolves the configured system HIU/locker and binds both subscription setup and self-uploaded records to it. This prevents client-side locker impersonation and supports the ABDM model where one approved Health Locker identity carries HIP and HIU responsibilities.

Evidence:

2. Locker setup is correlated with asynchronous ABDM callbacks

Before calling the ABDM locker endpoint, Orvo creates a pending subscription request. The callback service stores both subscription-request and durable subscription identifiers, acknowledges events, and has a locker-specific fallback correlation by patient and HIU for the callback shape that does not echo Orvo's request ID.

Evidence:

The subscription event handler parses ABDM's grouped care-context shape. A LINK event creates a context-scoped, self-requested (PATRQT) consent and avoids known duplicate requests. A DATA event tries to use an existing grant and otherwise creates a PATRQT request. Empty or unparseable LINK events do not create blind blanket requests.

Evidence:

4. The encrypted health-information path exists in both directions

As HIU, Orvo generates and retains ephemeral key material, requests health information, accepts the data push, decrypts Fidelius payloads, and normalizes FHIR bundles. Recovery logic looks for granted consents that did not progress to a health-information request and for stalled transfers.

As HIP, Orvo accepts a health-information request, finds eligible linked documents, produces FHIR, encrypts the response, pushes it to the requesting HIU, and records transfer status.

Evidence:

5. Self-uploaded records enter the HIP document pipeline

The backend validates declared PDF/JPEG/PNG signatures, maps the document to an ABDM HI type, generates a unique care-context reference, builds/stores a FHIR bundle, associates it with the locker facility, and attempts care-context linking. Patient reads and deletes are constrained by both ABHA address and uploader identity.

Evidence:

6. Embedded ABDM failures and live statuses are normalized

ABDM can return HTTP 200 with an embedded error. Enable/disable now converts that body to a real application error. The locker considers ACTIVE, ENABLED, and GRANTED live, case-insensitively, so a live subscription is not incorrectly offered an enable transition.

Evidence:

Findings requiring changes

HL-01 — Fallback auto-approval ID is not persisted

Severity: High

The locker first records an auto-approval ID returned by setup-locker. If none is returned and the patient requested auto-approval, Orvo calls setupAutoApproval, but ignores the returned policy and does not call recordLockerAutoApprovalId again. disableLockerAutoApproval later has no handle for that policy and silently returns.

Risk: pausing the locker may leave a standing auto-approval policy active at the CM.

Required change:

  1. Capture the fallback setup response.
  2. Extract and persist its auto-approval ID on the subscription request.
  3. Return separate subscription and policy outcomes.
  4. Add a regression test covering setup fallback followed by disable.

HL-02 — Resume does not restore auto-approval

Severity: High

Pause enables two transitions: disable subscription, then best-effort disable policy. Resume only enables the subscription. The status can therefore be “Active” with auto-approval off, while the PHR states that records synchronize automatically.

Required change: make resume an explicit patient choice:

The response must expose both outcomes and must not claim automatic sync if only the subscription is active.

Severity: Critical

The DATA branch selects the newest granted, unexpired consent for the patient and HIP. It does not check:

Expiry and HIP equality are necessary but not sufficient. A consent for one diagnostic report must not authorize requesting every record from the HIP.

Required change: create one shared consentCoversSubscriptionEvent() scope evaluator used by DATA events and by the health-information request guard. If the event lacks enough scope information to prove coverage, request fresh consent instead of broadening an existing grant.

HL-04 — Event work can be lost after acknowledgement

Severity: High

The LINK/DATA handler acknowledges the ABDM event and then invokes consent/health-information work with .catch(...) without awaiting or durably enqueueing it. If the process exits after ACK and before completion, ABDM will not redeliver the event. Logs do not provide guaranteed replay.

Required change:

  1. Persist the raw event keyed by event.id before ACK.
  2. Upsert idempotently on redelivery.
  3. ACK once durable acceptance succeeds.
  4. Process from a retryable worker with attempt count, backoff, last error, and dead-letter state.
  5. Provide an operator replay action.

The existing sync recovery worker reduces some health-information stalls but does not replace a durable LINK/DATA event inbox.

HL-05 — Unknown category is acknowledged and dropped

Severity: Medium

An event category other than LINK/DATA reaches neither branch after ACK. Persisting unknown events would make future ABDM additions observable and replayable instead of silently discarded.

HL-06 — Auto-approval setup has an invisible partial-success state

Severity: High

Policy setup is best-effort. The API can report locker setup success even if automatic approval failed or was disabled administratively. The PHR always shows “Health locker set up successfully.”

Required change: return and render a structured state such as:

{
  "subscription": { "state": "ACTIVE" },
  "autoApproval": { "state": "FAILED", "retryable": true },
  "syncMode": "MANUAL_CONSENT"
}

HL-07 — Remote revoke cannot reconcile its policy

Severity: High

The code records that a revoke performed in another ABHA application cannot disable the policy because the callback has no patient token. This leaves two remote resources that can diverge.

Required change: on the patient's next authenticated status refresh, reconcile the subscription and locker policy. Clearly show any active policy attached to a revoked subscription and offer a patient-authorized cleanup action. Record reconciliation time and outcome.

HL-08 — PHR overstates automatic behavior and hides scope

Severity: High (consent UX)

The setup modal and status card say records from every linked facility synchronize automatically. That is only true when subscription scope includes the HIP/category/HI type, auto-approval matches, consent is granted, and transfer succeeds. The UI does not show:

Required change: distinguish “notifications active” from “automatic consent active,” show a concise scope summary, and link to full subscription/consent details.

Severity: Medium

The checkbox is explicit but defaults to checked and describes “future records” without showing HIP scope, HI types, purpose, or policy duration. Before certification/privacy review, confirm that this meets the current NHA consent UX requirements. A safer presentation is unchecked by default or a separate confirmation showing the exact bounded policy.

HL-10 — Generic error toasts discard actionable ABDM errors

Severity: Medium

Setup, toggle, upload, and deletion all show generic failure messages. The backend now supplies useful normalized errors, but the PHR does not display them. This hid the earlier “not in revoked state” mismatch.

Required change: show safe server messages and an error/reference code, then refetch authoritative status after every failed transition.

HL-11 — Optimistic switch behavior is not reconciled immediately

Severity: Medium

The switch is controlled by cached status and invalidated only on success. On failure, the UI does not force a status refetch; on eventual ABDM state changes, invalidation may still read a transitional state. Add an onSettled authoritative refetch and render transition states.

HL-12 — Delete does not define linked care-context behavior

Severity: High

The patient can delete the stored file, but the audit found no explicit tombstone/unavailability strategy for an already-linked ABDM care context. A later consent request could refer to a context whose document no longer exists.

Required change: decide and test the product rule with ABDM/NHA:

The PHR also needs a confirmation explaining whether deletion affects future sharing and whether it is recoverable.

HL-13 — File safety is format validation, not malware scanning

Severity: High (security)

The backend verifies magic bytes, size, and supported MIME types. That prevents simple MIME spoofing but does not detect malicious PDFs/images or parser exploits. Add quarantine, malware scanning, safe previewing, scan state, and rejection/audit behavior before making uploads available for HIP sharing.

HL-14 — Multiple active locker subscriptions are silently collapsed

Severity: Medium

getOrvoSubscription prefers a non-terminal record and then the newest. If ABDM contains duplicate live subscriptions, the patient sees one without a reconciliation warning. Detect multiple live matches, log/metric the condition, and expose an operator/patient-safe recovery path.

HL-15 — Auto-approval status failure is represented as false

Severity: Medium

If list-lockers fails, status defaults autoApprove to false. This conflates “confirmed off” with “unknown.” Use a tri-state (ACTIVE, INACTIVE, UNKNOWN) and surface last successful reconciliation time.

HL-16 — Locker-specific onward-sharing proof is missing

Severity: Medium

The generic HIP pipeline exists, but static review alone does not prove that both imported locker records and self-uploaded records can be selected, packaged, encrypted, and pushed under an external HIU consent. Add an end-to-end test fixture for each provenance type.

Do not derive the whole locker from one enabled boolean. A compatible response should separate the remote resources and sync process:

interface HealthLockerState {
  registration: 'UNAVAILABLE' | 'REGISTERED'
  subscription: {
    state:
      | 'NOT_SETUP'
      | 'REQUESTED'
      | 'ACTIVE'
      | 'DISABLED'
      | 'REVOKED'
      | 'DENIED'
      | 'EXPIRED'
      | 'ERROR'
    id: string | null
    categories: Array<'LINK' | 'DATA'>
    validUntil: string | null
  }
  autoApproval: {
    state: 'ACTIVE' | 'INACTIVE' | 'EXPIRED' | 'FAILED' | 'UNKNOWN' | 'NOT_REQUESTED'
    id: string | null
    hiTypes: string[]
    validUntil: string | null
  }
  sync: {
    state: 'IDLE' | 'WAITING_FOR_CONSENT' | 'FETCHING' | 'PARTIAL_FAILURE' | 'ERROR'
    lastSuccessfulAt: string | null
  }
  reconciledAt: string | null
}

The simple switch can remain, but it should be a capability-driven control: canEnable, canDisable, and canRepair, based on this state.

Required validation scenarios

These should run against the current ABDM sandbox in addition to unit/integration tests.

  1. Fresh patient setup without auto-approval; LINK produces a manually approvable PATRQT consent.
  2. Fresh setup with auto-approval; verify exactly one bounded policy exists.
  3. Repeated setup click/network retry; verify no duplicate subscription or policy.
  4. LINK notification with multiple HI-type groups and care contexts.
  5. Duplicate LINK event; verify idempotent inbox and one consent request.
  6. DATA event covered by a matching consent; verify one health-information request.
  7. DATA event with wrong HI type/context/purpose/period; verify fresh consent instead of reuse.
  8. Encrypted multi-entry FHIR push; verify decryption, normalization, provenance, and CM notify.
  9. Process crash after event persistence and before processing; verify worker retry.
  10. Pause; verify subscription and policy independently become inactive.
  11. Resume with and without auto-approval; verify displayed mode matches behavior.
  12. Revoke from the reference ABHA app; sign into Orvo and verify reconciliation/cleanup UX.
  13. Policy expiry while subscription remains active; verify “manual consent” rather than “automatic.”
  14. Self-upload PDF/image; verify FHIR profile, care-context link, discovery, and patient display.
  15. Delete linked self-upload; verify the approved care-context/tombstone behavior.
  16. External HIU consent for a self-uploaded record; verify Orvo HIP encryption and transfer.
  17. External HIU consent for a record imported from another HIP; verify provenance and permitted onward sharing behavior.
  18. Duplicate live subscriptions and unknown status/category values; verify safe degraded state.
  19. ABDM HTTP 200 embedded error; verify backend failure and informative PHR recovery.
  20. Unsupported/malicious upload; verify quarantine/rejection and no ABDM linking.

Delivery order

Phase 2 — truthful patient experience

Phase 3 — document lifecycle and assurance

Certification evidence to retain

For each sandbox scenario, retain redacted evidence containing:

Never place patient tokens, ephemeral private keys, decrypted clinical content, uploaded document bytes, or full consent artefacts in routine logs or certification screenshots.