Plan B Phase 7 sub-step 2 — moves the bodies of parse_month_year,
base32_decode_lenient, guess_mime from crates/relicario-cli/src/parse.rs
to relicario-core. The CLI's parse.rs becomes a 19-line shim re-exporting
the new core API.
New core surface:
- time::MonthYear::parse (Result<_, RelicarioError>)
- mime::guess_for_extension (new mime module)
- item_types::TotpConfig::parse_secret — Zeroizing<Vec<u8>> wrapper
over base32::decode_rfc4648_lenient
base32 module promoted from pub(crate) to pub so non-core consumers
(CLI shim, future Phase 8 WASM exports) can reach it. New
RelicarioError::InvalidMonthYear(String) for the parse error path
(mirrors sub-step 1's InvalidBase32). MonthYear::new keeps its
&'static str error type — bringing it to RelicarioError is DEV-A's P3.
CLI callsites unchanged (commands/{add,edit,attach}.rs); RelicarioError
auto-converts to anyhow::Error at ? boundaries.
cargo test --workspace: green (core 143, +7 from new tests)
cargo clippy --workspace: silent
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
New crates/relicario-core/src/base32.rs hosts encode_rfc4648 +
decode_rfc4648_lenient (case-insensitive, optional padding, whitespace
stripped). Folds inline base32_encode (item.rs:255-275) and
decode_base32_totp (import_lastpass.rs:202-220) into the shared module;
both call sites updated.
- New RelicarioError::InvalidBase32(String) variant for the decoder
error path
- Module is pub(crate); public API surface unchanged
- Steam alphabet (item_types/totp.rs:13) intentionally separate with
neighbour comment pointing at crate::base32
Plan B Phase 7 sub-step 1 (DEV-A P2 base32 dedup half).
docs/superpowers/specs/2026-05-04-cli-restructure-design.md.
cargo test --workspace: green
cargo clippy --workspace: silent
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 3 code-quality review caught that the [`RECOVERY_PRODUCTION_PARAMS`]
form in the module header introduced a new rustdoc warning (the const is
module-private, so the link only resolves under --document-private-items).
Drop the brackets so it renders as plain backticks — same visual, no
broken link, no need to widen visibility.
Refs: docs/superpowers/specs/2026-05-04-security-polish-design.md (Phase 3)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Phase 3 of the security-polish series. Brings recovery_qr.rs up to
the documentation density of crypto.rs / imgsecret.rs / backup.rs /
tar_safe.rs. No runtime behaviour change: just module-level //! header
explaining the format + KDF domain separation + parameter-pinning
rationale, an ASCII diagram of the 109-byte payload layout pinned by
a static assertion, doc-comments on the four public items, and named
slice-range constants for the offset arithmetic.
production_params() is replaced with a top-level const so the "pinned,
do not change once shipped" property is visible at every use site.
Refs: docs/superpowers/specs/2026-05-04-security-polish-design.md (Phase 3)
Refs: docs/superpowers/reviews/2026-05-04-architecture-review.md (P1.7)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Wraps ssh-key's PublicKey::fingerprint(HashAlg::Sha256). Output format
matches ssh-keygen -lf and git verify-commit --raw stderr
(SHA256:<43-char base64>). Used by the upcoming relicario-server
verify-commit rewrite (audit S1).
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
OpenSSH-format keypair generation, signing, and verification.
Foundation for device authentication.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HOTP requires incrementing and persisting the counter after each use.
Without vault-save machinery in compute_totp_code, HOTP would desync
immediately. Now returns HotpNotSupported error.
TOTP and Steam codes continue to work.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- AttachmentId now uses 16 bytes of SHA-256 (128 bits) instead of 8,
requiring ~2^64 work for birthday collision instead of ~2^32.
- Added is_valid() to ItemId and AttachmentId for path traversal
prevention during backup restore.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Backup KDF was passing raw passphrase bytes to Argon2id without NFC
normalization, causing cross-platform restore failures for non-ASCII
passphrases (macOS NFD vs Linux NFC).
Now matches derive_master_key behavior from crypto.rs.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Brand name uses capital R in user-facing text — extension UI strings,
CLI clap help / descriptions / error prose, markdown docs. Lowercase
preserved for the binary command, crate names, npm package, file
paths, env vars, and code identifiers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- import_lastpass.rs: note that password and extra are intentionally
not trimmed (leading/trailing whitespace is significant for both).
- cmd_import_lastpass: document the coupling between the
ImportWarning message strings and the CLI summary's "skipped"
filter — partial-import warnings (TOTP/URL) must not contain
the word "skipped".
Comment-only; no behavior change. Catches I1 and M5 from the
final code review without taking on the cross-cut WarningKind
enum refactor (deferred to a follow-up if it ever ships).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bad URLs in login rows downgrade to url: None with a warning
rather than skipping the row. Header mismatches (extra columns,
wrong order) surface ImportCsvHeader. Quoted commas, multi-line
extra, unicode all parse cleanly via the csv crate's defaults.
Rows with url == "http://sn" map to SecureNoteCore with extra
copied verbatim into the body. LastPass-packed structured data
(credit cards, addresses) flows through unparsed — users can
re-categorize manually post-import.
SecureNote rows skip the password-required check that applies
to Logins.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Successful base32 decode attaches a SHA1/6/30s Totp config to
LoginCore.totp. Bad base32 emits a warning and imports the login
without TOTP rather than skipping the row entirely.
Refactors map_row to return (Option<Item>, Option<ImportWarning>)
so a single row can produce both an item and a warning.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Map LastPass grouping/fav/extra columns to relicario item metadata.
Grouping becomes item.group, fav="1" sets item.favorite, extra becomes item.notes.
Multi-line extra via CSV quoting round-trips correctly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Pins the parse_lastpass_csv signature and ImportWarning shape.
A single LastPass row with name/url/username/password round-trips
to a Login item with a freshly-minted ID. Header validation
rejects shape mismatches with a clear message.
TOTP, grouping, fav, SecureNote rows, and error paths land in
Tasks 3-6.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adds csv = "1" to relicario-core; introduces
ImportCsvHeader and ImportCsvFormat. Foundation for the
import_lastpass module landing in Task 2.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
pack_backup / unpack_backup ship the magic header, format version,
Argon2id KDF, XChaCha20-Poly1305 AEAD, and zstd-compressed JSON
envelope. Empty-vault round-trip is the foundation; later tasks
add items, attachments, image, and git history.
Adds zstd, tar, base64 to relicario-core; introduces
BackupBadMagic / BackupUnsupportedVersion / BackupSchemaMismatch.
Foundation for the backup module landing in Task 2.
Also ports TOTP RFC 6238 compute to relicario-core::item_types::totp
so native + CLI + WASM share one implementation (audit H5: CSPRNG
via core's Uniform-sampling generator).
Adds hmac = "0.12" and sha1 = "0.10" to relicario-core deps to support
HOTP/TOTP HMAC with Sha1/Sha256/Sha512. RFC 6238 test vector (t=59,
SHA-1, 8 digits) passes: "94287082".
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.
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>
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>