310 lines
21 KiB
Markdown
310 lines
21 KiB
Markdown
---
|
|||
|
|
phase: 10-key-material-hardening
|
||
|
|
plan: 02
|
||
|
|
type: execute
|
||
|
|
wave: 2
|
||
|
|
depends_on: ["10-01"]
|
||
|
|
files_modified:
|
||
|
|
- scripts/security/rpc-exposure-probe.sh
|
||
|
|
- docs/security/KEY-01-ON-NODE-VERIFICATION.md
|
||
|
|
autonomous: false
|
||
|
|
requirements: [KEY-01, KEY-04]
|
||
|
|
|
||
|
|
must_haves:
|
||
|
|
truths:
|
||
|
|
- "The unauthenticated RPC surface's real reachability from the LAN, from Tor and from the FIPS mesh ULA is measured, not assumed (KEY-04 / audit C-6)"
|
||
|
|
- "On a real node running 10-01's gate, an unauthenticated seed.restore with attacker-supplied words is refused and the node's node_key and nostr_secret are byte-identical afterwards"
|
||
|
|
- "A fresh, never-onboarded node still completes the whole onboarding wizard with 10-01's gate in place — the anti-brick proof for D-03's signal set"
|
||
|
|
- "The probe is safe by construction: it can never call an identity-mutating method unless the operator passes an explicit destructive flag"
|
||
|
|
artifacts:
|
||
|
|
- path: "scripts/security/rpc-exposure-probe.sh"
|
||
|
|
provides: "Repeatable, read-only-by-default RPC exposure probe for C-6 and the KEY-01 refusal check"
|
||
|
|
contains: "DESTRUCTIVE"
|
||
|
|
min_lines: 60
|
||
|
|
- path: "docs/security/KEY-01-ON-NODE-VERIFICATION.md"
|
||
|
|
provides: "The recorded on-node evidence that turns the audit's UNVERIFIED C-6 into a verified result"
|
||
|
|
contains: "C-6"
|
||
|
|
key_links:
|
||
|
|
- from: "scripts/security/rpc-exposure-probe.sh"
|
||
|
|
to: "docs/security/KEY-01-ON-NODE-VERIFICATION.md"
|
||
|
|
via: "probe output is pasted verbatim into the evidence record, per node and per transport"
|
||
|
|
pattern: "rpc-exposure-probe"
|
||
|
|
---
|
||
|
|
|
||
|
|
<objective>
|
||
|
|
Turn the audit's UNVERIFIED C-6 into recorded evidence, and prove on real hardware that 10-01's
|
||
|
|
gate does what it claims without bricking a fresh node.
|
||
|
|
|
||
|
|
Purpose: 10-01 is verified by unit tests against temp directories. Two things unit tests cannot
|
||
|
|
answer, and both are release-gating for D-10's OTA: (1) is the unauthenticated RPC surface
|
||
|
|
actually reachable from another machine on the LAN, over Tor, and over the FIPS mesh ULA — the
|
||
|
|
exposure surface F-01 depends on; (2) does a genuinely fresh node still get through onboarding
|
||
|
|
with the gate in place. Correctness trap 1 says a naive fix bricks first boot on every new node;
|
||
|
|
this plan is where that claim gets tested rather than argued.
|
||
|
|
|
||
|
|
Output: a reusable, read-only-by-default probe script and a written evidence record.
|
||
|
|
|
||
|
|
Sequencing (per the phase brief): C-6 is runnable the moment 10-01 lands — it does not wait for
|
||
|
|
KEY-02 or KEY-03. This plan depends on 10-01 only because its refusal check needs 10-01's gate
|
||
|
|
running on the target.
|
||
|
|
</objective>
|
||
|
|
|
||
|
|
<execution_context>
|
||
|
|
@$HOME/.claude/gsd-core/workflows/execute-plan.md
|
||
|
|
@$HOME/.claude/gsd-core/templates/summary.md
|
||
|
|
</execution_context>
|
||
|
|
|
||
|
|
<context>
|
||
|
|
@.planning/PROJECT.md
|
||
|
|
@.planning/STATE.md
|
||
|
|
@.planning/phases/10-key-material-hardening/10-CONTEXT.md
|
||
|
|
@.planning/phases/10-key-material-hardening/10-01-SUMMARY.md
|
||
|
|
@CLAUDE.md
|
||
|
|
</context>
|
||
|
|
|
||
|
|
<probe_method_correction>
|
||
|
|
**The audit's C-6 command does not measure what it claims. Fix it here rather than copying it.**
|
||
|
|
|
||
|
|
`docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md:814-824` probes with `seed.status`. But
|
||
|
|
`seed.status` is **not** in `UNAUTHENTICATED_METHODS` (`core/archipelago/src/api/rpc/middleware.rs:5-38`
|
||
|
|
lists `seed.generate`, `seed.verify`, `seed.restore`, `seed.save-encrypted` — not `seed.status`).
|
||
|
|
An unauthenticated `seed.status` therefore returns **401 by design**, so the audit's "Fail: 200"
|
||
|
|
criterion can never fire and the probe would report the surface as closed while F-01's actual
|
||
|
|
door stands open.
|
||
|
|
|
||
|
|
The probe this plan builds measures both facts separately:
|
||
|
|
|
||
|
|
- **Exposure:** `auth.isOnboardingComplete` — genuinely unauthenticated (`middleware.rs:9`),
|
||
|
|
read-only, no side effects. A `200` proves the unauthenticated RPC surface is reachable from
|
||
|
|
that vantage point. This is the honest C-6 signal.
|
||
|
|
- **Session enforcement:** `seed.status` — a `401` proves the session check is working for
|
||
|
|
non-allowlisted methods. A `200` here would be a far worse finding than C-6 and must stop the
|
||
|
|
plan.
|
||
|
|
|
||
|
|
Record this correction in the evidence document so the next reader does not re-derive it.
|
||
|
|
</probe_method_correction>
|
||
|
|
|
||
|
|
<tasks>
|
||
|
|
|
||
|
|
<task type="auto">
|
||
|
|
<name>Task 1: Build the read-only-by-default RPC exposure probe</name>
|
||
|
|
<files>scripts/security/rpc-exposure-probe.sh</files>
|
||
|
|
<read_first>
|
||
|
|
- core/archipelago/src/api/rpc/middleware.rs (lines 5-38 — the authoritative unauthenticated method list the probe is written against)
|
||
|
|
- core/archipelago/src/api/rpc/mod.rs (lines 245-300 and 505-520 — what a 401 and a 429 look like on the wire)
|
||
|
|
- docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md (section 6, checklist items C-6 and C-5 — C-5's "handle the output as key material" warning applies to this script too)
|
||
|
|
- image-recipe/configs/nginx-archipelago.conf (the :80 default_server and the /rpc/v1 and /rpc/ proxy blocks — the exact paths the probe must try)
|
||
|
|
- scripts/iso-smoke-test.sh (house style for a repo probe script: arg parsing, coloured pass/fail, exit codes)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
Create `scripts/security/rpc-exposure-probe.sh`, executable, `set -euo pipefail`.
|
||
|
|
|
||
|
|
Usage: `rpc-exposure-probe.sh --target <host-or-onion-or-ULA> [--scheme http|https] [--port N]
|
||
|
|
[--label <name>] [--destructive]`.
|
||
|
|
|
||
|
|
Default (read-only) mode issues exactly three POSTs to `/rpc/v1` and one to `/rpc/`, each with a
|
||
|
|
bounded `--max-time 15`, and prints a one-line PASS/FAIL verdict per check plus the raw HTTP
|
||
|
|
status:
|
||
|
|
|
||
|
|
1. `health` — liveness of the endpoint from this vantage point.
|
||
|
|
2. `auth.isOnboardingComplete` — the exposure signal. `200` means the unauthenticated RPC
|
||
|
|
surface is reachable from here. Report it as `EXPOSED` rather than `FAIL`, because on the LAN
|
||
|
|
this is currently expected and the point of the probe is to record the surface, not to assert
|
||
|
|
it is closed.
|
||
|
|
3. `seed.status` — the session-enforcement control. Anything other than `401` is reported as
|
||
|
|
`CRITICAL` and makes the script exit non-zero.
|
||
|
|
|
||
|
|
The read-only mode must be structurally incapable of mutating identity: build the request method
|
||
|
|
from a fixed `READONLY_METHODS` array and never from an argument, and put every mutating request
|
||
|
|
inside a single `if [ "$DESTRUCTIVE" = "1" ]` branch.
|
||
|
|
|
||
|
|
`--destructive` mode adds the KEY-01 refusal check and prints a red banner stating that it must
|
||
|
|
only be run against a disposable node. It POSTs `seed.restore` with a fixed, well-formed,
|
||
|
|
publicly-known 24-word BIP-39 test mnemonic (use the BIP-39 all-`abandon` + `art` 24-word vector,
|
||
|
|
which is published test data, so no real key material is ever handled — this is the deliberate
|
||
|
|
difference from the audit's C-5, which mints real mnemonics). It asserts the JSON response
|
||
|
|
carries an error whose message begins with the refusal prefix `Not supported:` emitted by
|
||
|
|
10-01's gate, and it exits non-zero on a `200`-with-result.
|
||
|
|
|
||
|
|
The before/after byte-identity check is NOT done by this script (it has no node-local file
|
||
|
|
access); the script prints the two `sha256sum` commands the operator must run on the node
|
||
|
|
itself, so they land in the transcript alongside the probe output.
|
||
|
|
|
||
|
|
Add a `--help` that prints the usage and the safety rules. Do not embed any credential, node
|
||
|
|
address or password in the script.
|
||
|
|
</action>
|
||
|
|
<verify>
|
||
|
|
<automated>bash -n scripts/security/rpc-exposure-probe.sh && bash scripts/security/rpc-exposure-probe.sh --help && test -x scripts/security/rpc-exposure-probe.sh</automated>
|
||
|
|
</verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- `bash -n scripts/security/rpc-exposure-probe.sh` exits 0 and the file is mode 755.
|
||
|
|
- `bash scripts/security/rpc-exposure-probe.sh --help` prints usage and exits 0.
|
||
|
|
- `grep -c 'READONLY_METHODS' scripts/security/rpc-exposure-probe.sh` is at least 1 and `grep -c 'DESTRUCTIVE' scripts/security/rpc-exposure-probe.sh` is at least 2 (the guard and the flag parse).
|
||
|
|
- If `shellcheck` is available on the host, `shellcheck -S error scripts/security/rpc-exposure-probe.sh` is clean; if it is not available, the SUMMARY records that it was unavailable rather than silently skipping.
|
||
|
|
- The script contains no host address, onion address, username or password.
|
||
|
|
</acceptance_criteria>
|
||
|
|
<done>A repeatable probe exists that measures exposure and session enforcement separately, cannot mutate identity without an explicit flag, and handles no real key material.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 2: Measure C-6 on real nodes and prove the refusal on a disposable one</name>
|
||
|
|
<precondition>A node running a build that contains 10-01's gate is reachable, and a second machine on the same LAN is available to probe from (probing from the node itself measures loopback, not exposure).</precondition>
|
||
|
|
<files>docs/security/KEY-01-ON-NODE-VERIFICATION.md</files>
|
||
|
|
<read_first>
|
||
|
|
- scripts/security/rpc-exposure-probe.sh (Task 1 output)
|
||
|
|
- .planning/phases/10-key-material-hardening/10-01-SUMMARY.md (the exact refusal message text 10-01 shipped)
|
||
|
|
- docs/security/ENTROPY-SEED-AUDIT-2026-07-31.md (section 6, C-6 — and the probe_method_correction section of this plan, which supersedes its command)
|
||
|
|
- CLAUDE.md (dev-pair policy: archi-dev-box + x250-dev are deployed and verified before any OTA)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
Claude builds the deployable artefact and hands the operator an exact, copy-pasteable sequence;
|
||
|
|
Claude does not ask the operator to do anything a CLI can do from here. Deploying 10-01's binary
|
||
|
|
to the dev pair is the operator's existing `scripts/deploy-to-target.sh` flow and is NOT planned
|
||
|
|
here (the phase brief excludes deployment). Claude's job in this task is to prepare the command
|
||
|
|
sequence, then record the operator's output verbatim into
|
||
|
|
`docs/security/KEY-01-ON-NODE-VERIFICATION.md` under a `## C-6 — unauthenticated RPC
|
||
|
|
reachability` heading with one subsection per node and per transport.
|
||
|
|
</action>
|
||
|
|
<what-built>A read-only-by-default probe that separates "is the unauthenticated surface reachable from here" from "is session enforcement working", plus a destructive refusal check that uses only published BIP-39 test vectors.</what-built>
|
||
|
|
<how-to-verify>
|
||
|
|
1. From a **second machine on the same LAN** (not the node), run:
|
||
|
|
`bash scripts/security/rpc-exposure-probe.sh --target <node-lan-ip> --scheme http --port 80 --label lan`
|
||
|
|
Record the three verdict lines. `auth.isOnboardingComplete` returning `200` is the C-6
|
||
|
|
exposure result; `seed.status` returning anything but `401` is a stop-the-plan finding.
|
||
|
|
2. Repeat over Tor against the node's onion address:
|
||
|
|
`torsocks bash scripts/security/rpc-exposure-probe.sh --target <onion> --scheme http --port 80 --label tor`
|
||
|
|
3. Repeat over the FIPS mesh ULA from a peer node:
|
||
|
|
`bash scripts/security/rpc-exposure-probe.sh --target <fips-ula> --scheme http --port 80 --label mesh`
|
||
|
|
(the peer listener allows `/rpc/v1` — `core/archipelago/src/server.rs:1270-1296`, asserted
|
||
|
|
at `:2080` — so a `200` here confirms the mesh half of F-01's reachability claim).
|
||
|
|
4. On a **disposable** node only — never one in real use — first capture the baseline on the
|
||
|
|
node: `sudo sha256sum /var/lib/archipelago/identity/node_key /var/lib/archipelago/identity/nostr_secret`
|
||
|
|
5. From the second machine: `bash scripts/security/rpc-exposure-probe.sh --target <disposable-node-ip> --destructive --label refusal`
|
||
|
|
6. On the disposable node, re-run the same `sha256sum` command. The two digests must be identical.
|
||
|
|
7. Paste every command and its full output into the checkpoint response.
|
||
|
|
</how-to-verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- Three transports (LAN, Tor, mesh) each have a recorded `auth.isOnboardingComplete` status code in the evidence document. An unreachable transport is recorded as `UNREACHABLE` with the error, never omitted.
|
||
|
|
- `seed.status` returned `401` on every transport tested. Any other code halts the phase and is raised as a blocker.
|
||
|
|
- The destructive run returned an error whose message begins with the refusal prefix from 10-01, and the two `sha256sum` outputs from steps 4 and 6 match character for character.
|
||
|
|
- `docs/security/KEY-01-ON-NODE-VERIFICATION.md` contains the probe-method correction (why `auth.isOnboardingComplete` replaced the audit's `seed.status` as the C-6 signal) and marks audit item C-6 as VERIFIED with a date and the node labels used.
|
||
|
|
</acceptance_criteria>
|
||
|
|
<resume-signal>Paste the probe output for each transport plus the two sha256sum lines, then type "approved" — or describe what failed.</resume-signal>
|
||
|
|
<done>Audit item C-6 is no longer UNVERIFIED: the exposure surface is measured per transport and the KEY-01 refusal is proven on real hardware with unchanged identity bytes.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
<task type="checkpoint:human-verify" gate="blocking">
|
||
|
|
<name>Task 3: Prove a fresh node still onboards end-to-end with the gate in place</name>
|
||
|
|
<precondition>An un-onboarded Archipelago instance is available — either a freshly flashed node, or a second daemon instance started with a clean ARCHIPELAGO_DATA_DIR (shape A of `.planning/todos/pending/2026-08-01-archi-dev-box-as-fresh-test-node-without-iso.md`).</precondition>
|
||
|
|
<files>docs/security/KEY-01-ON-NODE-VERIFICATION.md</files>
|
||
|
|
<read_first>
|
||
|
|
- .planning/todos/pending/2026-08-01-archi-dev-box-as-fresh-test-node-without-iso.md (shape A is the intended harness — a second instance boots un-onboarded, which is exactly the state the gate must let through)
|
||
|
|
- neode-ui/src/router/index.ts (lines 20-80 — the onboarding route order the walkthrough follows)
|
||
|
|
- core/archipelago/src/api/rpc/onboarding_gate.rs (10-01 output — the three signals and when each flips)
|
||
|
|
- docs/security/KEY-01-ON-NODE-VERIFICATION.md (Task 2 output — append to it)
|
||
|
|
</read_first>
|
||
|
|
<action>
|
||
|
|
This is the anti-brick proof for correctness traps 1 and 2, and it is the single most important
|
||
|
|
non-regression check in the phase: if the gate's signal set is wrong, every future flashed node
|
||
|
|
is unusable, and no unit test against a temp directory can catch a wrong signal choice in the
|
||
|
|
live boot sequence (the whole point of the scoping correction is that `Server::new` writes a
|
||
|
|
`node_key` before the user ever sees the wizard).
|
||
|
|
|
||
|
|
Claude prepares the walkthrough and, after the operator responds, records the result into
|
||
|
|
`docs/security/KEY-01-ON-NODE-VERIFICATION.md` under `## Fresh-node onboarding non-regression`,
|
||
|
|
including the daemon log lines around each seed RPC and the wall-clock time from first
|
||
|
|
`seed.generate` to `auth.onboardingComplete`.
|
||
|
|
</action>
|
||
|
|
<what-built>10-01's gate, running on a real un-onboarded instance, on the exact code path a newly flashed node takes.</what-built>
|
||
|
|
<how-to-verify>
|
||
|
|
1. Start (or flash) an un-onboarded instance and confirm it is genuinely fresh:
|
||
|
|
`ls -l <data-dir>/user.json <data-dir>/onboarding.json <data-dir>/identity/master_seed.enc`
|
||
|
|
— all three must be absent. Note that `<data-dir>/identity/node_key` WILL exist after boot;
|
||
|
|
that is expected and is precisely the condition the gate must tolerate.
|
||
|
|
2. Open the UI and walk the wizard end to end: intro → options → path → seed →
|
||
|
|
seed-verify → did → identity → backup → verify → done, then set the password on the login
|
||
|
|
screen. Do not skip the seed step.
|
||
|
|
3. While on the seed screen, force a retry: reload the page once and confirm the SAME 24
|
||
|
|
words are shown (the idempotent fast path must still work below the gate).
|
||
|
|
4. Confirm no `Not supported:` error and no `Rate limit exceeded` error appears at any point.
|
||
|
|
5. After completion, confirm the node is provisioned: `ls -l <data-dir>/user.json` exists.
|
||
|
|
6. Now confirm the door closed behind you — from a second machine, run
|
||
|
|
`bash scripts/security/rpc-exposure-probe.sh --target <instance> --destructive --label post-onboarding`
|
||
|
|
and confirm the refusal.
|
||
|
|
7. Paste the wizard outcome, the step-3 result, and the step-6 output.
|
||
|
|
</how-to-verify>
|
||
|
|
<acceptance_criteria>
|
||
|
|
- The wizard completed without any `Not supported:` or `Rate limit exceeded` error, and the operator confirms the same 24 words survived the reload in step 3.
|
||
|
|
- Step 5 shows `user.json` present, and step 6's refusal confirms the same instance now refuses `seed.restore`.
|
||
|
|
- `docs/security/KEY-01-ON-NODE-VERIFICATION.md` records both halves — onboarding succeeded, then the door closed — with the instance label and date.
|
||
|
|
- If ANY step fails, the executor must NOT patch the gate ad hoc: raise a blocker naming which of the three signals fired early, with the `file:line` and the on-disk state that triggered it.
|
||
|
|
</acceptance_criteria>
|
||
|
|
<resume-signal>Type "approved" with the wizard result and the step-6 probe output, or describe exactly which step failed and what error appeared.</resume-signal>
|
||
|
|
<done>A genuinely fresh instance onboards with the gate in place, and the same instance refuses identity replacement immediately afterwards.</done>
|
||
|
|
</task>
|
||
|
|
|
||
|
|
</tasks>
|
||
|
|
|
||
|
|
<threat_model>
|
||
|
|
## Trust Boundaries
|
||
|
|
|
||
|
|
| Boundary | Description |
|
||
|
|
|----------|-------------|
|
||
|
|
| Operator workstation -> node RPC | The probe crosses the same untrusted LAN an attacker would use; it is the attacker's-eye view by construction. |
|
||
|
|
| Probe script -> node identity | `--destructive` mode issues a real `seed.restore`. If run against a node in real use with a build that lacks 10-01's gate, it destroys that node's identity. |
|
||
|
|
| Evidence document -> repository | The probe output could carry node addresses, onion addresses and mesh ULAs into a repo that is being prepared for open-source publication. |
|
||
|
|
|
||
|
|
## STRIDE Threat Register
|
||
|
|
|
||
|
|
| Threat ID | Category | Component | Severity | Disposition | Mitigation Plan |
|
||
|
|
|-----------|----------|-----------|----------|-------------|-----------------|
|
||
|
|
| T-10-11 | Tampering | `--destructive` mode run against a production node | high | mitigate | Mutating requests live inside a single explicit-flag branch; the flag prints a red banner; every checkpoint step says "disposable node only"; Task 2 requires a before/after digest so an accident is at least detected |
|
||
|
|
| T-10-12 | Information disclosure | The probe mints or handles real mnemonics (the audit's C-5 does exactly this) | high | mitigate | The refusal check uses only the published BIP-39 all-`abandon`/`art` test vector; the script never generates a mnemonic and never prints one |
|
||
|
|
| T-10-13 | Information disclosure | Node addresses, onion addresses and ULAs committed into `docs/security/` ahead of open-sourcing | medium | mitigate | Evidence records node **labels** (`lan`, `tor`, `mesh`, `dev-box`) and status codes, never raw addresses; Task 1 forbids embedding any address in the script |
|
||
|
|
| T-10-14 | Repudiation | A checkpoint is rubber-stamped without the commands actually being run | medium | mitigate | Each acceptance criterion requires pasted verbatim output including status codes and two matching digests, not a yes/no |
|
||
|
|
| T-10-15 | Denial of service | The probe trips 10-01's new rate limits and reports a false negative | low | accept | The probe issues four requests per run, far under the 10/300s floor; a `429` is reported as its own verdict line rather than being conflated with a refusal |
|
||
|
|
| T-10-SC | Tampering | npm/pip/cargo installs | low | accept | No package-manager install occurs in this plan; `curl`, `sha256sum` and optionally `torsocks`/`shellcheck` are pre-existing host tools. Executor MUST halt and raise a checkpoint if a new dependency appears necessary. |
|
||
|
|
</threat_model>
|
||
|
|
|
||
|
|
<artifacts_this_phase_produces>
|
||
|
|
## Artifacts this plan produces
|
||
|
|
|
||
|
|
**New file:** `scripts/security/rpc-exposure-probe.sh` (mode 755)
|
||
|
|
|
||
|
|
| Symbol | Kind | Contract |
|
||
|
|
|---|---|---|
|
||
|
|
| `--target <host>` | CLI flag | required; host, onion or ULA to probe |
|
||
|
|
| `--scheme http\|https` | CLI flag | default `http` |
|
||
|
|
| `--port <n>` | CLI flag | default `80` |
|
||
|
|
| `--label <name>` | CLI flag | vantage-point label written into the verdict lines |
|
||
|
|
| `--destructive` | CLI flag | enables the single mutating branch (the `seed.restore` refusal check) |
|
||
|
|
| `READONLY_METHODS` | shell array | `health`, `auth.isOnboardingComplete`, `seed.status` — the only methods the default path may call |
|
||
|
|
| exit `0` | contract | all controls behaved as expected |
|
||
|
|
| exit non-zero | contract | `seed.status` returned other than 401, or `--destructive` was not refused |
|
||
|
|
|
||
|
|
**New file:** `docs/security/KEY-01-ON-NODE-VERIFICATION.md` — headings
|
||
|
|
`## C-6 — unauthenticated RPC reachability`, `## Probe-method correction`,
|
||
|
|
`## Fresh-node onboarding non-regression`.
|
||
|
|
</artifacts_this_phase_produces>
|
||
|
|
|
||
|
|
<verification>
|
||
|
|
- `bash -n scripts/security/rpc-exposure-probe.sh` and `--help` both succeed locally.
|
||
|
|
- Both checkpoints resolved with pasted, verbatim command output.
|
||
|
|
- Commit stages only `scripts/security/rpc-exposure-probe.sh` and
|
||
|
|
`docs/security/KEY-01-ON-NODE-VERIFICATION.md` by explicit path — never `git add -A`.
|
||
|
|
</verification>
|
||
|
|
|
||
|
|
<success_criteria>
|
||
|
|
- Audit item C-6 is recorded as VERIFIED with per-transport status codes and the method
|
||
|
|
correction that makes the measurement meaningful.
|
||
|
|
- `seed.status` returns 401 on every transport tested; any other result is escalated, not filed.
|
||
|
|
- The KEY-01 refusal is proven on real hardware with byte-identical identity files before and after.
|
||
|
|
- A fresh instance completes onboarding with the gate in place, then refuses identity replacement.
|
||
|
|
</success_criteria>
|
||
|
|
|
||
|
|
<output>
|
||
|
|
Create `.planning/phases/10-key-material-hardening/10-02-SUMMARY.md` when done, carrying the
|
||
|
|
per-transport status codes, both `sha256sum` outputs, the fresh-node walkthrough result, and an
|
||
|
|
explicit statement of which audit checklist items moved from UNVERIFIED to VERIFIED.
|
||
|
|
</output>
|