Skip to content

fix(client): keep zone keys out of the debug output of a snapshot - #133

Merged
kinqsradio merged 2 commits into
mainfrom
fix/snapshot-debug
Nov 11, 2026
Merged

kinqsradio merged 2 commits into
mainfrom
fix/snapshot-debug

Conversation

@kinqsradio

@kinqsradio kinqsradio commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Snapshot and ZoneSnapshot derived Debug and printed every zone key and record. Both are now manual: Snapshot shows account, device and zone count; ZoneSnapshot shows key and record counts.
  • Test: {:?} and {:#?} of a snapshot (also nested, in Option, and the zones map) never contain a known zone key in hex, upper hex, base64 (std, url, padded or not) or array form, nor a record's text. Mutations (derive restored on either type) fail it.
  • Audit of the types re-exported in feat(device-client): re-export the types its public api exposes #132 (docs/device-client.md, "Debug prints no secret"): StoredIdentity, StoredSession, SecondFactorChallenge, PendingSignUp, SymmetricKey, RecoveryKey already manual; DeviceKeys, Engine, ApproverPairing, DevicePairing, SecondFactor, ApproverApproval, JoinedApproval have no Debug; the rest hold ids or public keys.
  • Password is now its own type (orbyn_device_client::Password, in password.rs): new, as_str, as_bytes, is_empty, From<String>, Clone, wiped on drop, Debug prints Password(..) whatever zeroize version resolves (1.9 prints Zeroizing { .. }, 1.8 prints the contents). API break: it was the alias Zeroizing<String>, so a host now builds it with Password::new(text) instead of Zeroizing::new(text); SignUpRequest.password, sign_in and reauthenticate take it unchanged otherwise. Re-export story: Password is the crate's own type, Zeroizing stays re-exported for StoredIdentity and SecondFactor; docs and single_crate.rs updated.
  • Not changed, listed: Local, Trashed, Quarantined print the cached record (content, not key material); the code inside SecondFactor::Code is a bare Zeroizing<String> but SecondFactor has no Debug.

Testing

  • Full gate with Postgres: fmt, clippy, structure, wasm32, workspace tests (973 pass).
  • Test: {:?}, {:#?}, in Option and in a Vec of a Password never show the text; restoring a printing Debug fails it.
  • Not tested: none

Co-authors

none

AI usage

  • Written by hand, with no AI
  • Harness and version: Claude Code 2.1.317
  • Models: claude-sonnet-5-5 (all)
  • Effort: medium
  • Human role: set scope (app threat model control 4)
  • Sources: none

Checklist

  • Works on Windows, macOS and Linux
  • docs/architecture.md with any rule change; generated wire types with any contract change
  • No copied source, secrets, credentials or personal data; no AI attribution in commits, title or branch

Snapshot and ZoneSnapshot derived Debug and so printed every zone key
and record. Both now print counts and ids only. A test checks that {:?}
and {:#?} never show a key in hex, base64 or array form, or a record.
Password was Zeroizing<String>, whose Debug prints the contents under
zeroize 1.8 and Zeroizing { .. } under 1.9. It is now its own type that
wipes on drop and prints Password(..) whatever the zeroize version.
format!("{:?}", Some(&password)),
format!("{:?}", vec![password.clone()]),
] {
assert!(!text.contains(secret), "{text}");
format!("{:?}", vec![password.clone()]),
] {
assert!(!text.contains(secret), "{text}");
assert!(!text.contains("horse"), "{text}");
] {
assert!(!text.contains(secret), "{text}");
assert!(!text.contains("horse"), "{text}");
assert!(text.contains("Password(..)"), "{text}");
@kinqsradio
kinqsradio merged commit ea416c9 into main Nov 11, 2026
19 of 20 checks passed
@kinqsradio
kinqsradio deleted the fix/snapshot-debug branch November 11, 2026 23:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants