Security / Healthcare · client work, details anonymised
Healthcare Identity Library
A TypeScript library that obtains, validates and spends signed SAML credentials for calls to regional public-health services, behind a region-neutral API a clinical application can code against once.
- Role
- Sole engineer
- When
- 2026
- Source
- Client code, not public
- Stack
- TypeScript
- Node.js
- SAML 2.0
- XML-DSig (xml-crypto)
- Mutual TLS
- Vitest
- StrykerJS
- An assertion validator that returns a valid result only after verifying the signature against caller-pinned certificates, with structural defences against XML signature wrapping
- A region-neutral credential facade with caching, single-flight requests and automatic remedies applied at most once
- Mutation score of 97.44% (1,600 of 1,642 mutants killed) from a local StrykerJS run
- A fake identity provider, usable in-process or as a local TLS server, so delivery never depends on regional network access
Problem
A clinical practice application had to call public-health services such as electronic health records and e-prescription. Each call must carry a signed SAML assertion from a regional identity provider, requested over mutual TLS with the application’s own client identity. The rules come from a national e-health interoperability profile. Its specification is long and partly ambiguous, and in places it contradicts its own worked examples. When something is wrong, it fails late, as an opaque regional error code in front of a clinician. The application will also integrate other regions that use entirely different mechanisms.
My role
Sole engineer, from August to September 2026. I wrote the domain glossary, the architecture decision records, a log of specification questions and deviations, the library and its test tooling. Work was cut into GitHub issues and delivered through a gated, agent-assisted pipeline: each issue was specified as failing tests, implemented, then hardened.
Architecture
The package has three entrypoints. A neutral core exposes one operation. You request a credential for a tenant, operator, optional delegate, purpose and target service, and you get back either a credential that knows how to spend itself or a failure carrying the region’s code and a neutral remedy. A regional adapter holds the request-envelope builder, the response and assertion validators, signature verification, the HTTPS mutual-TLS transport, a credential store, and remedy logic. A testing entrypoint ships a fake identity provider driven by a behaviour table, plus certificate and DER helpers for generating test identities.
Key decisions
- Region-neutral facade (ADR 0001). The host codes against
getCredentialand the regional protocol lives behind it.coremay not import or name SAML, and an isolation test enforces that boundary. - Smart constructor, then infallible serialiser. The request constructor checks every invariant and throws a typed error naming the one that failed. The builder that serialises the result has no failure mode and returns bytes, so the declared encoding cannot drift.
- Structure before cryptography. Signature checking first requires a single reference to the assertion’s own ID, exactly one assertion in the document, and a pinned algorithm set. Only then is the signer’s certificate matched byte for byte against the tenant’s pinned certificates and the digest verified. There is no option to skip verification.
- Closed vocabularies. Request contexts are a closed union. The README is upfront about the cost: if the region adds a code later, the library needs a release. The design accepts that to keep typos from reaching the identity provider.
- PEM and PKCS#12 client identities (ADR 0002). Both are validated before any network call. Typed errors never contain secret material, and repository checks reject committed private keys.
- Live verification separate from delivery (ADR 0003). The fake identity provider is the deterministic gate. The real labeling path is a manual smoke test that writes only redacted evidence.
- Remedies at most once per call. Refresh or scoped re-request happen automatically with no loops, and the observer events carry no personal data.
Outcomes
The library builds requests, validates and caches assertions, and spends them as a WS-Security header or a compressed authorization value. Tests sit next to the code across 36 test files, plus a packaged acceptance suite, and the README reports a 97.44% mutation score.