@steve228uk/nhs-cli 0.0.1 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +26 -0
- package/CONTRIBUTING.md +11 -0
- package/INSTALL.md +88 -0
- package/README.md +140 -3
- package/SECURITY.md +9 -0
- package/bin/nhs-prescriptions.mjs +11 -0
- package/bin/nhs.mjs +3 -0
- package/docs/android-network-inspection.md +99 -0
- package/docs/api-observation-template.md +42 -0
- package/docs/api-research.md +128 -0
- package/docs/architecture.md +27 -0
- package/docs/authentication.md +45 -0
- package/docs/cli.md +35 -0
- package/docs/releasing.md +65 -0
- package/docs/validation.md +39 -0
- package/package.json +44 -7
- package/skills/nhs/SKILL.md +74 -0
- package/src/auth.mjs +282 -0
- package/src/cli.mjs +189 -0
- package/src/config.mjs +29 -0
- package/src/credentials.mjs +19 -0
- package/src/domains.mjs +184 -0
- package/src/errors.mjs +31 -0
- package/src/otp.mjs +46 -0
- package/src/output.mjs +32 -0
- package/src/storage.mjs +211 -0
- package/src/transport.mjs +116 -0
- package/src/types.mjs +13 -0
- package/src/ui.mjs +52 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Working on NHS CLI
|
|
2
|
+
|
|
3
|
+
This unofficial client handles authentication secrets and health information. Read `docs/architecture.md` before changing subsystem boundaries and `docs/api-research.md` before changing endpoints.
|
|
4
|
+
|
|
5
|
+
## Development
|
|
6
|
+
|
|
7
|
+
- Use Node 22 or 24, JavaScript ESM and checked JSDoc. Run `npm ci`, `npm run check`, and `npm test`.
|
|
8
|
+
- `npm run build` checks types and packs the reviewed file allowlist into `dist/`; `npm run test:package` verifies an isolated install. Keep `bin/` tracked: it contains executable source.
|
|
9
|
+
- Native storage tests are opt-in: `NHS_CLI_TEST_KEYRING=1 npm run test:keyring` creates and removes an isolated synthetic OS keyring entry. Linux requires persistent Secret Service.
|
|
10
|
+
- Keep credentials, cookies, patient identifiers, APKs, decompiled code, HARs and live responses out of the repository. Fixtures must be synthetic. Never inspect a user's vault or retrieve their credentials to debug code.
|
|
11
|
+
- Unit tests inject transports, clocks and stores. They must never contact NHS. Live smoke tests require an explicit request naming the operation; do not include clinical writes.
|
|
12
|
+
|
|
13
|
+
## Invariants
|
|
14
|
+
|
|
15
|
+
- Commands call domain adapters and `AuthClient`; only storage accesses vault files or keyring backends. Secrets enter via injection, terminal prompts (visible email/OTP, masked password), or explicit legacy migration. Interactive prompts require terminal input and stderr; progress contains only fixed labels and is disabled for JSON/noninteractive output.
|
|
16
|
+
- All persistent authentication data is encrypted. Default keys remain in the OS keyring; headless keys must be injected separately. Never add plaintext fallback or save injected credentials without `--save-credentials`.
|
|
17
|
+
- `@napi-rs/keyring` is pinned because its Linux fallback is security-relevant. Preserve the Secret Service verification before passing secrets to a new Entry. An environment-variable check is insufficient.
|
|
18
|
+
- `withLock` protects the whole authenticated command. Do not nest locks or steal unidentified locks. Preserve atomic writes and migration verification.
|
|
19
|
+
- Cookies stay scoped, redirects bounded, and destinations HTTPS-allowlisted. OAuth state must match. HTTP 403/429/5xx are not proof that credentials need to be resubmitted.
|
|
20
|
+
- New endpoints need client evidence, capability gates, response checks and synthetic tests. Do not infer routes from their names or send NHS credentials to third-party portals.
|
|
21
|
+
- Runtime APK research follows `docs/android-network-inspection.md`. Record native/web/CLI provenance separately, keep captures outside Git, and distinguish trust-store failure from active pinning. The official app may mark messages read when opening details.
|
|
22
|
+
- Reads must not send messages, mark them read, switch patients, book/cancel appointments, or submit prescriptions. Document actual semantics of upstream POSTs used for authentication or reads.
|
|
23
|
+
- Prescription submission requires explicit user authorization for the exact scope. Respect existing session authorization instead of asking repeatedly. Still require `--confirm`, validate current requestability and never retry ambiguous submissions.
|
|
24
|
+
- Output is health data. Diagnostics must not include raw native/upstream errors or bodies, even under `--debug`.
|
|
25
|
+
|
|
26
|
+
The package is public and publishable. Releases require an explicit release request; follow `docs/releasing.md`, inspect package contents and repository history, and use GitHub Actions OIDC for version-tag releases. The initial `0.0.1` bootstrap is the documented CLI-published exception. Do not publish, push, or alter account credentials during routine validation. Update command docs and the single `skills/nhs/SKILL.md` with behaviour changes. Agent setup lives in `INSTALL.md`; Grokbot runs the CLI on the user's Mac through ExternalShell and saves the same skill through skill-write.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Use Node 22 or 24; run `npm ci`, `npm run check`, and `npm test`. Update affected docs and skills. Follow `AGENTS.md`.
|
|
4
|
+
|
|
5
|
+
Use synthetic credentials, patients, medicines and responses in tests. Never attach a live response, decrypted session, Keychain entry, medical screenshot, APK or decompiled source to an issue or PR. Reports should include CLI version, OS, error code and public-client evidence without account information.
|
|
6
|
+
|
|
7
|
+
Document new methods in `docs/api-research.md`: origin, parameters, capability restrictions, responses and side effects. Test behavioural contracts, including recovery and prevention of duplicate writes. Never add live NHS calls to CI.
|
|
8
|
+
|
|
9
|
+
Native integration tests are opt-in: `NHS_CLI_TEST_KEYRING=1 npm run test:keyring`. They use a unique service and synthetic state. Linux tests must demonstrate Secret Service persistence and failure when unavailable. Dependency upgrades must recheck fallback behaviour before changing the pin.
|
|
10
|
+
|
|
11
|
+
Before release work, run `npm run build` and `npm run test:package`, review staged files/history for accidental secrets and run the CI matrix. `bin/` is executable source; generated packages belong in ignored `dist/`. Releases require an explicit request and follow [the release runbook](docs/releasing.md), using GitHub Actions OIDC after the one-time bootstrap. Never add an npm publish token to CI.
|
package/INSTALL.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# Install NHS CLI and its agent skill
|
|
2
|
+
|
|
3
|
+
This is an installation guide for the agent receiving the user's setup request. Perform the applicable steps and report the result; do not simply repeat this document. Use npm on the user's computer.
|
|
4
|
+
|
|
5
|
+
The user can paste:
|
|
6
|
+
|
|
7
|
+
> Install NHS CLI from https://github.com/steve228uk/nhs-cli. Follow INSTALL.md to install the CLI and NHS skill for this agent, then walk me through secure terminal login.
|
|
8
|
+
|
|
9
|
+
Install one CLI package (`@steve228uk/nhs-cli@0.1.0`) and one skill (`nhs`). Do not create a teammate, schedule checks, read medical data, or submit prescriptions during installation. The `0.0.1` bootstrap package is a placeholder and does not contain the CLI.
|
|
10
|
+
|
|
11
|
+
## 1. Choose the user's computer
|
|
12
|
+
|
|
13
|
+
For a local coding agent, run commands on the user's configured macOS or Linux computer. On Linux, secure storage requires an unlocked persistent Secret Service, or explicit encrypted-file configuration with a separately injected key; see [authentication](docs/authentication.md).
|
|
14
|
+
|
|
15
|
+
For **Grokbot**, follow the same local execution model as the [Grok Bot iMessage pack](https://github.com/steve228uk/grok-bot-imessage/blob/main/INSTALL.md): use `ExternalShell` on the user's Mac. Never install or authenticate on Grokbot's own hosted Linux computer. Run `uname -s` through ExternalShell; if the result is not `Darwin`, or local execution is unavailable, direct the user to [Local execution](grokbot://app/v1/settings?id=local-execution) and wait for them to connect their Mac. Do not create a teammate.
|
|
16
|
+
|
|
17
|
+
## 2. Install and verify the CLI
|
|
18
|
+
|
|
19
|
+
Check `node --version`, `npm --version`, and `command -v nhs`. Use Node 22 or 24. If Node/npm is missing, explain that prerequisite and use the user's existing Node installation method; do not change a system-wide runtime without their instruction.
|
|
20
|
+
|
|
21
|
+
If `nhs --version` already reports `0.1.0` or newer, keep it. Otherwise install:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install --global @steve228uk/nhs-cli@0.1.0
|
|
25
|
+
nhs --version
|
|
26
|
+
nhs --help
|
|
27
|
+
nhs doctor --json
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Do not use `sudo` to work around npm permissions; use a user-writable npm prefix or the user's Node manager. Keep optional dependencies enabled: the pinned keyring package provides secure storage. If `nhs` is not found after installation, check the npm prefix and ensure its `bin` directory is on the local execution process's PATH.
|
|
31
|
+
|
|
32
|
+
If the registry does not have `0.1.0` yet, report that release availability is pending. Do not substitute the placeholder. For an explicitly requested source install, use a reviewed checkout and its build instructions in the README.
|
|
33
|
+
|
|
34
|
+
`doctor` checks local prerequisites, not NHS connectivity. A storage failure needs the keyring unlocked/configured or the original injected key restored. Never inspect stored secrets, generate a replacement key for an existing vault, or fall back to plaintext.
|
|
35
|
+
|
|
36
|
+
## 3. Install the one NHS skill
|
|
37
|
+
|
|
38
|
+
The canonical file is `skills/nhs/SKILL.md`, also included in the npm package. Install it for the current agent, not every agent found on the machine.
|
|
39
|
+
|
|
40
|
+
### Grokbot
|
|
41
|
+
|
|
42
|
+
Fetch the canonical file from the release tag:
|
|
43
|
+
|
|
44
|
+
https://raw.githubusercontent.com/steve228uk/nhs-cli/v0.1.0/skills/nhs/SKILL.md
|
|
45
|
+
|
|
46
|
+
Save it with Grokbot's skill-write tool using ID `nhs`, the `name` and `description` from YAML frontmatter, and the Markdown body. Update an existing `nhs` skill instead of creating a duplicate. If skill-write is unavailable, report that the CLI is installed but skill installation needs a capable Grokbot session; do not invent a filesystem path on its hosted computer.
|
|
47
|
+
|
|
48
|
+
### Codex, Claude Code, Cursor, and other supported agents
|
|
49
|
+
|
|
50
|
+
Use the [skills installer](https://github.com/vercel-labs/skills) with the current agent's supported ID. For example, Codex:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
npx skills add https://github.com/steve228uk/nhs-cli/tree/v0.1.0/skills/nhs --skill nhs --global --agent codex --yes
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Use `claude-code` or `cursor` when appropriate. For an unknown agent ID, consult the installer's supported-agent list rather than guessing. For manual installation, copy the complete `nhs` folder from `$(npm root --global)/@steve228uk/nhs-cli/skills/nhs` into the current agent's documented skills directory. Update only this skill, preserving unrelated skills and user configuration.
|
|
57
|
+
|
|
58
|
+
If the obsolete `nhs-prescriptions` skill is already installed, explain that `nhs` replaces it and remove the obsolete stock skill only when it has no user customizations. Preserve customized content for the user to review. The `nhs-prescriptions` executable remains compatible.
|
|
59
|
+
|
|
60
|
+
## 4. Walk through secure login
|
|
61
|
+
|
|
62
|
+
Run `nhs auth status --json` to report local saved-material status; it does not prove server validity. If a session is already stored, preserve it and do not force reauthentication during setup.
|
|
63
|
+
|
|
64
|
+
If login is needed, have the user run this directly in their own terminal:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
nhs auth login
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Explain that email and OTP prompts are visible and the password is masked. For encrypted credential reuse on future logins, offer this explicit alternative:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
nhs auth login --save-credentials
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Never collect passwords, OTPs, or encryption keys in chat, command arguments, shell history, logs, or agent tool outputs. Do not drive secret entry through ExternalShell or a captured terminal. The user completes it privately in their terminal. Do not read the vault, Keychain, or secret environment variables to debug login.
|
|
77
|
+
|
|
78
|
+
These NHS-specific rules also apply when Grokbot's iMessage skill is active: do not ask for pasted OTPs or delete OTP messages through that skill. `--messages-otp` is an optional CLI feature requiring explicit user authorization and macOS Messages access. Otherwise the user enters the code in the CLI. On cancellation, stop; on MFA/login failure, report the CLI's safe error code without requesting raw responses.
|
|
79
|
+
|
|
80
|
+
After the user completes login, check only:
|
|
81
|
+
|
|
82
|
+
```sh
|
|
83
|
+
nhs auth status --json
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## 5. Report completion
|
|
87
|
+
|
|
88
|
+
Report CLI version and computer, whether the `nhs` skill was saved for the current agent, the doctor result, and whether login material is stored. Identify any unfinished prerequisite. Explain that no health records were fetched and no prescription requests were sent; the CLI is ready for the user's next explicitly requested operation.
|
package/README.md
CHANGED
|
@@ -1,7 +1,144 @@
|
|
|
1
1
|
# NHS CLI
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An unofficial CLI for NHS App services, with securely saved login state and commands for prescriptions, GP records, results, appointments, messages, profiles, pharmacies and documents.
|
|
4
4
|
|
|
5
|
-
The
|
|
5
|
+
The npm package is `@steve228uk/nhs-cli`; the command is `nhs`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
This experimental client uses undocumented endpoints and is not affiliated with or endorsed by the NHS. Availability depends on your account and GP provider. Secure login, cross-process session reuse, capability discovery and current medicines have passed live checks. Other read adapters are based on the public client and synthetic tests; they still need account verification. Confirm important information in the official NHS App.
|
|
8
|
+
|
|
9
|
+
## Quick install
|
|
10
|
+
|
|
11
|
+
Use Node.js 22 or 24 on macOS or Linux:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install --global @steve228uk/nhs-cli@0.1.0
|
|
15
|
+
nhs --version
|
|
16
|
+
nhs doctor --json
|
|
17
|
+
nhs auth login
|
|
18
|
+
nhs auth status --json
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
macOS uses Keychain. Desktop Linux needs an unlocked Secret Service provider such as GNOME Keyring, session D-Bus, and the `@napi-rs/keyring` optional dependency. Unavailable secure storage stops the CLI; it never silently saves a plaintext session.
|
|
22
|
+
|
|
23
|
+
### Give this prompt to your agent
|
|
24
|
+
|
|
25
|
+
> Install NHS CLI from https://github.com/steve228uk/nhs-cli. Follow INSTALL.md to install the CLI and NHS skill for this agent, then walk me through secure terminal login.
|
|
26
|
+
|
|
27
|
+
[The installer](INSTALL.md) supports local coding agents and **Grokbot**. Grokbot saves the same NHS skill using skill-write and runs the CLI on your Mac through local execution. It never installs or logs in on its own hosted computer. Passwords and OTPs stay in your terminal, including when an iMessage skill is installed.
|
|
28
|
+
|
|
29
|
+
### Install the agent skill yourself
|
|
30
|
+
|
|
31
|
+
One `nhs` skill covers reads, login recovery, exports, and explicitly authorized repeat-prescription requests. With the [skills CLI](https://github.com/vercel-labs/skills), select your agent:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
# Codex; use claude-code or cursor for those agents.
|
|
35
|
+
npx skills add https://github.com/steve228uk/nhs-cli/tree/v0.1.0/skills/nhs --skill nhs --global --agent codex --yes
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
For another runtime, copy the `nhs` folder from `$(npm root --global)/@steve228uk/nhs-cli/skills/nhs` into its documented skills directory. Grokbot users should use the prompt above. The old separate prescription skill is now part of `nhs`; the legacy CLI command remains supported.
|
|
39
|
+
|
|
40
|
+
The `0.0.1` bootstrap package is a placeholder, not a usable CLI. If `0.1.0` is not yet available, use the development build below or wait for the release.
|
|
41
|
+
|
|
42
|
+
## Secure login
|
|
43
|
+
|
|
44
|
+
Ordinary login saves an encrypted session. To also save verified credentials for future sign-ins, explicitly choose:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
nhs auth login --save-credentials
|
|
48
|
+
nhs auth status --json
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Login uses Clack terminal prompts: email and NHS security codes are visible; the password is masked. Progress explains each login stage. Prompts use terminal stderr, keeping JSON on stdout separate. Use `--no-prompt` for unattended calls. `--save-credentials` encrypts verified credentials for reuse. Ordinary commands reuse sessions first and can sign in again using saved credentials and remembered-device state. NHS may still require another code or a full login.
|
|
52
|
+
|
|
53
|
+
`auth login` reuses a valid session. `--reauth` deliberately signs in again; `--save-credentials` also performs fresh verification before saving. `auth status` reports locally saved material without claiming server validity.
|
|
54
|
+
|
|
55
|
+
Authentication data lives in an AES-256-GCM encrypted vault. Its random encryption key stays in the OS credential store, allowing atomic session updates without credential-store size limits. Ordinary files contain no plaintext credentials, tokens or patient identifiers. See [storage and authentication](docs/authentication.md).
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
nhs auth logout
|
|
59
|
+
nhs auth logout --forget
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Logout clears session/device state and attempts server-session deletion. `--forget` also clears CLI-managed credentials. The OS encryption-key entry remains so the cleared vault stays readable. Injected credentials and original legacy Keychain entries remain managed by their source.
|
|
63
|
+
|
|
64
|
+
## Commands
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
nhs capabilities --json
|
|
68
|
+
nhs prescriptions list --json
|
|
69
|
+
nhs prescriptions history --from=2026-01-01 --json
|
|
70
|
+
nhs records --json
|
|
71
|
+
nhs results list --year=2025 --json
|
|
72
|
+
nhs results get result-id --json
|
|
73
|
+
nhs appointments list --json
|
|
74
|
+
nhs appointments slots --json
|
|
75
|
+
nhs messages list --source=nhs --index=0 --count=20 --json
|
|
76
|
+
nhs messages get message-id --source=gp --json
|
|
77
|
+
nhs profile --json
|
|
78
|
+
nhs pharmacy --json
|
|
79
|
+
nhs documents list --json
|
|
80
|
+
nhs documents get document-id --json
|
|
81
|
+
nhs documents download document-id --output=./letter.pdf
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Messages are read without mark-as-read calls. Documents use GP Connect and an ID from the current account's available document list. Unimplemented providers return `unsupported`; disabled access returns `capability_unavailable`.
|
|
85
|
+
|
|
86
|
+
Use `--output=./new-file.json` for explicit sensitive JSON exports. Exports use mode `0600`, require an existing parent directory and never overwrite files. Health data is not cached. Terminal and JSON output remain sensitive health information.
|
|
87
|
+
|
|
88
|
+
### Prescription requests
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
nhs prescriptions order --ids=course-id-1,course-id-2 --dry-run --json
|
|
92
|
+
nhs prescriptions order --ids=course-id-1,course-id-2 --confirm --json
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Review the preview and authorize the exact medicines before submission. Use the same IDs and user-supplied `--note` for preview and submission. `--all-requestable` cannot be combined with `--ids`. Unknown, duplicate or unavailable IDs are rejected. An uncertain response returns `order_unknown`; check the official app before trying again. Submission is not GP approval or pharmacy dispatch.
|
|
96
|
+
|
|
97
|
+
### Agents and headless Linux
|
|
98
|
+
|
|
99
|
+
Runtimes can inject `NHS_CLI_CREDENTIALS` or legacy `NHS_PRESCRIPTIONS_CREDENTIALS` as JSON containing `email` and `password`. Never put actual credentials in `.env`, shell commands, prompts, logs or issues. Injection takes precedence over stored credentials and is not persisted without `--save-credentials`.
|
|
100
|
+
|
|
101
|
+
For Linux without a keyring, explicitly set non-secret configuration `NHS_CLI_STORAGE=encrypted-file`. Have your secret manager inject `NHS_CLI_STATE_KEY`: standard base64 encoding of a random 32-byte key, stable across invocations. Keep it separate from the vault. Missing or incorrect keys stop the CLI; no fallback key is generated.
|
|
102
|
+
|
|
103
|
+
```sh
|
|
104
|
+
nhs prescriptions list --no-prompt --json
|
|
105
|
+
nhs prescriptions list --no-login --no-prompt --json
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`--no-login` prevents credential login but permits session validation, GP uplift and bearer renewal. `--no-prompt` prevents CLI terminal prompts; the OS keyring must still be unlocked. `auth_required` means a human must run login in a terminal.
|
|
109
|
+
|
|
110
|
+
`--messages-otp` opts into macOS Messages lookup, limited to NHS messages received after the current challenge. It needs Messages access and `sqlite3`. Lookup can fall back to terminal entry unless `--no-prompt` is set. `--force-otp` overrides only the local ten-minute SMS cooldown, not NHS limits.
|
|
111
|
+
|
|
112
|
+
The [NHS skill](skills/nhs/SKILL.md) includes these authentication rules and prescription authorization guidance. It contains no account data or credentials.
|
|
113
|
+
|
|
114
|
+
## Migration
|
|
115
|
+
|
|
116
|
+
`nhs-prescriptions status|list|order|login|doctor` remain available. Prescription status/submission JSON retains prior shapes; previews add the note. See [CLI contracts](docs/cli.md) for clarified diagnostics and errors.
|
|
117
|
+
|
|
118
|
+
The first authenticated command imports `~/.local/share/nhs-prescriptions/state.json`, verifies secure storage and then deletes the plaintext file. Unsafe permissions or failed verification preserve the original and stop. No plaintext backup is created. Original macOS credential entries can be explicitly copied with:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
nhs auth login --migrate-credentials
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
To map remaining API variants using the official app, see the [Android runtime network-inspection workflow](docs/android-network-inspection.md) and [API observation template](docs/api-observation-template.md).
|
|
127
|
+
|
|
128
|
+
```sh
|
|
129
|
+
npm ci
|
|
130
|
+
npm run build
|
|
131
|
+
npm test
|
|
132
|
+
npm run test:package
|
|
133
|
+
NHS_CLI_TEST_KEYRING=1 npm run test:keyring
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`build` checks JavaScript/JSDoc and creates `dist/steve228uk-nhs-cli-0.1.0.tgz` with a verified package file list. JavaScript runs directly; there is no transpilation step. `test:package` installs that tarball into a temporary prefix and checks both executables and synthetic local diagnostics. To install your reviewed local build for normal use:
|
|
137
|
+
|
|
138
|
+
```sh
|
|
139
|
+
npm install --global ./dist/steve228uk-nhs-cli-0.1.0.tgz
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Tests use synthetic data, never NHS services. The opt-in native test creates and deletes a unique test entry. See [architecture](docs/architecture.md), [API research](docs/api-research.md), [validation and remaining checks](docs/validation.md), [contributing](CONTRIBUTING.md), [security](SECURITY.md), and [AGENTS.md](AGENTS.md).
|
|
143
|
+
|
|
144
|
+
Releases use version tags and GitHub Actions OIDC after the one-time npm bootstrap. See [the release runbook](docs/releasing.md). Installing the package never runs login or installs agent skills automatically.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Security
|
|
2
|
+
|
|
3
|
+
This unofficial project processes authentication secrets and health information. Supported persistence is OS-keyring-backed authenticated encryption, or explicitly configured encryption with a separately injected key. Plaintext credential/session storage is not supported.
|
|
4
|
+
|
|
5
|
+
Sensitive boundaries include cookie scoping, OAuth validation, keyring fallback, migration, patient-context selection, output filtering and submission retries. OS credential storage does not protect against malicious software already running as the same user or a compromised secret manager.
|
|
6
|
+
|
|
7
|
+
Report vulnerabilities privately through the repository host's private reporting facility when available. Otherwise, open a minimal issue requesting a private contact method without exploit details, authentication material or patient information. For NHS service vulnerabilities, use the NHS's own disclosure channel.
|
|
8
|
+
|
|
9
|
+
Provide synthetic reproductions and the affected CLI version. Do not test other accounts or modify real healthcare data. Rotate/revoke exposed material through its source and the official service; deleting a file does not remove it from history or backups.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
import { main } from '../src/cli.mjs';
|
|
5
|
+
|
|
6
|
+
// Preserve legacy commands and helper imports while sharing the secure client.
|
|
7
|
+
export { buildStatusPayload, parsePositiveInteger } from '../src/domains.mjs';
|
|
8
|
+
export { parseCredentialsPayload } from '../src/credentials.mjs';
|
|
9
|
+
export { redact } from '../src/errors.mjs';
|
|
10
|
+
export { extractOtpCode, isLikelyNhsOtpText } from '../src/otp.mjs';
|
|
11
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href) await main(process.argv.slice(2), true);
|
package/bin/nhs.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Inspecting the Android app's network traffic
|
|
2
|
+
|
|
3
|
+
Use runtime observation to connect a specific app action to its actual requests, capability checks and response structures. Combine it with [static API evidence](api-research.md); strings in an APK alone cannot establish which API a particular account uses.
|
|
4
|
+
|
|
5
|
+
## Current readiness
|
|
6
|
+
|
|
7
|
+
On 2026-09-06, the research Mac is ARM64 and the downloaded NHS App 6.4.2 XAPK is still available outside the repository. The archive contains a base APK, `config.arm64_v8a.apk`, language splits and `config.xxhdpi.apk`. Android Studio, the conventional Android SDK directory, an AVD directory, `adb`, `emulator` and mitmproxy were not found in the checked locations/PATH. The APK has **not been launched or captured** in this investigation. Installing the Android tooling and creating a dedicated AVD are prerequisites.
|
|
8
|
+
|
|
9
|
+
The package hashes, signer verification and limits of third-party download provenance are recorded in [API research](api-research.md). Recheck those before installation. Runtime success under an emulator, proxy or modified trust store remains unverified.
|
|
10
|
+
|
|
11
|
+
## Prepare an isolated device
|
|
12
|
+
|
|
13
|
+
Install Android Studio/SDK tooling from the [Android downloads](https://developer.android.com/studio), including Platform Tools, Emulator and Build Tools. Create a dedicated ARM64 AVD, named `nhs-research` in the examples below. Record the exact image/API level and whether it is AOSP, Google APIs or Google Play. Use an Android version supported by the app; manifest minimum API 26 does not guarantee the server will accept every older image.
|
|
14
|
+
|
|
15
|
+
Keep this AVD separate from normal personal devices and accounts. Its writable data image can contain app credentials and health-data caches even when the proxy never saves flows. Keep its directory private and on encrypted storage. Start without loading or saving snapshots:
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
emulator -list-avds
|
|
19
|
+
emulator -avd nhs-research -no-snapshot
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Use a second terminal to select the exact test device from `adb devices -l`. Set `NHS_DEVICE_SERIAL` to the returned emulator serial; use `adb -s "$NHS_DEVICE_SERIAL"` on every subsequent command so a connected personal phone cannot be selected accidentally. See the [emulator command-line reference](https://developer.android.com/studio/run/emulator-commandline).
|
|
23
|
+
|
|
24
|
+
Extract only the reviewed APK entries into a private directory outside the repository. Set `NHS_APK_DIR` to that directory. Install the matching splits together on the fresh ARM64 AVD:
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
adb -s "$NHS_DEVICE_SERIAL" shell getprop ro.product.cpu.abilist
|
|
28
|
+
adb -s "$NHS_DEVICE_SERIAL" install-multiple \
|
|
29
|
+
"$NHS_APK_DIR/com.nhs.online.nhsonline.apk" \
|
|
30
|
+
"$NHS_APK_DIR/config.arm64_v8a.apk" \
|
|
31
|
+
"$NHS_APK_DIR/config.en.apk" \
|
|
32
|
+
"$NHS_APK_DIR/config.xxhdpi.apk"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The reviewed archive has only one ABI split. Do not install it on an x86 image or install only the base APK. Other language splits are optional for that language; select the appropriate density split for a different download. Launch the installed app from its launcher icon and verify its displayed version. A split/ABI installation error is separate from a server rejection. Refer to [ADB's install and device-selection documentation](https://developer.android.com/tools/adb).
|
|
36
|
+
|
|
37
|
+
## Establish a baseline, then a proxy
|
|
38
|
+
|
|
39
|
+
First launch the unmodified app without a proxy and inspect public startup/configuration behaviour. Record whether it starts, which login UI it opens, and any compatibility error category. Do not interpret a failed emulator launch as attestation enforcement without supporting evidence.
|
|
40
|
+
|
|
41
|
+
For HTTPS inspection, install [mitmproxy](https://docs.mitmproxy.org/stable/overview/installation/) and use its local interactive UI. Set `NHS_PROXY_DIR` to a new private directory outside Git and cloud sync, then run this in a local terminal that is not being recorded by an agent or session logger:
|
|
42
|
+
|
|
43
|
+
```sh
|
|
44
|
+
umask 077
|
|
45
|
+
: "${NHS_PROXY_DIR:?Set a new private path outside the repository}"
|
|
46
|
+
mkdir -m 700 "$NHS_PROXY_DIR" &&
|
|
47
|
+
mitmweb --listen-host 127.0.0.1 --listen-port 8080 \
|
|
48
|
+
--set "confdir=$NHS_PROXY_DIR" \
|
|
49
|
+
--set web_host=127.0.0.1 \
|
|
50
|
+
--set web_port=8081 \
|
|
51
|
+
--set ssl_insecure=false
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Use a new directory to avoid inherited proxy addons or capture settings. Do not enable `save_stream_file`, `hardump`, a static viewer export, or request/response logging. Do not export flows as curl commands: those include authentication material. The UI and its startup access token are sensitive. Inspect locally in memory; this is not a guarantee against OS swap, browser caches or crash dumps. The proxy CA private key is created in its configuration directory and must also remain private. See [mitmproxy's options](https://docs.mitmproxy.org/stable/concepts/options/).
|
|
55
|
+
|
|
56
|
+
Configure the **Android system Wi-Fi proxy** inside the AVD to host `10.0.2.2`, port `8080`. That address reaches this host's loopback from the emulator. Use Android Settings, not Android Studio's download proxy or the emulator's Extended Controls proxy. Google's current guidance distinguishes the Android system proxy used for HTTPS debugging from the emulator `-http-proxy` tunnel; some applications can ignore the system proxy. See [proxy selection](https://developer.android.com/studio/run/emulator-networking-proxy) and [network addressing](https://developer.android.com/studio/run/emulator-networking-address).
|
|
57
|
+
|
|
58
|
+
Confirm routing with a non-sensitive browser request before opening NHS login. Limit interception to the NHS origins needed for the selected action using mitmproxy's `allow_hosts` option. A display filter only hides flows; it does not limit collection. Start with the app, login and authorization origins already documented, then review any newly observed destination before adding it. Unmatched traffic may pass through without decryption.
|
|
59
|
+
|
|
60
|
+
## Establish trust without misdiagnosing pinning
|
|
61
|
+
|
|
62
|
+
The inspected app targets API 36 and declares no custom trust anchors. Android's default policy for modern target SDKs does not trust user-installed CAs. Therefore installing the proxy certificate in the user store may make the browser work while the app still fails. This is a trust-policy issue, not proof of active pinning. See [Android network security configuration](https://developer.android.com/privacy-and-security/security-config).
|
|
63
|
+
|
|
64
|
+
If HTTPS bodies are needed, use a disposable image that permits installing the inspection CA into the system trust store, and follow instructions appropriate to that exact image. Google Play production images restrict `adb root`; recent Android images can also have different trust-store layouts. The [mitmproxy system-CA guide](https://docs.mitmproxy.org/stable/howto/install-system-trusted-ca-android/) describes the approach but documents several procedures against older API levels. Do not assume an old remount recipe works on API 36.
|
|
65
|
+
|
|
66
|
+
Record every trust-store/image change. Keep the APK unchanged for the baseline comparison and retain upstream TLS certificate verification. Do not change the Mac's global trust store, install a CA on a personal phone, or make the CLI disable TLS verification as part of this workflow.
|
|
67
|
+
|
|
68
|
+
If a correctly trusted proxy still fails, separate routing, hostname/chain validation, app-specific trust, possible active pinning, and server/device-integrity rejection. Collect a non-sensitive error category and compare the same action without the proxy. A CONNECT record or encrypted packet trace can show a destination, but cannot establish HTTP paths or JSON contracts. Do not report decrypted traffic unless it was actually visible. Instrumentation or a modified APK would be a separate experiment whose results must identify that change; it cannot prove stock-app behaviour.
|
|
69
|
+
|
|
70
|
+
## Observe one flow at a time
|
|
71
|
+
|
|
72
|
+
Use the account holder's own login or a permitted test account. The user enters credentials and MFA directly in the app; agents must not retrieve the CLI vault or extract tokens for replay. Agree the named account reads before navigating: existing authorization for those actions remains sufficient.
|
|
73
|
+
|
|
74
|
+
| Priority | Observation | Questions to resolve |
|
|
75
|
+
| --- | --- | --- |
|
|
76
|
+
| 1 | Login, normal app restart, later session expiry | Which device-cookie and body-token values rotate independently? Which API establishes the app session? Does restart reuse it? |
|
|
77
|
+
| 2 | First GP read after login | When is asserted-identity OAuth/GP uplift requested, and which values change? |
|
|
78
|
+
| 3 | Active session extension and NHS inbox | What triggers extension and bearer renewal? Is app-session expiry handled separately? |
|
|
79
|
+
| 4 | Version-3 results and historical results | Which service/provider, paths, list/detail schema and paging rules are actually selected? |
|
|
80
|
+
| 5 | Document metadata and explicit retrieval | Which provider/identifier is used? Does a POST retrieve content, start a job or change state? |
|
|
81
|
+
| 6 | Appointments, GP inbox and pharmacy | Which capability gates and unavailable/error shapes apply? |
|
|
82
|
+
|
|
83
|
+
Do not accelerate expiry by changing device time or repeatedly submit bad logins. Observe ordinary elapsed-time behaviour. Do not submit prescriptions, book appointments, change pharmacies or switch proxy patients while mapping reads.
|
|
84
|
+
|
|
85
|
+
Opening a message in the official app may automatically mark it read even though the CLI omits that operation. Inspect message lists and static call sites first; use a permitted test message or separately authorized read-status change for a detail-screen experiment. Do not label a UI action read-only just because its first request is GET, or block every POST indiscriminately: authentication and some document reads use POST.
|
|
86
|
+
|
|
87
|
+
## Convert observation into repository evidence
|
|
88
|
+
|
|
89
|
+
Write original notes while inspecting; retain no raw HARs, flow files, screenshots, logcat dumps, bearer tokens or patient responses in this project. Before committing notes, replace identifiers in paths with placeholders, omit query/header values, and use synthetic values for examples. Names of fields and object keys can themselves contain patient data, so a generic redaction pass is insufficient.
|
|
90
|
+
|
|
91
|
+
Use the [observation template](api-observation-template.md) for each action. Record method, origin, templated path, parameter names/types, required authentication material by **name**, response structure, capability gate, side effects, timing/retry behaviour and observed failure cases. Describe cookie scope/expiry and whether values rotated without copying or hashing the values.
|
|
92
|
+
|
|
93
|
+
Label source as `native`, `web` or `cli`, and observation as `static` or `runtime`. An account-verified native request does not automatically validate the CLI: compare its own request and selected read outcome separately. Unknown auth requirements remain unknown until observed; do not remove headers or replay requests against production merely to infer which ones are mandatory.
|
|
94
|
+
|
|
95
|
+
Update the inventory, add hand-authored synthetic fixtures, implement the smallest matching adapter and run the relevant mocked tests. Only promote an endpoint's evidence level for the client/account flow actually observed.
|
|
96
|
+
|
|
97
|
+
## Cleanup
|
|
98
|
+
|
|
99
|
+
Remove the Android Wi-Fi proxy setting, stop mitmweb, close its local browser UI and shut down the dedicated AVD without snapshots. Remove the inspection CA from the test image or delete that disposable AVD through Device Manager when finished. Remove the dedicated proxy directory containing its CA key and any accidental exports. Avoid deleting shared SDK images or unrelated devices. Deletion does not erase historical backups; isolation and encrypted storage are needed from the beginning.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# API observation template
|
|
2
|
+
|
|
3
|
+
Copy this structure for a reviewed observation. This file contains no live evidence. Use synthetic examples only; never paste a raw request/response and attempt to redact it later.
|
|
4
|
+
|
|
5
|
+
## Context
|
|
6
|
+
|
|
7
|
+
- Observation date:
|
|
8
|
+
- Source client: native / web / CLI
|
|
9
|
+
- Observation method: static / runtime
|
|
10
|
+
- Evidence level: client-observed / account-verified / unverified
|
|
11
|
+
- App/web/CLI version, APK hash and signature-verification reference:
|
|
12
|
+
- Android API level, image type, ABI and proxy version (runtime):
|
|
13
|
+
- Stock APK or exact instrumentation/trust-store changes:
|
|
14
|
+
- User action and authorized scope (no account identity):
|
|
15
|
+
- Provider/capability variant (no NHS number, practice or person identifiers):
|
|
16
|
+
|
|
17
|
+
## Contract
|
|
18
|
+
|
|
19
|
+
| Property | Observation |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| Origin | Fixed origin only |
|
|
22
|
+
| Method and path | Replace all resource/patient identifiers with `:id` / `:nhsNumber` |
|
|
23
|
+
| Query parameters | Names, types, optionality and bounds; no observed values |
|
|
24
|
+
| Request body | Field names and types; original synthetic example if needed |
|
|
25
|
+
| Authentication | Cookie/header names; observed presence vs established requirement |
|
|
26
|
+
| Prerequisites | Session validation, GP uplift, bearer renewal, capability gate |
|
|
27
|
+
| Cookie rules | Domain, path, Secure, HttpOnly, SameSite and expiry semantics |
|
|
28
|
+
| Response | Status, media type, field types and null/empty/error containers |
|
|
29
|
+
| Pagination | Cursor/index/count contracts; never copy a live cursor |
|
|
30
|
+
| Rotation | Which values changed; no values or token hashes |
|
|
31
|
+
| Side effects | All requests caused by the UI action, including read-status changes |
|
|
32
|
+
| Failure/retry | What was actually observed; distinguish hypotheses |
|
|
33
|
+
|
|
34
|
+
## Verification and follow-through
|
|
35
|
+
|
|
36
|
+
- Non-sensitive static symbol/call-site reference:
|
|
37
|
+
- Runtime outcome and conditions:
|
|
38
|
+
- Native/web result vs CLI verification status:
|
|
39
|
+
- Unresolved questions and unsupported variants:
|
|
40
|
+
- Synthetic fixture/test references:
|
|
41
|
+
- Adapter and documentation changes:
|
|
42
|
+
- Confirm that notes contain no live identifiers, secrets, clinical values, raw captures or account-linked timestamps.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# API research and evidence
|
|
2
|
+
|
|
3
|
+
Inspected on 2026-09-06. This document is an original description of observed interfaces, not redistributed NHS source. Downloaded packages, public JavaScript and disassembly remain outside the repository. No patient account was queried during this work.
|
|
4
|
+
|
|
5
|
+
## Evidence levels
|
|
6
|
+
|
|
7
|
+
- **Client-observed**: a shipped client method and/or its call site establishes the contract. This does not establish entitlement, current server behaviour, or success for an account.
|
|
8
|
+
- **Account-verified**: explicitly authorized live authentication/read with a recorded date, client version and redacted outcome. Capability discovery and current medicines have this designation for the CLI checks recorded below; other new read adapters remain client-observed.
|
|
9
|
+
- **Unverified**: inference, library-only evidence, or an incomplete contract. Do not turn it into a working command by guessing.
|
|
10
|
+
|
|
11
|
+
Synthetic tests check our implementation against these contracts, not the NHS server. Capability configuration can differ by practice, supplier, account and rollout.
|
|
12
|
+
|
|
13
|
+
## Android provenance and integrity
|
|
14
|
+
|
|
15
|
+
The package was fetched from [APKPure's NHS App listing](https://apkpure.com/nhs-app/com.nhs.online.nhsonline) using its latest XAPK download endpoint. That URL is mutable; use the hashes below to identify this inspection. This is a third-party distribution, not a direct Google Play acquisition.
|
|
16
|
+
|
|
17
|
+
| Property | Observation |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| Package | `com.nhs.online.nhsonline` |
|
|
20
|
+
| Manifest version | `6.4.2`, code `44150` |
|
|
21
|
+
| SDK | minimum 26, target 36 |
|
|
22
|
+
| Container | 13,175,738 bytes; base APK and 19 split APKs |
|
|
23
|
+
| XAPK SHA-256 | `b298d993a6a185e4ae29e525edf95c86515ca73f861687bd72ace18c3db2f2cb` |
|
|
24
|
+
| Base APK SHA-256 | `a942d06796ce0a95dc1c0de94c3bf292582b12fbf67daa353cbdb7c9ec5a7688` |
|
|
25
|
+
| Signer certificate SHA-256 | `154851accf1efe0919abb77db883b0bd7f6938f97a7fd2859c8bc8c47bac9559` |
|
|
26
|
+
| Signer certificate SHA-1 | `37a79c75227ef5c2344d7de54dc8858a0b60cc35` |
|
|
27
|
+
|
|
28
|
+
Using Androguard 4.1.3 to parse signing blocks and Python cryptography to verify signatures, all 20 APKs passed APK Signature Scheme v2 signature and content-digest verification. The signer used algorithm `0x0104` (RSA PKCS#1 v1.5 with SHA-512). Verification checked the signed-data signature, equality of the signer key and certificate key, and recomputed the specified 1 MiB chunk digests over the pre-signing-block bytes, central directory and EOCD with its central-directory offset adjusted to the signing-block start. The signed aggregate digest matched for every APK.
|
|
29
|
+
|
|
30
|
+
The certificate SHA-1 also matches an APKPure release listing. These checks establish integrity under that signing key, not independent proof of NHS/Google Play provenance. No modified APK was installed, no signing identity was forged, and no runtime bypass was attempted. See the [Android v2 signature specification](https://source.android.com/docs/security/features/apksigning/v2).
|
|
31
|
+
|
|
32
|
+
### Authentication and transport references
|
|
33
|
+
|
|
34
|
+
Names below are obfuscated symbols in the inspected base DEX, under `com/nhs/online/nhsonline`; they are version-specific references.
|
|
35
|
+
|
|
36
|
+
| Mechanism | Application evidence and limits |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| OAuth/PKCE | Web authorization requests use authorization code, S256 challenge, nonce, state, `nhs-online`, the app callback and GP registration scopes. The app session creation exchanges code, verifier and nonce server-side. The CLI follows this web flow; it does not pretend to possess a native private key. |
|
|
39
|
+
| Remembered device | The public login client sends `remember_my_device` cookie material in the sign-in body's `rmd_token`; responses may independently return a token or `INVALID`. SMS verification is followed by an explicit remember-device operation. This is distinct from app-session persistence. |
|
|
40
|
+
| FIDO/biometrics | `zk` constructs `/authRequest`; `zk.a(String,String,BiometricPrompt.CryptoObject,Context)` requests a challenge and uses a biometric-provided Signature and stored key pair for a UAF response. `rl.e(...)` constructs and submits a signed registration response, checking `SUCCESS`. Production configuration in `zx2.<clinit>` names `uaf.login.nhs.uk`; test/stub variants also exist. This is application flow evidence, not merely FIDO library presence. |
|
|
41
|
+
| Keystore | `k01` configures `AndroidKeyStore`, `secp256r1`, `SHA256withECDSA`; `k01.e(String,boolean)` creates a signing key with SHA-256/384/512 digests and forwards its boolean to `setUserAuthenticationRequired`. `cm.a(...)` calls key generation; `cm.g(...)` retrieves a private key and initializes the signer. Alias prefix: `com.nhs.online.nhsonline.fidouafclient.keystore.key`. Do not claim every generated key unconditionally requires biometrics. |
|
|
42
|
+
| Key attestation | On API 28+, `k01.e` sets a 16-byte zero attestation challenge and biometric-enrollment invalidation. That setter alone does not prove server-enforced attestation or Play Integrity checks. Enforcement was not established. |
|
|
43
|
+
| Cleartext/TLS | `res/xml/network_security_config.xml` declares `cleartextTrafficPermitted="false"` and no pin set. TLS verification stays enabled in the CLI. |
|
|
44
|
+
| Certificate pinning | The relocated OkHttp pinner `yu` contains ordinary pin checking; `yu.<clinit>` builds an empty default through `yu$a`. Inspection of references to that builder found no application call adding pins. Library presence is not evidence of active application pinning. Runtime/native/other-SDK pins have not been exhaustively ruled out. |
|
|
45
|
+
|
|
46
|
+
Official background: [OIDC flow](https://nhsconnect.github.io/nhslogin/oidc-login-flow/), [session management](https://nhsconnect.github.io/nhslogin/session-management/), [FIDO](https://nhsconnect.github.io/nhslogin/fido/), and [managing remembered devices](https://help.login.nhs.uk/manage/devices). These partner interfaces do not make NHS App private routes a supported third-party API.
|
|
47
|
+
|
|
48
|
+
## Public web-client evidence
|
|
49
|
+
|
|
50
|
+
The [public configuration](https://www.nhsapp.service.nhs.uk/v4-76-3/config.json) reported `v4.76.3`, commit `bfaf34f72f`, with minimum Android/iOS version `6.3.0`. The inspected app bundle was `app.0b0976c0.js`. Generated methods establish paths and parameters; Vuex actions/consumers establish response fields and capability choices. `src/config.mjs` centralizes compatibility headers; update it with fresh evidence rather than copying an old native-version string. The CLI uses web metadata, not the previous `ios 6.2.0` value.
|
|
51
|
+
|
|
52
|
+
The [public login client](https://access.login.nhs.uk/) served `main-CZQNUBVB.js` and `chunk-QBZCHNOG.js`. The latter's `userSignIn`, `otp`, `rmd`, `triggerOtp` and session-service methods establish current request bodies. The main bundle's `userSignInSuccess$` handles `VERIFIED` as the MFA state, with mobile, landline and TOTP choices. `AUTHENTICATED` follows the returned redirect. TOTP/landline/registration steps are not implemented in the CLI and return `auth_required`; unknown states fail with `auth_flow_changed`.
|
|
53
|
+
|
|
54
|
+
The current OTP request includes `otp_type: "mobile"`. Remember-device selection happens in the subsequent POST with `remember_my_device: "true"`. The response body token and cookie are kept separately; the cookie is preferred for later sign-in, matching `getRememberMyDevice()` in the client. See the NHS's [2026 release notes](https://digital.nhs.uk/services/nhs-login/nhs-login-for-partners-and-developers/service-updates-and-releases/releases-2026) for the moved remember-device step. The user completed CLI sign-in on 2026-09-06; remembered-device material was saved. The complete raw authentication exchange was not captured, so individual MFA/renewal branches remain supported by client evidence and synthetic tests.
|
|
55
|
+
|
|
56
|
+
## Origins and session mechanisms
|
|
57
|
+
|
|
58
|
+
| Name | Origin/base | Purpose |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| App | `https://www.nhsapp.service.nhs.uk` | Web app and exact OAuth callbacks |
|
|
61
|
+
| App API | `https://api.nhsapp.service.nhs.uk` | App cookies, CSRF and patient context |
|
|
62
|
+
| Login API | `https://api.login.nhs.uk` | Credential and SMS steps |
|
|
63
|
+
| Authorization | `https://auth.login.nhs.uk` | OIDC authorize, authcode |
|
|
64
|
+
| Login UI | `https://access.login.nhs.uk` | Authorization-cookie and browser login |
|
|
65
|
+
| GP Connect | `https://gpconnectapi.nhsapp.service.nhs.uk/api` | Own-account document references/content |
|
|
66
|
+
|
|
67
|
+
App authentication uses URL-scoped `NHSO-Session-Id`/`NHSO-Session-Expiry` cookies, `X-CSRF-TOKEN`, and `NHSO-Patient-Id` (patient-session context, not an arbitrary NHS number). The session response supplies `token`, `patientSessionId`, `hasGpSession`, `sessionTimeout`, sometimes `accessToken` and own-account `nhsNumber`. The last value stays in memory for document routing. GP uplift obtains an asserted identity, follows a separate OAuth callback and updates the app session. It is needed before GP operations, including after a fresh login.
|
|
68
|
+
|
|
69
|
+
App session extension prolongs an active session. Access-token renewal obtains a bearer token inside a valid session; it is not an OAuth refresh-token grant. Neither is assumed capable of resurrecting an expired app session. The server's 401 is distinct from permission, throttle, provider and service failures. Remember-device state can reduce interaction during another credential sign-in but cannot guarantee it.
|
|
70
|
+
|
|
71
|
+
## Authentication inventory
|
|
72
|
+
|
|
73
|
+
All routes in this table are **client-observed**, with synthetic validation only.
|
|
74
|
+
|
|
75
|
+
| Origin | Method/path | Parameters and response | Semantics |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| Authorization | GET `/authorize` | code, client, redirect, scope, vtr, nonce, state, S256 challenge; redirects and authorization cookie | Begins login; bounded and destination/state checked |
|
|
78
|
+
| Login API | POST `/login/user-sign-in` | email, password, rmd_token; authentication_state, methods, redirect_uri, rmd_token | Credential authentication |
|
|
79
|
+
| Login API | POST `/login/trigger-otp` | is_login, otp_type | Sends an SMS: only interactive or explicit Messages flow |
|
|
80
|
+
| Login API | POST `/login/otp` | client_id, session_id, otp_code, otp_type; id_token | Verifies challenge |
|
|
81
|
+
| Login API | POST `/login/remember-my-device` | remember_my_device; optional token and Set-Cookie | Registers/renews remembered device |
|
|
82
|
+
| Authorization | POST `/authcode` | OIDC cookie parameters; ID-token Authorization; Location | Code exchange; exact callback checked |
|
|
83
|
+
| App API | POST `/v1/session` | authCode, codeVerifier, redirectUrl, nonce, referrers; session fields/cookies | Creates app session |
|
|
84
|
+
| App API | GET `/v1/session` | app context; session fields/cookies | Validates/rotates existing session |
|
|
85
|
+
| App API | POST `/v1/session/extend` | app context | Extends only active session |
|
|
86
|
+
| App API | DELETE `/v1/session` | app context | Ends server session, best effort at logout |
|
|
87
|
+
| App API | POST `/v1/patient/asserted-login-identity` | IntendedRelyingPartyUrl; token | Creates assertion for GP uplift |
|
|
88
|
+
| App API | PUT `/v1/session/gp-session-on-demand` | authCode, redirectUrl, referrers; session fields | Establishes GP session after asserted-identity OAuth |
|
|
89
|
+
| App API | POST `/v1/patient/authorization/access-token/refresh` | app context; `{token}` | Renews service bearer token |
|
|
90
|
+
|
|
91
|
+
## Read and prescription inventory
|
|
92
|
+
|
|
93
|
+
Unless specified, origin is App API and authentication is app cookie + CSRF + patient context. **GP** adds GP uplift; **bearer** adds access-token Authorization. The CLI discovers `GET /v1/patient/journey-configuration`, consuming `{journeys}` before gated services. Routes are **client-observed** unless account verification is explicitly recorded. CLI 0.1.0 (web compatibility 4.76.3) passed live capability discovery and current-medicines reads on 2026-09-06, with separate processes reusing authentication. These two reads are **account-verified**; capability advertisement does not verify the other endpoints. Unknown provider versions are explicitly unsupported.
|
|
94
|
+
|
|
95
|
+
| Command/contract | Method/path and parameters | Gate/auth | Observed response and effects |
|
|
96
|
+
| --- | --- | --- | --- |
|
|
97
|
+
| Capabilities | GET `/v1/patient/journey-configuration` | app | `{journeys}`; read; account-verified 2026-09-06 |
|
|
98
|
+
| Medicines | GET `/v1/patient/courses` | prescriptions.provider=im1; GP | courses with id/name/details/requestable, specialRequestNecessity; read; account-verified 2026-09-06 |
|
|
99
|
+
| Prescription history | GET `/v1/patient/prescriptions?fromDate=<ISO>` | same; GP | prescriptions[], courses[]; read, default six months |
|
|
100
|
+
| Existing ordering | POST `/v1/patient/prescriptions` with CourseIds, SpecialRequest | same; GP; exact scoped user approval | 201 acknowledgement; clinical write, never automatically retried |
|
|
101
|
+
| Records | GET `/v1/patient/my-record` | medicalRecord.version=1/2; GP | response with access flags and sections; read |
|
|
102
|
+
| Results list | Same record endpoint, testResults section | classic IM1 results; GP | testResults; read; version 3 is unsupported |
|
|
103
|
+
| Result detail | GET `/v1/patient/test-result?testResultId=<id>` | same; GP | `{response}`; read |
|
|
104
|
+
| Historic results | GET `/v1/patient/historic-test-results/:year` | same; GP | `{response}`; read |
|
|
105
|
+
| Appointments | GET `/v1/patient/appointments` | appointments.provider=im1; GP | upcomingAppointments[], pastAppointments[], availability flags; read |
|
|
106
|
+
| Available slots | GET `/v1/patient/appointment-slots` | same; GP | slots[], booking guidance, reason necessity, contact fields; read only |
|
|
107
|
+
| GP inbox | GET `/v1/patient/messages` | im1Messaging.isEnabled; GP | messageSummaries[]; read |
|
|
108
|
+
| GP message | GET `/v1/patient/messages/:id` | same; GP | messageDetails; no separate updateReadStatus PUT |
|
|
109
|
+
| NHS inbox | GET `/v2/api/users/me/messages?index=0&count=20` | messaging; bearer | messages[], canLoadMore; bounded explicit pages |
|
|
110
|
+
| NHS message | GET `/v1/api/users/me/messages/:id` | same; bearer | message object; no read-status PATCH |
|
|
111
|
+
| Profile | GET `/v1/patient/demographics` | app; server enforces access | response with demographic/GP fields; read |
|
|
112
|
+
| Pharmacy | GET `/v1/patient/nominated-pharmacy` | nominatedPharmacy | pharmacyDetails and metadata; read |
|
|
113
|
+
| Document metadata | GP Connect GET `/v1/AccessDocuments/Patient/:nhsNumber/DocumentReference` | documents; GP; own account only | patientDocuments[] with id, description, size, etc.; read |
|
|
114
|
+
| Document download | GP Connect GET `/v1/AccessDocuments/Download/:id`, `Prefer: statuscode=200` | same; ID must be in current list | content (base64), contentType; read, written only with explicit output |
|
|
115
|
+
|
|
116
|
+
An empty list is accepted only with the expected response container. Per-section access/error flags remain in output; they are not interpreted as reassuring clinical absence. Response bodies have an 8 MiB bound, including base64 document content. The CLI does not invent pagination for routes without observed pagination parameters.
|
|
117
|
+
|
|
118
|
+
### Observed but not implemented
|
|
119
|
+
|
|
120
|
+
The generated IM1 document methods include POST `/v1/documents/:documentIdentifier` with `type` and `name` to obtain content, and a `/download` variant returning bytes. These POSTs retrieve documents; HTTP method alone does not make them clinical writes. They remain unimplemented because provider-specific identifiers and contracts need fuller mapping. GP Connect prescriptions, version-3 results, linked/proxy accounts, third-party portals, and new appointment/message/pharmacy write workflows are excluded. The public bundle also contains service origins beyond this CLI's allowlist; their presence is not permission to use them.
|
|
121
|
+
|
|
122
|
+
## Updating evidence and live checks
|
|
123
|
+
|
|
124
|
+
For runtime mapping, follow [Android network inspection](android-network-inspection.md) and record original notes using the [observation template](api-observation-template.md). Track both the source client and static/runtime observation method. Native-app success does not, by itself, verify the CLI contract.
|
|
125
|
+
|
|
126
|
+
Re-record date, hashes, signer verification and source symbols when upgrading. Keep public-client artifacts outside Git, inspect call sites rather than just string matches, and add synthetic tests before wiring a route. Review service-journey defaults and alternative providers; `null` IM1 results version selects the classic path, while version `3` selects a separate path this CLI does not implement.
|
|
127
|
+
|
|
128
|
+
Live smoke checks are manual, explicitly requested and limited to named authentication/read commands. Use `nhs auth status`, then the requested login/read operation with `--no-prompt` where appropriate. Record only version, command category, date and success/error code. Do not save patient responses, tokens, screenshots, HARs or credentials as test evidence. Never include a prescription submission in a smoke test.
|