Handle client keys, encrypted vault

This commit is contained in:
2026-10-19 23:11:29 +03:00
parent 766059e6d1
commit c879a8101b
11 changed files with 4267 additions and 21 deletions
+410 -9
View File
@@ -266,11 +266,11 @@ No Alpine build, container tunnel, or VM/systemd check is claimed by M1 acceptan
#### Review and repair
| Finding | Iteration | Location and impact | Owner | Resolution and evidence | State |
| --- | --- | --- | --- | --- | --- |
| M1-R1-01 | R1/R2 | `src/domain.rs` constructor can claim verified without a matching applied snapshot; misleading state | repairer | constructor rejects inconsistent Verified; Commit/mismatch demote stale Verified; 13 focused, 22 total tests passed; R2 confirmed | closed |
| M1-R1-02 | R1/R2 | `AGENTS.md` still claims no Rust application exists | coordinator | opening statement updated to limited scaffold; R2 confirmed | closed |
| M1-R1-03 | R1/R2 | `Cargo.lock` existed outside Git index | coordinator | `git add -N Cargo.lock`; `git ls-files --error-unmatch Cargo.lock` succeeds; R2 confirmed; no commit/content staging | closed |
| Finding | Iteration | Location and impact | Owner | Resolution and evidence | State |
| -------- | --------- | ---------------------------------------------------------------------------------------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | ------ |
| M1-R1-01 | R1/R2 | `src/domain.rs` constructor can claim verified without a matching applied snapshot; misleading state | repairer | constructor rejects inconsistent Verified; Commit/mismatch demote stale Verified; 13 focused, 22 total tests passed; R2 confirmed | closed |
| M1-R1-02 | R1/R2 | `AGENTS.md` still claims no Rust application exists | coordinator | opening statement updated to limited scaffold; R2 confirmed | closed |
| M1-R1-03 | R1/R2 | `Cargo.lock` existed outside Git index | coordinator | `git add -N Cargo.lock`; `git ls-files --error-unmatch Cargo.lock` succeeds; R2 confirmed; no commit/content staging | closed |
#### Evidence and handoff
@@ -619,9 +619,409 @@ remain. Stop here; milestone 3 is the next action only on a new request.
### Milestone 3 — Client keys, encrypted vault, and existing-client import
Implement separate vault/client key pairs, recipient encryption, password-protected vault identity, and versioned secret storage. Import from the configured directory on initial setup without deleting originals. Verify key correspondence and classify anomalies; conflicts block reissue until resolved.
Status: accepted (2026-10-19T20:08:32Z; independent R7 review and coordinator gates passed).
Prerequisites: milestones 1 and 2 accepted; pinned native Rust environment and
access to compatible approved cryptographic dependencies or a populated cache.
No root, running WireGuard, systemd, Docker, VM, or production input is needed.
Acceptance: new secrets encrypt without password entry, correct/incorrect password and corruption paths are covered, original files are unchanged, duplicates/conflicts are represented correctly, and persisted application data contains no plaintext client private keys. WireGuard key handling uses approved libraries, not utility subprocesses.
#### Outcome and scope
Deliver library operations for WireGuard client key generation, encrypted vault
initialization, password-free encryption of new records, password-authorized
secret retrieval, and safe reconciliation/import of existing client files.
Reuse M1 identities/settings and M2 server inspection without rewriting originals.
Read SPEC.md Data sources, Encrypted vault, Importing existing clients, Connection
delivery, and Backups and restoration; ADRs 0002 and 0003; CONFIG_FORMAT.md;
DEPENDENCIES.md, DEVELOPMENT.md, and TESTING.md. Preserve the distinction between
vault encryption, server secrets, and an unencrypted output file.
This milestone prepares versioned vault bytes and encrypted import results;
filesystem persistence/Commit and its mandatory backups belong to milestone 4.
Initial setup prepares encryption of valid existing files immediately in the
same setup operation, but must not install a vault, rewrite the server, or bypass
the later persistence gate. Use supplied buffers and an explicit read-only source
adapter; no automatic scan at application startup. TUI/password prompts, delivery
files/QR and first-delivery session orchestration belong to milestones 6 and 7.
No runtime or service operations are introduced.
#### Existing code and integration constraints
- M1 provides `domain::PublicKey`, `Client`, `ClientAddresses`, `Snapshot`,
`ClientStates`, and validated/resolved settings, including the source directory
and data paths. Public keys, not filenames or repeated aliases, identify secrets.
- M2 exposes `wireguard::ConfigDocument::parse`, `render`, `peers`, `sections`,
and `source_slice`, plus explicit add/delete/rename/legacy-metadata edits.
`PeerInspection` provides identity candidates, AllowedIPs, metadata, and issues.
Its raw source may contain secrets; never expose it through import reports.
- `project_client` requires valid attached metadata and a supported address shape.
Do not make successful projection a prerequisite for preserving or reconciling
existing records. Missing metadata, IPv6-only peers, extra routes, and ambiguous
keys must remain inspectable rather than being silently dropped or normalized.
- Managed metadata is already v1: Base64url alias and optional strict UTC creation
string in an attached comment block. Keep that grammar unchanged. The server
alias wins; a filename/public-key fallback is display-only until an explicit
metadata edit. Do not assign import time as an unknown creation date.
- Server parsing is not client parsing: a client file's `[Interface] PrivateKey`
identifies the client, while its `[Peer] PublicKey` normally identifies the
server. Never use that server key as the client's identity.
- Cargo currently contains base64, ipnet, rustix, serde, thiserror, and toml.
Add only the approved crypto crates needed here: age, x25519-dalek, getrandom,
secrecy, and zeroize; tempfile may be a dev-dependency for source-adapter tests.
Verify current official APIs, maintained compatible versions, feature flags,
and secret-type compatibility before choosing them. No custom cipher/KDF,
OpenSSL subprocess, `wg genkey`, unapproved crate, or unrelated upgrade.
#### Context and implementation steps
Run these tasks sequentially with one writer. GPT-6-Sol/medium coordinates;
GPT-6-Luna/high implements, reviews independently, and repairs. The coordinator
supplies scope, owned files, relevant documents, accepted interfaces, checks, and
required report contents. Workers do not accept milestones or edit PLANS.md.
| Task | Owner | Files and deliverable |
| --- | --- | --- |
| M3.1 | implementer | `src/vault.rs`, optional `src/vault/keys.rs`, `src/lib.rs`, Cargo files: secret-safe WireGuard key types, generation and matching |
| M3.2 | implementer | `src/vault/` modules as needed, `docs/VAULT_FORMAT.md`: asymmetric vault setup, password-protected identity, versioned envelopes and load validation |
| M3.3 | implementer | `src/vault/import.rs` or a narrow import module: caller-buffer client parsing, reconciliation reports and encrypted import preparation |
| M3.4 | implementer | read-only configured-directory adapter, `tests/vault_import.rs`, synthetic temporary inputs and README guidance: initial setup/import workflow and failure coverage |
| M3.5 | reviewer, repairer | Independent crypto/use-of-library, import and integrated regression review; bounded fixes and re-review |
| M3.6 | coordinator | Integrated gates, findings/evidence and acceptance/handoff in PLANS.md |
M3.1 wraps private keys/passwords in secret-aware containers with redacted or no
Debug, no implicit Display/Serialize, and explicit exposure only at necessary
cryptographic/encoding boundaries. Generate WireGuard-compatible private keys
using OS randomness and derive their public key using x25519-dalek. Preserve
canonical WireGuard Base64 on output; validate decoded length/encoding on input.
Keep imported private-key bytes intact rather than replacing them with newly
generated keys. Test generation/derivation and matching without leaking values
in assertion failures. Handle random-source errors as recoverable errors.
M3.2 initializes a separate age recipient/identity pair, never reusing a WireGuard
key. Store only the recipient and password-encrypted vault identity in the public
serialized bundle; encrypt each client record to that recipient without requiring
the password. Use the age library's established authenticated encryption and
password protection; do not implement the cryptographic construction manually.
Document exact v1 vault/record formats, component filenames/layout intended for
M4, version handling, public record identifiers, and size limits before encoding
production data. Use serde/TOML or opaque age payloads as appropriate, without
silently introducing a second serializer dependency. Ensure serialization cannot
write plaintext secrets through a generic derive. Reject unknown versions and
truncated/oversized structures with redacted typed errors.
Bind the decrypted payload to its expected client public key and vault context;
derive its public key again and reject mislabelled/swapped records. On unlocking,
verify the decrypted vault identity matches the declared recipient. Do not allow
public envelope metadata alone to authorize a key's identity or reissue status.
Do not invent cryptography to authenticate a plaintext index: document the limits
of unauthenticated public metadata and perform the necessary checks after decrypt.
Each authorized retrieval accepts a fresh password, decrypts only for that
operation, validates binding, and returns an explicit short-lived secret result.
Do not cache an unlocked identity, retain the password between requests, or fall
back to original plaintext files after wrong password, missing/corrupt records,
or password loss. Keep cloning/exposure of secrets minimal; zeroize supported
owned buffers on drop and document unavoidable library-managed copies without
claiming all process memory can be erased. Password-policy/UI additions are not
part of this milestone.
M3.3 parses supplied client content without generic lossy INI serialization.
Require one unambiguous client private key and derive its PublicKey for matching.
Validate client Address values while retaining enough information to report
unsupported shapes and compare addresses rather than conflating client prefixes
with server host routes. Preserve existing optional connection parameters needed
later, including any valid preshared secret, inside the encrypted payload; do not
introduce new MTU/keepalive/PSK settings or generation. Reject ambiguous or malformed
secret fields rather than choosing the last occurrence. Do not run PostUp/hooks,
resolve DNS, mutate routes, or execute text from imported files.
Reconcile against the authoritative M2 server inspection and provide explicit,
secret-free results for server-only, file-only, matched, mismatched, invalid,
and ambiguous/duplicate candidates. Missing/invalid managed metadata and narrow
projection failures are represented separately from cryptographic key mismatch.
Compare the client file's server peer key with the server identity when it can
be determined safely; absent/ambiguous server identity is unresolved, not assumed
valid. Do not infer identity from filenames or friendly aliases.
Matching key material may be encrypted even when address parameters conflict,
but record/report that reissue is blocked pending explicit reconciliation. Do
not truncate routes, overwrite server metadata, automatically add a file-only
client to the VPN, or treat public filename fallback as authoritative. Duplicate
files or existing vault entries must not cause silent overwrite or last-wins
selection; preserve the prior record and return an explicit resolution issue.
A repeat import without decryption must not claim plaintext equality it cannot
verify. Define deterministic handling and tests rather than guessing equivalence.
M3.4 supplies a bounded read-only adapter for the configured source directory:
regular files only, documented candidate selection, no recursive traversal or
symlink following, bounded content sizes, and redacted per-file errors. A source
file failure must not abort other valid imports; a directory-enumeration failure
must be visible and must not be called a successful complete scan. Use safe file
opening for privileged future callers; expanding rustix's approved feature set
is permitted when required, but no new direct crate is implicitly approved.
Initial setup combines vault creation with attempted import of valid candidates.
Return the encrypted prepared bundle and a report; invalid/conflicting candidates
remain visible and originals are untouched. Explicit later import reuses the
vault recipient without asking for a password or replacing the protected identity.
Reissue authorization remains separate. Keep directory IO out of pure parsing and
crypto tests; all adapter tests use synthetic temporary directories, never actual
configured server/client paths. All writes in this milestone are test artifacts
or explicit serialization buffers, not installed application state.
#### Acceptance and validation
- [x] M3-A1: generated WireGuard keys derive valid PublicKey values; imported key
correspondence and malformed Base64/length handling are tested without tools.
- [x] M3-A2: vault setup uses an independent key pair and password-protected
identity; serialized application vault data contains no plaintext private keys,
passwords, or imported secret-bearing configurations.
- [x] M3-A3: new record encryption and later explicit import need only the public
recipient, not a password/unlocked identity; loading ciphertext needs no password.
- [x] M3-A4: correct-password retrieval returns the selected secret; wrong/missing
password, unknown versions, malformed/truncated ciphertext, recipient mismatch,
and record relabelling fail without fallback or loss of the original bundle.
- [x] M3-A5: independent retrieval operations require fresh authorization, with no
reusable unlocked identity/password exposed through ordinary vault APIs. Errors,
reports and Debug are redacted; secret serialization is explicit and protected.
- [x] M3-A6: matching uses the public key derived from client Interface PrivateKey,
not its server peer key or alias. Reports cover matched/server-only/file-only,
conflicts, invalid files, duplicate/ambiguous keys and unsupported address shapes.
- [x] M3-A7: address/server-binding discrepancies remain explicit and block the
authorized reissue path until resolved; a valid secret may still be encrypted.
Duplicate imports never silently replace existing secrets or trusted metadata.
- [x] M3-A8: setup prepares encrypted imports from valid source files while skipping
invalid candidates with explanations. Regular-file selection, limits, symlink
rejection and enumeration failures are tested using temporary data only.
- [x] M3-A9: source files and original server bytes are unchanged; no metadata is
implicitly inserted, no creation dates invented, and M2 parsing/editing guarantees
remain intact. Missing metadata does not prevent encrypted preservation of a key.
- [x] M3-A10: serialized bundle round-trip preserves encrypted identity/records
without decryption, ready for M4 backup/persistence; no filesystem installation,
implicit Commit/runtime change, UI delivery or first-issue bypass is introduced.
- [x] M3-A11: standard integrated gates and M1/M2 regressions pass; independent
review closes actionable crypto, import, secret-lifetime and boundary findings.
Planned commands from the repository root after M3 tests and lockfile exist:
```sh
cargo test --locked vault
cargo test --locked --test vault_import
cargo test --locked --test config_preservation
cargo fmt --all -- --check
cargo clippy --locked --all-targets -- -D warnings
cargo test --locked
cargo build --locked --release
./target/release/wireguard-admin --help
./target/release/wireguard-admin --version
git diff --check
```
Ensure the focused filter runs tests and record counts/results; all gates should
exit zero. Resolve approved new dependencies/update the lockfile before locked
checks. Keep password-encryption tests bounded while exercising real library
crypto; do not weaken production KDF parameters simply to speed up tests. Use
synthetic generated keys and no sensitive panic/assert output. No root, Docker,
VM, actual WireGuard interface, or production config is required or claimed.
#### Progress
- [x] Description prepared against accepted M1/M2 code and current documents.
- [x] M3.1 — implementer (`/root/implementer_m3_1`) completed 2026-10-18T19:41:39Z; secret-safe WireGuard key generation, parse, derivation and matching.
- [x] M3.2 — implementer (`/root/implementer_m3_2`) completed 2026-10-18T19:46:09Z; versioned age-encrypted bundle, fresh-password retrieval, format documentation.
- [x] M3.3 — implementer (`/root/implementer_m3_3`) completed 2026-10-18T19:52:45Z; caller-buffer parse/reconciliation and authenticated import status.
- [x] M3.4 — implementer (`/root/implementer_m3_4`) completed 2026-10-18T19:58:55Z; bounded read-only directory adapter, initial setup/import flow and synthetic tests.
- [x] M3.5 — fresh reviewer (`/root/reviewer_m3_r1`) completed R1–R7; repairer (`/root/repairer_m3_r1`) resolved all findings; R7 found no actionable issue.
- [x] M3.6 — coordinator integrated gates passed and acceptance recorded.
#### Review and repair
| Finding | Iteration | Location and impact | Owner | Resolution and evidence | State |
| --- | --- | --- | --- | --- | --- |
| M3-R1-01 | R1 | `src/vault.rs:134` Debug printed untrusted imported connection fields | repairer | Redacted imported payload Debug; sentinel regression and R2 passed | closed |
| M3-R1-02 | R1 | `src/vault/source.rs:110` directory path was reopened after secure FD open, allowing enumeration/read inventory mismatch | repairer | Enumerates through pinned `rustix::fs::Dir`; path-replacement regression and R2 passed | closed |
| M3-R1-03 | R1 | `src/vault/import.rs:409` unknown server identity was collapsed into a known key mismatch | repairer | Added Matches/Mismatch/Unresolved and blocked retrieval regression; R2 passed | closed |
| M3-R1-04 | R1 | `src/vault/source.rs:206` duplicate existing vault entry was generic encryption failure | repairer | Added equality-unverified duplicate issue; repeat-import regression and R2 passed | closed |
| M3-R2-01 | R2 | `docs/VAULT_FORMAT.md` omitted intended component filename/layout for M4 | repairer | Documented intended `data_directory/vault.toml` without persistence; R3 confirmed | closed |
| M3-R2-02 | R2 | Secret-bearing test `assert_eq!` output could disclose synthetic key/config bytes on failure | repairer | Replaced with redacted boolean assertions; R3 confirmed | closed |
| M3-R3-01 | R3 | `src/vault.rs:306` unsupported encrypted payload version reported as `RecordMismatch`, not actionable `UnsupportedVersion` | repairer | Split version/label checks; valid encrypted unsupported-version regression and R4 passed | closed |
| M3-R4-01 | R4 | `src/vault.rs:48` canonical Base64 comparison created a plaintext key `String` without zeroization | repairer | Wrapped in `Zeroizing<String>`; R5 confirmed | closed |
| M3-R5-01 | R5 | `src/vault.rs:38` Base64 decode could drop partially decoded key bytes on malformed input before zeroizing wrapper | repairer | Decode into zeroizing buffer from allocation onward; R6 confirmed | closed |
| M3-R5-02 | R5 | `src/vault.rs:287` identity UTF-8 conversion cloned decrypted identity into ordinary Vec, vulnerable on error | repairer | Borrow from zeroizing plaintext; R6 confirmed | closed |
| M3-R6-01 | R6 | `src/vault/import.rs:112` allocated ordinary directive String before knowing whether value was secret/ignored | repairer | Borrowed dispatch; zeroizing PrivateKey/PSK allocations, duplicate/error paths and regressions passed; R7 confirmed | closed |
The reviewer otherwise found crypto binding, fresh authorization and bounded
scan behavior sound at fixture level. No finding may be closed by hiding a
conflict, disabling tests, or relaxing encryption.
#### Evidence and handoff
Planning inspected Cargo.toml, exported M2 document/edit/metadata APIs,
CONFIG_FORMAT.md, M1 model constraints, approved dependencies and ADR 0002.
M2 records independent R3 acceptance, 42 unit plus 9 integration tests, and passing
integrated gates. Those results are historical evidence; this planning task did
not rerun application tests, select crypto versions, or add dependencies/source.
No workers were dispatched and no source directory, private key, service, or
network environment was accessed.
Next execution action: verify configured model roles and the current repository,
review official APIs/compatibility for the approved cryptographic libraries, then
dispatch M3.1 with the bounded task and acceptance criteria. Escalate unavailable
packages as dependency/environment blockers rather than changing approved crypto.
Execution verified the coordinator session as `gpt-6-sol`/`medium`; M1 and M2
are accepted. At start, the only worktree change was this prepared M3 plan.
Rustc/Cargo 1.97.1, rustfmt 1.9.0 and Clippy 0.1.97 match the pinned baseline.
Official crate documentation was checked for `age` 0.12.1 (Rust 1.74 minimum,
recipient and passphrase APIs, no default features), `x25519-dalek`,
`getrandom`, and `secrecy`. `age` re-exports its compatible `secrecy` via
`age-core`; use that re-export rather than adding a conflicting direct version.
The local cache initially lacked `age`; a read-only `cargo info age@0.12.1`
failed under sandbox DNS restrictions, then succeeded with approved network
escalation. No manifest or source was changed during this prerequisite check.
Next: assign M3.1 to the configured implementer.
M3.1 added `vault::WireGuardPrivateKey` using approved `x25519-dalek` 2.0.1,
`getrandom` 0.3.4, and `zeroize` 1.9.0; no `age`/`secrecy` direct dependency was
introduced prematurely. The coordinator inspected the new key API and manifest.
The implementer ran `cargo test --locked vault` (3 tests), `cargo test --locked`
(45 unit and 9 preservation integration tests), fmt, Clippy, release build and
diff check successfully. Its turn context was verified `gpt-6-luna`/`high`.
No production data or service was accessed. Next: M3.2 versioned age vault
format and authorized retrieval on top of this key type.
M3.2 added approved `age` 0.12.1, `VaultBundle` recipient encryption and
passphrase-protected identity, bounded TOML loading, ciphertext/recipient/key
binding checks, and `docs/VAULT_FORMAT.md`. The coordinator inspected source,
format and manifest. The worker ran `cargo test --locked vault` (9 tests),
`cargo test --locked` (51 unit and 9 preservation integration tests), fmt,
Clippy, release build and diff check successfully; its turn context was
verified `gpt-6-luna`/`high`. This is task progress, not integrated M3
acceptance. M3.3 must extend encrypted records to retain imported connection
parameters and enforce duplicate/no-overwrite and conflict reissue semantics;
independent review must scrutinize the current public `reissue_blocked` flag's
authority and secret lifetime. Next: M3.3 caller-buffer import/reconciliation.
M3.3 added `src/vault/import.rs` and extended the encrypted v1 record payload
to retain imported connection parameters, including valid PSKs. It changed
duplicate insertion to fail without overwrite and moved the conflict gate into
the authenticated payload; the public flag is advisory. The coordinator
inspected implementation and updated format documentation. The worker ran
`cargo test --locked vault` (15 tests), `cargo test --locked` (57 unit and 9
preservation integration tests), fmt, Clippy, release build and diff check
successfully; its turn context was verified `gpt-6-luna`/`high`. This remains
pending independent review. Potential review focus: unresolved versus
mismatched server identity classification, preservation and lifetime of source
buffers, and absence of conflict-clearing bypass. Next: M3.4 bounded read-only
directory adapter and setup/import workflow.
M3.4 added `src/vault/source.rs`, `tests/vault_import.rs`, README guidance,
approved `tempfile` 3.23.0 as a development dependency, and `rustix` fs/std
features. It uses Linux `openat2` no-symlink file opens, per-file/aggregate
limits and incomplete-scan reporting. The coordinator inspected the adapter,
fixture tests and manifest. The worker ran `cargo test --locked vault` (16
tests), `cargo test --locked --test vault_import` (5),
`cargo test --locked --test config_preservation` (9), `cargo test --locked`
(58 unit, 9 preservation and 5 vault-import tests), fmt, Clippy, release build
and diff check successfully; its turn context was verified `gpt-6-luna`/`high`.
No production path or service was accessed. Possible review focus: path-based
directory enumeration after secure fd open, explicit duplicate-existing-record
reporting, and whether server-key unknown is distinct from mismatch. Linux
`openat2` absence yields an incomplete scan, not success. Next: fresh integrated
independent review M3.5 before acceptance.
Fresh R1 reviewer turn used `gpt-6-luna`/`high`. It inspected integrated code
and ran focused vault (16), vault-import (5), and M2 preservation (9) tests;
the full suite passed 58 unit plus 14 integration tests, and fmt, Clippy,
release build, help/version and diff check passed. It found M3-R1-01 through
M3-R1-04 above. These passing tests do not close the review findings. Next:
repair, re-review, then coordinator integrated gates.
The repairer (`/root/repairer_m3_r1`, `gpt-6-luna`/`high`) reported all four
R1 findings repaired with focused regressions. It ran `cargo test --locked
vault` (18), vault-import (5), M2 preservation (9), full tests (60 unit plus
14 integration), fmt, Clippy, release build and diff check successfully. The
coordinator inspected changed paths. Findings remain pending independent R2
re-review; no acceptance is claimed yet.
R2 independently confirmed M3-R1-01 through M3-R1-04 and found no remaining
defect in those paths. It found M3-R2-01 and M3-R2-02 above. Focused vault
(18), vault-import (5), preservation (9), full tests (60 unit plus 14
integration), fmt, Clippy, release build, help/version and diff check passed;
the passing gates do not resolve the documentation and test-output gaps. Next:
narrow repair and R3 re-review.
R3 independently closed M3-R2-01/02 and confirmed R1 repairs remain intact.
It found M3-R3-01 and noted an M3-A7 evidence gap: the existing known
server-key mismatch test also has unsupported address projection, so an
isolated mismatch-blocks-reissue test is needed. Aggregate scan-byte and
directory-entry limits are implemented but lack dedicated boundary tests;
their coverage should be strengthened if practical. R3 reran focused vault
(18), vault-import (5), preservation (9), full suite (60 unit plus 14
integration), fmt, Clippy, release build, help/version and diff check
successfully. Passing checks do not close M3-R3-01. Next: repair and R4.
The repairer (`/root/repairer_m3_r1`) split encrypted payload version and
label checks, added a valid age-encrypted unsupported-version regression, an
isolated known server-key-mismatch/reissue-block regression, and inexpensive
aggregate-byte/directory-entry cap tests through private testable limits. It
reported 22 focused vault tests, 5 vault-import tests, 9 M2 preservation
tests, 64 unit plus 14 integration tests in the full suite, and passing fmt,
Clippy, release build and diff check. The coordinator inspected the new code;
R4 independent re-review is pending. No M3 acceptance is claimed yet.
R4 independently confirmed M3-R3-01 and all earlier repairs. It reran focused
vault (22), vault-import (5), M2 preservation (9), full tests (64 unit plus
14 integration), fmt, Clippy, release build, help/version and diff check,
all passing. It found M3-R4-01 above; this must be repaired before M3
acceptance. No production access or real-system result is claimed.
R5 independently confirmed M3-R4-01 but identified M3-R5-01/02 in adjacent
secret-lifetime paths. Focused vault (22), vault-import (5), preservation (9),
full tests (64 unit plus 14 integration), fmt, Clippy, release build,
help/version and diff check all passed; these checks do not prove zeroization
on failure. Earlier repairs remain intact. Next: targeted cleanup and R6
re-review; M3 is not accepted.
R6 independently confirmed M3-R5-01/02: base64 decoding now occurs inside a
zeroizing Vec even on failure, and age identity parsing borrows from zeroizing
bytes. It found M3-R6-01 in the import parser. It reran focused vault (22),
vault-import (5), M2 preservation (9), full suite (64 unit plus 14
integration), fmt, Clippy, release build, help/version and diff check, all
passing. No production access or real-system result was claimed. Next:
targeted parser cleanup and R7.
The repairer changed `src/vault/import.rs` to borrow section, directive key and
value until dispatch; ignored/misplaced values are not allocated. Recognized
PrivateKey/PSK values enter zeroizing storage immediately, including
duplicate/error paths. Redaction regressions were added. It reported focused
vault (22), full tests (64 unit plus 14 integration), fmt, Clippy, release
build and diff check passing; the coordinator inspected the changed parser.
Independent R7 re-review was the next gate after this repair.
R7 independent reviewer (configured `gpt-6-luna`/`high`) inspected the final
parser, prior repairs, cryptographic/secret lifetimes, source-directory safety
and M3-A1–A11. It found no remaining actionable issue and recommended
acceptance. The coordinator reran the full planned command set on the final
integrated source on 2026-10-19: `cargo test --locked vault` passed 22 tests;
`cargo test --locked --test vault_import` passed 5; `cargo test --locked --test
config_preservation` passed 9; `cargo fmt --all -- --check`, `cargo clippy
--locked --all-targets -- -D warnings`, `cargo build --locked --release`, and
`git diff --check` exited zero; `cargo test --locked` passed 64 unit tests,
9 preservation integration tests and 5 vault-import integration tests (0 doc
tests); release `--help` and `--version` both exited zero with existing safe
startup output. The focused vault filter ran 22 actual tests. These are
unprivileged synthetic/unit fixture results only. No production files,
running VPN, host service, Alpine container or systemd VM was accessed;
container/VM checks are not required for this M3 library milestone and are
not claimed. No M3 blocker remains.
#### Decisions and discoveries
The client's Interface private key and the server's Peer public key have different
roles. Existing M2 source/span APIs support authoritative reconciliation but must
not be reused as an unsafe source-dump parser for secrets. M1 projection cannot
represent all legacy peers; use richer import inspection without changing that
model or metadata v1 merely to suppress anomalies.
Prepared vault encoding is the M3/M4 interface: M3 supplies versioned encrypted
components; M4 owns atomic installation, backup-before-mutation, restore, and
Commit deletion. M6 owns file/QR generation and first-delivery session policy.
This boundary avoids persisting unbacked state during an import/crypto milestone.
#### Outcome
Accepted after independent R7 review, closure of all findings and passing
M3-A1–A11 gates. Stop here; milestone 4 is the next action only on a new
request. No vault installation, Git commit, deployment or production access
occurred.
### Milestone 4 — Backups, safe Commit, and full restoration
@@ -661,11 +1061,12 @@ Acceptance: real D-Bus job handling, separate restart verification, address rele
## Current progress and resume point
- Milestones 1 and 2 are accepted; milestones 3–9 remain pending.
- Milestones 1, 2 and 3 are accepted; milestones 4–9 remain pending.
- The approved dependency list is in [DEPENDENCIES.md](docs/DEPENDENCIES.md); container testing methodology is in [TESTING.md](docs/TESTING.md).
- Milestone 1 is expanded and the Rust environment is pinned.
- Milestone 2 delivered a lossless in-memory WireGuard configuration editor, versioned metadata, and behavioral preservation tests.
- Next action: stop after accepted M2; begin M3 only if separately requested.
- Milestone 3 delivered secret-safe key handling, a versioned encrypted vault, caller-buffer reconciliation, and a bounded read-only source-directory import adapter.
- Next action: stop after accepted M3; begin M4 only if separately requested.
- M1 integrated gates and independent re-review passed. No container or VM results are claimed.
## Plan maintenance