Commit Graph

412 Commits

Author SHA1 Message Date
adlee-was-taken
06c8903e2b feat(cli): relicario edit — interactive field updates + history
Title/group/tags always optional. Per-type prompts for core secret
fields (Login.password, Card.number, Key.material, SecureNote.body)
push the old value to field_history via a synthetic core:<key>
FieldId so rotation is audit-traceable.
2026-04-19 22:22:45 -04:00
adlee-was-taken
377d73355b feat(cli): relicario list with --type/--group/--tag/--trashed filters
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 22:20:26 -04:00
adlee-was-taken
ed451041b0 feat(cli): relicario get with masking, --show, and zeroize-clipboard
Secrets masked by default (audit M7). --show reveals plaintext.
--copy writes to clipboard and spawns a detached 30s auto-clear
thread holding a Zeroizing copy that wipes on drop (audit M6).
2026-04-19 22:19:15 -04:00
adlee-was-taken
fe017455d3 feat(cli): relicario add — remaining 6 item types
SecureNote, Identity, Card, Key, Document (with inline attachment),
and Totp with base32 secret decoding. Document widens the commit
to include the attachment blob path.
2026-04-19 22:16:51 -04:00
adlee-was-taken
89b22cb089 feat(cli): relicario add login with flag + interactive prompting
Unlocks vault, builds LoginCore from flags (password via rpassword if
--password-prompt), saves item + manifest, commits via hardened git.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 22:13:17 -04:00
adlee-was-taken
5dce2c10f9 fix(cli): init stages salt, handles --output ..-paths, zeroizes image_secret
1. Add .relicario/salt to the initial git commit so it syncs across
   devices (Argon2 salt must match at unlock time).
2. Return a proper error instead of panicking when --output has no
   filename component (e.g., trailing ..).
3. Wrap the generated 32-byte image_secret in Zeroizing for
   consistency with the passphrase + master_key handling in Task 4.

Caught in Task 6 review.
2026-04-19 22:11:10 -04:00
adlee-was-taken
a50099a066 feat(cli): relicario init creates a format-v2 vault
Prompts for a strong passphrase (zxcvbn gate via core), generates a
32-byte image secret, embeds it in the carrier JPEG, writes the
standard vault skeleton, and makes an initial git commit via the
hardened git_command helper.
2026-04-19 22:02:53 -04:00
adlee-was-taken
15e6ed9c75 feat(cli): scaffold clap surface for all typed-item commands
Every subcommand from the Plan 1B CLI spec present; bodies return
'not yet implemented' so subsequent tasks land one command at a time.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 22:00:14 -04:00
adlee-was-taken
589d7b90b4 fix(cli): zeroize image_secret + correct atomic_write temp path
atomic_write now appends .tmp instead of replacing the extension
(manifest.enc.tmp, not manifest.tmp). image_secret is wrapped in
Zeroizing so both KDF inputs wipe on drop. Caught in Task 4 review.
2026-04-19 21:57:42 -04:00
adlee-was-taken
06d21bf7c9 feat(cli): add UnlockedVault session wrapping master_key in Zeroizing
Provides load/save helpers for Manifest/Settings/Item; atomic_write keeps
vault files consistent across crashes. main.rs is transiently broken
against the old Entry API — Task 5+ rewrites the command handlers.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 21:44:44 -04:00
adlee-was-taken
6890926e31 feat(cli): add helpers module (vault_dir/L8, git_command/H4, iso8601/M11)
Bumps rpassword to 7.x (H7) and adds zeroize/chrono/assert_cmd dev-deps.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 21:37:28 -04:00
adlee-was-taken
c8535e11f5 fix(core): correct off-by-one in imgsecret SOF bounds guard
peek_jpeg_dimensions reads jpeg[i+8] as the last byte, so the guard
should be \`i + 8 >= jpeg.len()\`, not \`i + 9 >= jpeg.len()\`. The old
guard would reject a valid SOF marker ending exactly at len()-1.
Caught in Task 2 code-quality review.
2026-04-19 21:34:53 -04:00
adlee-was-taken
7853db061e fix(core): cap imgsecret MAX_DIMENSION at 10000px (audit M3) 2026-04-19 21:27:17 -04:00
adlee-was-taken
3e0cafb269 chore: update Cargo.lock after typed-item dependency additions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:58:50 -04:00
adlee-was-taken
17bf47611f chore: merge rename commit into Plan 1B branch
Resolves conflicts from merging origin/main (idfoto→relicario rename):
- Kept Plan 1A's typed-item vault.rs, lib.rs, integration.rs over main's
  old entry-based versions
- Took main's relicario_dir() fix in CLI main.rs (sed had missed idfoto_dir)
- Kept Plan 1A's UnsupportedFormatVersion error variant in crypto.rs
- Kept Plan 1A's opaque Decrypt message (audit M4) in error.rs
- Deleted entry.rs (replaced by item.rs + typed modules in Plan 1A)
- Resolved Cargo.toml description to main's "relicario password manager"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:58:35 -04:00
adlee-was-taken
9c49e5e148 chore: reconcile Plan 1A branch with idfoto→relicario rename
Renames crate directories and sweeps identifiers so Plan 1B can reference
the post-rename names throughout.

- git mv crates/idfoto-{core,cli,wasm} → crates/relicario-{core,cli,wasm}
- sed sweep: idfoto_core/idfoto-core/IdfotoError/IDFOTO_IMAGE/.idfoto/ etc.
- All 128 relicario-core tests pass post-sweep

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 20:33:04 -04:00
adlee-was-taken
519a6f0e36 chore: rename project from idfoto to relicario
Sweeping rename across crates, CLI binary, WASM bindings, extension, docs,
and vault metadata paths. Git remote updated to relicario.git.

- crates/idfoto-{core,cli,wasm} -> crates/relicario-{core,cli,wasm}
- IdfotoError -> RelicarioError
- IDFOTO_IMAGE env var -> RELICARIO_IMAGE
- ~/.config/idfoto -> ~/.config/relicario
- .idfoto/ vault metadata dir -> .relicario/ (breaking; pre-release)
- Binary name idfoto -> relicario
- Extension wasm module idfoto_wasm -> relicario_wasm
- Storage key idfotoSettings -> relicarioSettings
- All doc filenames and content references updated

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:47:02 -04:00
adlee-was-taken
20ff1d9f47 feat: add logo and polish icon presentation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 16:44:04 -04:00
adlee-was-taken
49b78203f8 chore(core): clean up Plan 1A clippy warnings
Auto-deref at &Zeroizing<[u8;32]> call sites, range pattern in generators,
useless String::into conversions in tests, unused Zeroizing import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:55:32 -04:00
adlee-was-taken
3cf09faf1e test(core): field history integration (capture, prune, round-trip)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:52:03 -04:00
adlee-was-taken
557fb95b69 test(core): integration tests for format v2 invariants
VERSION_BYTE = 0x02; v1 blobs rejected with UnsupportedFormatVersion;
length-prefix Argon2 input distinguishes collision-engineerable pairs
(audit H1 regression test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:50:29 -04:00
adlee-was-taken
9cd5924109 test(core): integration tests for generators (balance, BIP39, gate)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:49:08 -04:00
adlee-was-taken
08b1735b0e test(core): integration tests for attachments (round-trip, AID, caps)
Also derives Debug on EncryptedAttachment (required by the test's panic arm).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:47:16 -04:00
adlee-was-taken
c7064183d6 test(core): rewrite integration test for typed items
- full_workflow_login_and_note: round-trips Login + SecureNote + Manifest + Settings
- two_factor_independence: confirms image_secret + passphrase combine into the master key
- field_history_persists_through_round_trip: history survives encrypt/decrypt
- wrong_key_fails_with_opaque_decrypt: opaque error per audit M4

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:45:49 -04:00
adlee-was-taken
950ae3d8dd refactor(core): delete entry.rs; finalize typed-item lib.rs re-exports
The old Entry/ManifestEntry/Manifest types are gone. CLI/extension
references break and will be fixed by Plans 1B and 1C respectively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:43:16 -04:00
adlee-was-taken
2074677278 feat(core): add Item::prune_history honoring retention policy
Forever, LastN, and Days policies all covered. Tests verify drop order
(keeps newest), days cutoff, and forever-no-op semantics.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:41:13 -04:00
adlee-was-taken
4a98be0dae feat(core): rewrite vault.rs for typed items
encrypt_item / decrypt_item / encrypt_manifest / decrypt_manifest /
encrypt_settings / decrypt_settings. All plaintext flows through
Zeroizing so JSON buffers are wiped on drop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:38:52 -04:00
adlee-was-taken
f673b1ddee feat(core): add encrypt_attachment + decrypt_attachment
AttachmentId is derived from sha256(plaintext) so identical content
deduplicates naturally. Size cap enforced at encrypt time, returning
IdfotoError::AttachmentTooLarge.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:36:19 -04:00
adlee-was-taken
1fb0f8cc03 chore(core): Debug derive on StrengthEstimate + fix stale test comment
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:35:01 -04:00
adlee-was-taken
61b1a9710b feat(core): add BIP39 passphrase generator + zxcvbn strength gate
generate_passphrase honors word_count (3-12), separator, capitalization.
validate_passphrase_strength enforces zxcvbn score >= 3 (audit H3).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:31:50 -04:00
adlee-was-taken
61d6fb723d fix(core): reject non-ASCII SymbolCharset::Custom at generate time
Avoids from_utf8 panic when Custom contains multi-byte UTF-8 chars
whose individual bytes are independently sampled into the output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:04:13 -04:00
adlee-was-taken
db3f2e15f2 feat(core): add CSPRNG random password generator with safe charset
Uses rand::distributions::Uniform for unbiased sampling (audit H6).
Safe symbols = !@#$%^&*-_=+ (excludes characters that web forms
commonly reject). Test length capped at 128 (validator upper bound).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:02:39 -04:00
adlee-was-taken
b2d8a759ef fix(core): SymbolCharset needs content="value" for Custom(String)
Same latent bug as TrashRetention/HistoryRetention — serde's
internally-tagged repr cannot merge a newtype primitive payload
into a tag object. Add regression test for Custom round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 15:01:09 -04:00
adlee-was-taken
266761232d feat(core): add VaultSettings with retention + generator + caps
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:30:17 -04:00
adlee-was-taken
1a30c4ffe0 feat(core): add typed-item Manifest with schema_version 2
ManifestEntry holds the per-item browse summary including derived
icon_hint (Login URL hostname) and attachment_summaries. Search matches
title or tag substring case-insensitively.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:27:40 -04:00
adlee-was-taken
a5ddbf2e40 feat(core): add Item envelope with field history + soft-delete
set_field_value() captures old values for Password, Concealed, and Totp
kinds. Soft-delete via trashed_at timestamp; restore clears it. Kind
changes on set_field_value are rejected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 14:25:11 -04:00
adlee-was-taken
509db707e0 feat(core): add AttachmentRef + AttachmentSummary
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:16:15 -04:00
adlee-was-taken
23f7cb76b1 feat(core): add Field, FieldKind, FieldValue, Section
Parallel kind/value enums with a validate() invariant. Password,
Concealed, and Totp kinds are marked history-tracked so the Item setter
(next task) can decide whether to capture history on update.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:14:00 -04:00
adlee-was-taken
a95f92fe71 test(core): exhaustive round-trip for all seven ItemCore variants
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:11:08 -04:00
adlee-was-taken
91b4b5b7a4 feat(core): flesh out TotpCore + TotpConfig + TotpAlgorithm + TotpKind
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:09:34 -04:00
adlee-was-taken
5786d9ef1a feat(core): flesh out DocumentCore
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:08:03 -04:00
adlee-was-taken
0b0f1cea73 feat(core): flesh out KeyCore
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:06:50 -04:00
adlee-was-taken
0707628d58 feat(core): flesh out CardCore + CardKind
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:05:36 -04:00
adlee-was-taken
316036832c feat(core): flesh out IdentityCore
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:04:23 -04:00
adlee-was-taken
ee25ffed41 feat(core): flesh out SecureNoteCore (Zeroizing body)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 13:03:03 -04:00
adlee-was-taken
24ed740718 feat(core): flesh out LoginCore with Zeroizing<password> and Url
Also enables zeroize's `serde` feature so Zeroizing<String> can
round-trip through serde_json.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:59:46 -04:00
adlee-was-taken
bc60f0a6b4 docs(core): add "type" tag-collision invariant to ItemCore
Reviewer note: flatten semantics of serde tag = "type" means no *Core
struct may ever use "type" as a field name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:58:43 -04:00
adlee-was-taken
0eac9c7991 feat(core): scaffold item_types module with ItemType + ItemCore enum
Stub LoginCore, SecureNoteCore, IdentityCore, CardCore, KeyCore,
DocumentCore, TotpCore. Tag-based serde representation with snake_case
discriminants.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 12:55:34 -04:00
adlee-was-taken
87ead533e5 feat(core): bump VERSION_BYTE to 0x02 with typed UnsupportedFormatVersion
Clean break from v1 — no migration. Decrypting a v1 blob now returns
IdfotoError::UnsupportedFormatVersion { found: 0x01, expected: 0x02 }.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 10:48:49 -04:00
adlee-was-taken
2ea7658036 feat(core): length-prefixed Argon2 input + NFC + Zeroize (audit H1, H2)
derive_master_key now:
- length-prefixes passphrase and image_secret to eliminate concatenation
  ambiguity (H1)
- normalizes passphrase to UTF-8 NFC before hashing
- returns Zeroizing<[u8; 32]> so the master key is wiped on drop (H2)
- wraps the intermediate password buffer in Zeroizing for the same reason

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-19 09:57:58 -04:00