Files
chanora/poc/secure-storage-spike/VERIFICATION.md
T
EdisonJwa 50c95b61ad feat(poc/storage): add secure-storage spike (Linux)
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.
2026-05-14 12:26:23 +08:00

106 lines
4.5 KiB
Markdown

# 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
```bash
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* `_ses`
keyring. The wrapping `keyctl 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 `Secret` newtype's `Debug` formatter prints
`Secret(<N bytes redacted>)` and `Display` prints `<redacted>`. The
SS-AUD-003 test exercises both representations.