Proof-of-concept proving the secure-storage exit criterion from docs/architecture/proof-of-concept-plan.md §2: "Secret write/read/delete works through platform secure storage." Implements a typed SecretStorageRepository trait per ADR-006 (SecureStore + per-platform adapters) and a Linux adapter (the only adapter in PoC scope) that supports both equivalent Linux backends per SysRS-053/SysRS-162: Secret Service (libsecret) and kernel keyutils. The audit test suite covers: SS-AUD-001 identity secret absent from local DB (raw file scan) SS-AUD-002 server password absent from local DB SS-AUD-003 secrets absent from logs (Secret newtype redaction) SS-AUD-005 failure returns safe typed error (NotFound) SS-AUD-006 delete removes entry SS-TC-003 Linux round-trip set/get/delete Verified on 2026-05-13 against the local keyutils backend (cargo test runs need 'keyctl session -' to provide a valid session keyring under non-interactive shells, documented in the spike README). The CLI driver additionally observed a real locked gnome-keyring collection and exercised the typed-error → fallback path live. Surfaced finding for the decision register: DEC-013 does not pin a Linux secure-storage backend policy. Both Secret Service and keyutils are 'equivalent' per the requirements; production code needs an owner ruling. Out of scope: Windows DPAPI, macOS/iOS Keychain, Android Keystore, SS-AUD-004 (covered by diagnostics-redaction spike), SS-AUD-007/008 (process / migration items). Authority: PoC plan §2, ADR-006, SDD-078, SRS-091..095, SysRS-158..162. Not product code; not promoted into chanora_storage.
4.5 KiB
4.5 KiB
Verification record — secure-storage-spike
Result
PASS. The PoC exit criterion (from
docs/architecture/proof-of-concept-plan.md §2: "Secret write/read/delete
works through platform secure storage") is met on Linux through the
kernel keyutils backend, with a documented Secret Service path verified
manually via the CLI driver.
Environment
| Field | Value |
|---|---|
| Date | 2026-05-13 |
| Host OS | Linux (Arch, kernel 7.0.5-arch1-1, x86_64) |
| Rust toolchain | stable 1.95.0 |
| Backend (tests) | kernel keyutils (linux-native, add_key(2) / request_key(2)) |
| Backend (CLI) | tried Secret Service first; fell back to keyutils because the default Secret Service collection was locked (no graphical login) |
keyring crate |
3.6.3 (sync-secret-service, linux-native, crypto-rust) |
linux-keyutils |
0.2.5 |
rusqlite |
0.32.1 (bundled) |
Reproduction
keyctl session - cargo test --tests
keyctl session - cargo run --bin secure-storage-cli
Test run
running 6 tests
test ss_aud_003_secret_values_not_in_logs ... ok
test ss_aud_006_delete_removes_entry ... ok
test ss_aud_001_identity_secret_absent_from_local_db ... ok
test ss_aud_002_server_password_absent_from_local_db ... ok
test ss_aud_005_safe_error_on_missing_entry ... ok
test ss_tc_003_round_trip_linux ... ok
test result: ok. 6 passed; 0 failed; 0 ignored; 0 measured;
0 filtered out; finished in 0.00s
CLI run
INFO spike: Secret Service unavailable (Platform secure storage failure:
DBus error: Cannot create an item in a locked collection);
falling back to keyutils
INFO spike: writing secret identity.primary
INFO spike: reading back
INFO spike: round-trip OK (48 bytes)
INFO spike: deleting
INFO spike: post-delete read correctly returned NotFound
OK — Linux Secret Service round-trip verified.
This CLI output also evidences SS-AUD-005 in practice: a locked Secret
Service collection produced a typed Backend error containing no secret
material, which the caller could then route to the fallback adapter
rather than propagating the raw D-Bus message to the user.
Audit-check coverage
| Check | Mapped test(s) | Result |
|---|---|---|
| SS-AUD-001 (Identity secret absent from local DB) | ss_aud_001_identity_secret_absent_from_local_db |
PASS — raw SQLite file scanned for plaintext; only the lookup name appears. |
| SS-AUD-002 (Server password absent from local DB) | ss_aud_002_server_password_absent_from_local_db |
PASS — same scan, distinct plaintext marker. |
| SS-AUD-003 (Secret values not in logs) | ss_aud_003_secret_values_not_in_logs |
PASS — captured tracing output contains <redacted> markers; never contains the plaintext. |
| SS-AUD-005 (Failure → safe error) | ss_aud_005_safe_error_on_missing_entry + CLI fallback path |
PASS — NotFound is the typed Display, no leakage. |
| SS-AUD-006 (Delete removes entry) | ss_aud_006_delete_removes_entry |
PASS — second delete returns NotFound, not silent success. |
| SS-TC-003 (Linux round-trip) | ss_tc_003_round_trip_linux |
PASS — set/get equality + delete + post-delete NotFound. |
What this spike does NOT validate
- SS-AUD-004 (diagnostic export redaction) —
diagnostics-redaction-spike. - SS-AUD-007 (per-platform documentation completeness).
- SS-AUD-008 (migration / import safety).
- SS-TC-001 (Windows DPAPI / Credential Manager).
- SS-TC-002 (macOS Keychain).
- SS-TC-004 (Android Keystore).
- SS-TC-005 (iOS Keychain).
- Concurrent access from multiple threads / processes.
- Long-lived persistence behaviour across reboots (keyutils backend is session-scoped by design).
- Behaviour under a locked or absent Secret Service collection in production (mitigation strategy is shown but not policy).
Notable observations
- The kernel keyutils session keyring is inherited from the calling
process; non-interactive shells can present an expired
_seskeyring. The wrappingkeyctl session -is the documented workaround and is universal on Linux. Production code on a graphical session inherits a valid session from PAM and does not need it. - gnome-keyring on this host was running but the default collection was locked because no graphical login had occurred. The CLI's Secret-Service-first / keyutils-fallback path observed exactly this condition and reacted correctly, doubling as live evidence for SS-AUD-005.
- The
Secretnewtype'sDebugformatter printsSecret(<N bytes redacted>)andDisplayprints<redacted>. The SS-AUD-003 test exercises both representations.