Commit Graph

64 Commits

Author SHA1 Message Date
adlee-was-taken
eae07e778b merge: origin/main (Dev-E keyfile-ext + D-minors) into org-foundation
A-vs-E keep-both integration in 3 security-sensitive SW files:
- shared/messages.ts: kept E's attach_vault keyfile second-factor fields
  (secondFactor/referenceImageBytes?/keyfileBytes?) AND my 4 org message types.
- vault.ts: registerDeviceAndPersistConfig auto-merged to take BOTH E's
  factorStorage (image|keyfile) AND my handle (Task 4.5 device-key persist);
  both create/attach call sites pass (factorStorage, deviceName, h) so each
  flow does E secret-gen AND my register+persist in one coherent path.
- router/popup-only.ts: took E's 'unlock' -> handleUnlock refactor and moved my
  device-key restore + migration into handleUnlock's shared tail (after
  setCurrent, before manifest fetch) so it runs for both image + keyfile unlock.
- keyfile-unlock.test.ts: makeWasm mock gains persist_device_key + restore_device_key
  stubs (the merged create/attach/unlock flow now calls them) — no assertion weakened.

Gates: SW vitest 175/175 (18 files); cargo core+wasm 243/0; wasm32 build clean;
clippy -D warnings clean; build:all clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
2026-06-27 11:22:17 -04:00
adlee-was-taken
288956c089 feat(core,wasm): filter_by_collections single-source + org_manifest_decrypt_filtered
Add OrgManifest::filter_by_collections(&[String]) as the single source of
grant-filter logic. Refactor filter_for_member to delegate to it (no
duplicated loop). Add org_manifest_decrypt_filtered WASM binding that
decrypts + filters in core so ungranted entries never cross to JS (phase-1
SOFT/UX filter; per-collection crypto isolation is phase-2). Declare the
binding in extension/src/wasm.d.ts. Vec<String> compiled cleanly for
wasm32-unknown-unknown.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
2026-06-26 22:19:55 -04:00
adlee-was-taken
b2bed1c50e fix(core): zeroize keyfile_encode output + lock-in tests for backup-keyfile-error and malformed-vs-wrong unlock
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McMF5pUJbCMricmFEnf2sG
2026-06-26 21:45:14 -04:00
adlee-was-taken
ce141480b9 merge: origin/main (Dev-D keyfile, d649203) into org-foundation
Additive conflict in extension/src/wasm.d.ts resolved by keeping BOTH the
org_* declarations (org_unwrap_key/org_manifest_decrypt/org_manifest_encrypt)
and the keyfile_* declarations (unlock_with_secret/keyfile_encode/keyfile_decode).
Rust lib.rs (core + wasm) auto-merged cleanly with both feature sets.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
2026-06-26 21:35:56 -04:00
adlee-was-taken
eabc93bd84 fix(core): zeroize keyfile decode intermediate + clearer errors + error-arm tests 2026-06-25 21:15:11 -04:00
adlee-was-taken
92febf38f1 refactor(core,wasm): zeroized ed25519 seed decode in core; org_unwrap_key reuses it
Review fixes for Task 1 (org_unwrap_key). Public signature unchanged.

- Add device::extract_ed25519_seed -> Zeroizing<[u8;32]>: length-checks the
  slice then copy_from_slice straight into the Zeroizing buffer, so the raw
  seed never lands in a plain [u8;32] (Important #1).
- Refactor device::sign() to call the new helper (pure refactor; device/sign
  roundtrip tests unchanged and green) (Important #2).
- org_unwrap_key now calls relicario_core::device::extract_ed25519_seed
  directly; drop the duplicated local decode_device_seed helper and remove the
  redundant ssh-key dep from relicario-wasm (core already pins it) (Important #2).
- Device-key-form finding (OpenSSH PEM, not base64; spec name matches reality)
  moved onto the core helper, condensed copy kept on org_unwrap_key.
- Test helper keeps the private key as Zeroizing<String> and passes &priv
  (derefs to &str) — no plain-String copy (Minor #3).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014s527M917W47LDrfQ4t47g
2026-06-25 21:13:49 -04:00
adlee-was-taken
3b7099c6f4 feat(core): key-file armor (relicario-keyfile-v1) encode/decode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McMF5pUJbCMricmFEnf2sG
2026-06-25 21:09:40 -04:00
adlee-was-taken
b08b388d5d feat(core+cli): SecondFactor hint on ParamsFile (default image, back-compat)
Add SecondFactor enum (Image | Keyfile, serde lowercase) to relicario-core::crypto,
re-export from lib.rs alongside KdfParams.  Add #[serde(default)] second_factor field
to ParamsFile in relicario-cli::session so existing params.json files (no field) still
parse as Image.  for_new_vault() initialises the field to Image.  No unlock logic
changed; that is deferred to Task 4.  TDD: tests written RED first, then GREEN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01McMF5pUJbCMricmFEnf2sG
2026-06-25 21:02:13 -04:00
adlee-was-taken
5a5cf04b8f release: v0.8.2 — extension vault-creation fix (binary transport)
Extension-only patch: fixes vault create/attach in the browser extension, where
chrome.runtime.sendMessage silently dropped ArrayBuffer payloads (carrier JPEG →
0 bytes → opaque "no SOF marker found in JPEG"). See CHANGELOG for the full set.

- Version bumps: extension 0.7.0→0.8.2 (it had no changes in 0.8.0/0.8.1);
  relicario-core/-cli/-wasm 0.8.1→0.8.2 for unified-tag consistency. The Rust
  core/CLI are functionally unchanged from v0.8.1. relicario-server stays 0.1.1
  (independent version line; no functional change).
- CHANGELOG.md: v0.8.2 section. STATUS.md / ROADMAP.md: v0.8.2 landed, v0.9.0
  flagged as ready to kick off.

Verify: cargo build/test/clippy -D warnings all green (35 test binaries);
extension 435/435 vitest, build:all clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pe8qw5KePDqAEBsAxnVQuJ
2026-06-25 20:26:36 -04:00
adlee-was-taken
2fa4d6824c release: v0.8.1 — org item-type parity + collection-scoped attachments 2026-06-20 22:24:22 -04:00
adlee-was-taken
50b5c01291 release: v0.8.0 — enterprise org vault
Bump core/cli/wasm 0.7.0 -> 0.8.0; finalize CHANGELOG v0.8.0 header. Git-native multi-user org vaults (core org module + ECIES X25519 wrap, server signature-verifying pre-receive hook, CLI admin + item CRUD); 332/0 workspace tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RXpTHcQzw1n8qjYwZqruzQ
2026-06-20 16:06:16 -04:00
adlee-was-taken
8bb1d779c4 docs(org): pre-stage A5 living-docs for merged core+server+CLI-admin (item-CRUD/extension TODO)
Pre-stages the A5 living-docs sweep for the already-merged A (relicario-core org
module) + C (relicario-server pre-receive hook) + CLI admin/rotate/status-audit
work, so the final A5 sweep (after Dev-B B9-B14 merges) is fast.

Adds org sections to docs/FORMATS.md (org repo wire formats + wrapped-key blob
layout), docs/CRYPTO.md (ECIES X25519 wrap/unwrap, no-Argon2id contrast, rotate
re-encryption), docs/SECURITY.md (signature-verifying hook, owner-only elevation,
audit vocabulary, honest limitations), DESIGN.md (org-master-key secrets row +
server org mode + deps), core/cli ARCHITECTURE.md (org module + org_session), and
an Unreleased CHANGELOG entry.

B item-CRUD (org add/get/list/edit/rm/restore/purge + main.rs wiring) and extension
parity are left as explicit TODO. STATUS/ROADMAP mark-shipped and
extension/ARCHITECTURE are deferred to the full A5 (track not yet landed; Dev-D
deferred). All cited code constants pinned with file:line per living-docs discipline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-20 15:23:27 -04:00
adlee-was-taken
743a46f3d5 test(core/org): full org lifecycle integration tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-19 23:44:15 -04:00
adlee-was-taken
409ddce049 feat(core/org): encrypt/decrypt_org_manifest vault wrappers
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-19 23:24:55 -04:00
adlee-was-taken
631608e6e5 refactor(core/org): drop unreachable unwrap in unwrap_org_key; assert hex in OrgId test
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-19 23:14:27 -04:00
adlee-was-taken
ca4936cf95 feat(core/org): org types, manifest, and X25519 key wrap/unwrap (Zeroizing KDF)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-19 23:07:13 -04:00
adlee-was-taken
da4dc44f80 feat(core/org): add x25519-dalek dep + stub org module
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TJo44YM3UbBjro2fG6NrKy
2026-06-19 22:51:27 -04:00
adlee-was-taken
7c7efa7c43 release: v0.7.0 — extension restructure complete (Plan C Phases 3/4/6)
Completes the extension restructure begun in v0.6.0. Phases 3 (setup
wizard SW migration + step registry), 4 (vault.ts split + vault_locked
lift), and 6 (get_vault_status + sidebar status indicator) all merged to
main (9df2fee, 3b8368d, 397cc78) via three parallel worktree streams.

This commit is the release-prep wrap-up:
- Version bump to v0.7.0 across the three relicario crates + Cargo.lock,
  extension/package.json, and both extension manifests (the manifests had
  lagged at 0.5.0 — corrected here).
- CHANGELOG.md v0.7.0 entry.
- STATUS.md: extension restructure moved to shipped; Phases 3/4/6 landing
  section added.
- ROADMAP.md: v0.7.0 row added; Up-next now command palette.
- extension/ARCHITECTURE.md: all three phases integrated (new vault-*
  modules, setup-steps.ts, get_vault_status protocol + status indicator,
  vault_locked lift, git-host sync cache).
- Plan completion checkboxes ticked.

Task 7.1 verification: done-criteria sweep all green; 423/423 vitest;
build:all clean (only the pre-existing 4MB WASM size warning).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 22:50:17 -04:00
adlee-was-taken
d2d11a4c9f chore: release v0.6.0
Rolls up four weeks of post-v0.5.0 work into one tag:

- Phase 2B polish foundation + form layout (2026-05-02, 5da1e52)
- v0.5.1 Stream A — 3-column vault layout + toast + bottom sheet (2026-05-03, c16adc4)
- v0.5.1 Stream B — left-nav settings (2026-05-03, bd6a301)
- v0.5.1 Stream C — Recovery QR + setup wizard Style C (2026-05-03, 934dfe0)
- 1C-γ — Document item type + attachments + device registration + trash + history
- Plan B refactor (Cycles 1+2) — commands/ split, prompt_or_flag, core/WASM seam
- Vault-tab management surfaces revamp (2026-05-24..30) — settings split, devices fingerprint, trash countdown, history polish
- Doc-structure redesign (2026-05-30) — DESIGN/CRYPTO/docs/FORMATS rename + scope headers + Next: footers
- Lock-screen logo for parity with popup unlock
- 17 stale tests updated to match post-Stream-B / post-revamp components

Versions: relicario-{core,cli,wasm} → 0.6.0; extension/package.json → 0.6.0.
relicario-server stays at 0.1.0 (separate cadence).

Suite status at tag time: 371/371 extension + 281 Rust tests green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 21:17:54 -04:00
adlee-was-taken
5e7023fcc1 docs: add scope headers + Next: footers to all tour docs
Each of the eight tour docs (README, DESIGN, docs/CRYPTO,
docs/FORMATS, docs/SECURITY, crates/relicario-core/ARCHITECTURE,
crates/relicario-cli/ARCHITECTURE, extension/ARCHITECTURE) now
declares its scope in a blockquote under its H1 and ends with a
single-line "Next:" pointer to the next doc in the canonical
reading order: README → DESIGN → CRYPTO → FORMATS → SECURITY →
core → cli → extension.

Also trimmed README's mid-section "Architecture" stub to a one-
paragraph pointer at DESIGN.md (was duplicating cross-codebase
content and referencing a non-existent docs/architecture/ tree).

Renamed docs/CRYPTO.md's H1 from "Relicario — Architecture" to
"Relicario — Crypto Pipeline" to match the file's renamed scope.

Spec: docs/superpowers/specs/2026-05-30-doc-structure-redesign-design.md
2026-05-30 15:36:46 -04:00
adlee-was-taken
cf7478d178 docs: refresh per-crate ARCHITECTURE — missing core modules + CLI commands
Punch items from doc audit:
- relicario-core: module map missing 5 public modules (backup,
  device, import_lastpass, recovery_qr, tar_safe); added with
  1-2 sentence descriptions in the existing voice.
- relicario-core: "ed25519-dalek is a dependency placeholder" was
  stale — device.rs now consumes it for signing/verify/keypair.
- relicario-cli: Rate (zxcvbn scoring) and RecoveryQr (generate/unwrap)
  commands were absent from Key flows; added.
- relicario-cli: "Backup-passphrase-style commands (none yet)" rewritten
  — Backup (export/restore .relbak) and Import (lastpass) both shipped.
- relicario-cli: module map refreshed — handlers moved out of main.rs
  into commands/, plus prompt.rs/parse.rs/device.rs/gitea.rs surfaced.

Stale main.rs:NNNN line citations on individual flows are not fixed
here — those handlers now live in commands/*.rs and warrant a deeper
pass later.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 13:24:49 -04:00
adlee-was-taken
8e81ef8b8b chore(license): switch from MIT to GPL-3.0-or-later
Adds top-level LICENSE (GPL-3.0 full text), updates README, and sets
`license = "GPL-3.0-or-later"` on all four crate manifests.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 12:30:49 -04:00
adlee-was-taken
fc9264e9ae feat(wasm): add parse_month_year, base32_decode_lenient, guess_mime exports
Plan B Phase 8 — three #[wasm_bindgen] exports for the parsers migrated
in Phase 7, mirrored in extension/src/wasm.d.ts under "Pure parsers
(no session needed)". snake_case JS naming consistent with every
existing export; SessionHandle not required.

- parse_month_year(s) → { month, year } via js_value_for
- base32_decode_lenient(s) → Uint8Array
- guess_mime(filename) → string

Tests in session_tests mod cover the OK paths; error-path / JsValue
serialization can't be tested natively (JsError construction panics
off-wasm) and is covered in core (time::tests + base32::tests).

Plan C will wire SW message handlers consuming these exports in a
future round; this commit delivers only the seam.

Includes simplify-feedback fixes:
- relicario-core lib.rs module-list mentions base32 and mime
- item_types/totp.rs neighbour comment unified to ///-style block

cargo test --workspace: green
cargo clippy --workspace: silent
cargo build -p relicario-wasm --target wasm32-unknown-unknown: clean
cd extension && npm run test: 17 pre-existing failures only (baseline)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-09 11:33:40 -04:00
adlee-was-taken
03f2a1b58e refactor(core,cli): migrate CLI parsers to relicario-core, parse.rs becomes shim
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>
2026-05-09 11:12:05 -04:00
adlee-was-taken
e5d63ab196 refactor(core): extract base32 module, dedupe two RFC 4648 impls
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>
2026-05-09 10:46:17 -04:00
adlee-was-taken
f8296fa03b docs(core): drop intra-doc link to private RECOVERY_PRODUCTION_PARAMS
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>
2026-05-08 21:53:20 -04:00
adlee-was-taken
229e483430 docs(core): bring recovery_qr.rs to the documented-zone standard
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>
2026-05-06 19:33:40 -04:00
adlee-was-taken
cf66bd97b7 chore: bump crate and extension versions to 0.5.0
Catches the workspace and the extension manifests up to the v0.5.x
release line (was still showing 0.2.0).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-05 17:49:09 -04:00
adlee-was-taken
f17944a404 fix(core,wasm): correct QR version comment, expect msg, zeroize image_secret in closure 2026-05-03 21:09:02 -04:00
adlee-was-taken
762a008171 test(core): recovery_qr roundtrip + error cases 2026-05-03 20:53:59 -04:00
adlee-was-taken
f93bce7388 chore(core): re-export recovery_qr module 2026-05-03 20:51:36 -04:00
adlee-was-taken
8eabaf5f31 feat(core): recovery_qr generate + unwrap + SVG functions 2026-05-03 20:51:33 -04:00
adlee-was-taken
04142dc116 feat(core): add derive_master_key_raw + RecoveryQr error variant 2026-05-03 20:51:29 -04:00
adlee-was-taken
8739f1f67b chore(core): add qrcode dependency for recovery QR 2026-05-03 20:48:38 -04:00
adlee-was-taken
4d02a50cc8 chore(core): fix pre-existing clippy warnings (-D warnings gate)
Resolves pre-existing lint issues in imgsecret.rs, time.rs, totp.rs,
and crypto.rs that blocked the cargo clippy --workspace -D warnings
gate. No logic changes: loop-index → iterator, manual div_ceil →
.div_ceil(), manual range contains → .contains(), auto-deref cleanup.

Also fixes pre-existing warnings in relicario-cli (main.rs, session.rs,
device.rs, gitea.rs, helpers.rs, test helpers): dead_code suppression,
too_many_arguments, literal_with_empty_format_string, manual_char_cmp,
map_or → is_none_or, and repeat().take() → vec! in test helpers.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 19:32:45 -04:00
adlee-was-taken
6a1c6d5875 fix(core,cli): harden backup-restore tar unpack against path traversal (audit S2)
cmd_backup_restore previously called tar::Archive::unpack with default
settings, allowing malicious .relbak archives to escape the target
directory via .. entries, absolute paths, or symlinks. No size cap
meant tar bombs could exhaust disk space.

Replaced with relicario_core::safe_unpack_git_archive which:
- Rejects .. (ParentDir), absolute (RootDir), and drive-prefix
  (Prefix) components with "path traversal blocked" error.
- Rejects symlinks and hardlinks outright.
- Checks declared header size before reading body; rejects entries or
  cumulative totals exceeding the caller's cap.
- Returns (relative-path, bytes) pairs; the CLI re-checks
  dest.starts_with(git_dir) after OS-level path resolution.
- CLI cap: min(100 × compressed size, 1 GiB).

Acceptance: 5 unit tests in relicario-core (traversal, absolute path,
symlink, size bomb, happy path); existing CLI backup roundtrip tests
remain green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-02 17:16:11 -04:00
adlee-was-taken
8a72b5e192 feat(core): add device::fingerprint helper for SSH SHA256 fingerprints
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>
2026-05-02 16:23:10 -04:00
adlee-was-taken
dc683c7e4c feat(core): add device module with ed25519 signing
OpenSSH-format keypair generation, signing, and verification.
Foundation for device authentication.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-05-02 12:13:57 -04:00
adlee-was-taken
628e2bd636 fix(core): disable HOTP with clear error (audit I6)
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>
2026-05-02 01:36:31 -04:00
adlee-was-taken
466efe4b8a fix(core): expand AttachmentId to 128 bits, add is_valid (audit I2, B4)
- 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>
2026-05-02 01:32:48 -04:00
adlee-was-taken
bbdbcca87b fix(core): NFC normalize backup passphrase (audit B2)
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>
2026-05-02 01:29:08 -04:00
adlee-was-taken
39ae2ecbf3 style: capitalize "Relicario" in prose / UI / CLI help
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>
2026-05-01 17:29:10 -04:00
adlee-was-taken
cf3960186c docs(core,cli): document implicit contracts flagged in code review
- 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>
2026-04-30 18:55:46 -04:00
adlee-was-taken
6f2e868892 feat(core): import_lastpass — URL/header robustness
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.
2026-04-29 23:09:23 -04:00
adlee-was-taken
0841bddcb5 feat(core): import_lastpass — SecureNote rows
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>
2026-04-29 23:06:03 -04:00
adlee-was-taken
c4905c5ee7 feat(core): import_lastpass — TOTP base32 → TotpConfig
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>
2026-04-29 23:02:16 -04:00
adlee-was-taken
16888d5a3a feat(core): import_lastpass — group, favorite, notes
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>
2026-04-29 22:57:37 -04:00
adlee-was-taken
9ee876cc4b feat(core): import_lastpass parser — happy-path Login
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>
2026-04-29 22:52:20 -04:00
adlee-was-taken
768f0d39a5 feat(core): add csv dep + import error variants
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>
2026-04-29 22:47:06 -04:00
adlee-was-taken
e02f62f961 test(core): backup error paths
Covers bad magic, unsupported version, wrong passphrase, truncation,
and tampered ciphertext. The wrong-passphrase / tampered-tag pair both
collapse to RelicarioError::Decrypt — same opaque-failure contract as
the live vault.
2026-04-27 22:42:44 -04:00