- Add settings-security.ts with renderSecuritySection / teardownSecuritySection
- Three states: amber warning (no QR), green status (QR set up), modal overlay (show/print SVG)
- Device list with inline revoke; passphrase collected via prompt()
- QR payload never written to chrome.storage; only recovery_qr_generated_at timestamp stored
- Add generate_recovery_qr / unwrap_recovery_qr message types to messages.ts + POPUP_ONLY_TYPES
- Add SW handlers in popup-only.ts delegating to wasm_generate_recovery_qr / wasm_unwrap_recovery_qr
- Declare wasm_generate_recovery_qr and wasm_unwrap_recovery_qr in wasm.d.ts
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Renders the 16-optimized SVG (icons/relicario-logo-16.svg) inline
before the brand text in .vault-sidebar__header. Sized to 20×20 px
with flex-shrink: 0 so it survives narrow-pane wraps. The header
already had display: flex + gap: 8px, so the layout absorbed the new
element without further changes. Popup surface is untouched (this
override is scoped to .vault-sidebar__header .brand-logo).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.5.0 Plan A — Security Fixes + Repo Cleanup. 7 commits, ~800 net
insertions across the Rust workspace. Four items delivered:
- S1 (HIGH-severity authentication bypass fix): rewrite verify_commit
in relicario-server. The previous implementation accepted any
GOODSIG/Good signature line on stderr, ignoring whether the signing
key was registered or revoked. The new implementation:
* builds a temp gpg.ssh.allowedSignersFile from devices.json at the
commit (no global git-config mutation)
* parses the SHA-256 fingerprint from `git verify-commit --raw`
stderr via regex
* checks revocation FIRST (revoked entries may have been removed
from devices.json), with the historical-commit case
(committer_ts < revoked_at) explicitly allowed
* uses committer date (GIT_COMMITTER_DATE / `git show -s
--format=%ct`), not author date or wall clock
* tightened the bootstrap guard to require BOTH devices and revoked
to be empty (closes an empty-devices.json privilege-escalation
route present in the original code)
* 4 acceptance integration tests build real on-disk repos with
SSH-signed commits and verify each scenario
- S2 (tar archive path-traversal hardening): replace
tar::Archive::unpack with safe_unpack_git_archive. Located in
relicario-core (per-spec, so integration tests can reach it without
the bytes-in/bytes-out invariant breaking). Validates each entry's
type (rejects symlinks/hardlinks), path components (rejects '..',
RootDir, Windows drive Prefix), and declared size (rejects
individual or cumulative > 100×compressed-or-1-GiB whichever is
lower). The CLI's restore path adds a paranoid OS-level
starts_with(.git/) check on the joined destination as
defense-in-depth even after textual validation. 5 acceptance tests
cover path traversal, symlinks, oversized headers (header claim of
2 GiB tested without allocating disk).
- S3 (RELICARIO_* env-var audit): docs/SECURITY.md gains a
"Configuration env vars" section enumerating each variable, its
purpose, and trust assumption. Active-in-all-builds variables
(RELICARIO_IMAGE, RELICARIO_GITEA_*) are documented; debug-only
variables (RELICARIO_NO_GROUPS_CACHE, RELICARIO_TEST_*) are gated
behind cfg(debug_assertions) so the env-var lookup is removed from
--release binaries.
- C1 (stale feature branch prune): 5 merged feature branches and
3 worktrees pruned interactively per dev report.
- Bonus: 4d02a50 fixes pre-existing clippy warnings across
crates/relicario-{core,cli} (deref operators, Option::is_none_or
vs map_or(true, ...), iter_mut().enumerate() patterns,
div_ceil()) so the workspace builds clean under `-D warnings`.
Merge resolution: docs/SECURITY.md had a conflict where main's F11/F12
(Device Authentication paragraph naming relicario-server + simplified
"Device registration is optional" line) collided with Plan A's S3
section. Resolved by keeping both — F11/F12's wording for the
Device Authentication section, then Plan A's "Configuration env vars"
section appended below.
Cargo.lock regenerated. The previous committed lock was stale since
commit 8855078 (--totp-qr); cargo test on both devs' worktrees
produced identical regenerated locks. Plan A genuinely added regex +
tempfile to relicario-server (both already transitively present from
relicario-cli), so no new top-level deps; the Cargo.lock churn is
catch-up of crate-version bumps that have happened since the last
commit-of-record.
Tests: 248 cargo tests pass; extension tests unchanged (336/8 with 8
pre-existing device-auth scaffolding failures).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
v0.5.0 Plan B — Extension UX Polish + Bug Fixes. 15 commits, 22 files,
+853/-33 lines, all in extension/. Five features delivered:
- P4: ERROR_COPY centralized map; popup humanizeError now a thin shell
over lookupErrorCopy; fullscreen tab gets friendly title/body/CTA blocks
(closes B2). Generated test enumerates every grep'd error code so the
registry can't drift.
- B1: applyGeneratedPassword dispatches a synthetic input event after
the regenerate handler sets the password value, so the strength-meter
listener re-rates the new value.
- P1: end-to-end password coloring — pure colorizePassword utility,
chrome.storage.sync round-trip via applyColorScheme, CSS rules with
custom properties, four reveal surfaces (popup item-detail, vault
item-detail, field-history, generator preview), boot wiring + storage
listener, Display section in settings with color pickers + swatch +
reset.
- P3: .form-lower wrapper constrains lower form sections (notes,
custom-fields, attachments, actions) to the same max-width: 960px
envelope as .form-grid above, gated on surface === 'fullscreen' so
the popup is unaffected.
- P2: finishSetup() opens the fullscreen vault tab and best-effort
closes the setup tab after successful device registration. Both
create-new and attach-existing flows funnel through it.
Implementation notes:
- vault.ts uses event delegation on the stable #vault-app root for
.error-cta clicks (better than the plan's per-render handler attach;
survives re-renders without leaking listeners).
- fields.ts gained a kind: 'password' | 'concealed' option on
ConcealedRowOpts so wireFieldHandlers can apply colorizePassword
selectively at the shared rendering layer.
- New WASM stub at src/__stubs__/relicario_wasm.stub.ts + vitest config
alias lets unit tests import setup.ts without exploding on the
runtime-only WASM module.
Tests: +28 (336/8 vs main's 308/8); 8 pre-existing device-auth
scaffolding failures unchanged. Builds clean: cargo wasm + Chrome
bundle + Firefox bundle.
Manual acceptance items (P3 viewport sweep at 1920/1440/1024/768,
P2 setup-flow smoke) deferred to user's pre-tag smoke walk.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After successful device registration (state.configPushed = true), the
wizard now opens vault.html in a new tab and closes the setup tab.
Both create-new and attach-existing flows funnel through the same
finishSetup() handler. Closing the setup tab is best-effort --
chrome.tabs.remove failures don't block the vault open.
Add src/__stubs__/relicario_wasm.stub.ts + vitest.config alias so
setup.ts can be imported in unit tests without the runtime WASM file.
Exclude the stubs dir from the webpack/tsc build in tsconfig.json.
Notes, custom-fields disclosure, attachments disclosure, and form-actions
in fullscreen logins now sit inside a .form-lower wrapper with the same
max-width: 960px; margin: 0 auto envelope as .form-grid above. Removes
the visual rhythm break at the 2-col -> full-width transition.
Popup keeps its current single-column behavior (gated on surface flag).
Import applyColorScheme in popup.ts and vault.ts, await it at boot,
and register a chrome.storage.onChanged listener so live color-picker
changes take effect without a reload.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The groups-cache opt-out is a developer debugging knob, not a
user-facing config. Gating the env-var lookup behind cfg!(debug_assertions)
makes release builds ignore the variable; the optimiser removes the
lookup entirely, so the variable name doesn't appear in release binary
strings output.
Doc-comments updated to reflect the new behaviour.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Adds a "Configuration env vars" section listing every RELICARIO_*
variable read by production code, with purpose and trust boundary.
Splits user-facing vars from debug-only ones (cfg(debug_assertions))
to make the attack surface explicit for security reviewers.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add data-field-kind attribute to renderConcealedRow so wireFieldHandlers
can distinguish password fields from other concealed rows (TOTP secrets,
CVV, PIN, private keys). Apply colorizePassword() on reveal when kind is
"password"; plain textContent otherwise. Pass kind through renderSections
for custom-section password fields.
Import colorizePassword and post-process .revealed value cells after
innerHTML render, replacing escaped-HTML text with colored spans via
the valueStore plaintext lookup.
Programmatic input.value = newPassword does not fire input events, so
the strength-meter listener at shared/form-affordances/password-tools.ts:65
never re-rates the new value — meter stays stuck on the prior reading.
Extract applyGeneratedPassword(input, value) helper that sets value, type,
then dispatches new InputEvent('input', { bubbles: true }). Vitest covers
the dispatch + a sanity check that bubbling listeners fire.
Replaces raw escapeHtml(state.error) renders with lookupErrorCopy()-driven
title/body/CTA blocks. vault_locked specifically gets an 'Unlock vault'
CTA that refocuses the passphrase input. Other CTAs route to setup.html
or chrome.runtime.reload().
Closes B2; concludes P4.
verify_commit previously loaded devices.json/revoked.json and threw
both away, accepting any commit whose stderr contained "GOODSIG" or
"Good signature". This left device registration and revocation as
no-ops: unregistered keys could push, revoked keys kept working.
The fix:
- Build a temp gpg.ssh.allowedSignersFile from devices.json at the
commit, passed via GIT_CONFIG_COUNT/KEY/VALUE env (no global git
config mutation).
- Run git verify-commit --raw and parse SHA256 fingerprint from stderr
regardless of exit code (SSH git outputs the "Good" line even for
keys not in allowed-signers, with "No principal matched" + exit 1).
- Check revoked.json FIRST: reject if committer_ts >= revoked_at;
accept historical commits (committer_ts < revoked_at).
- Reject if fingerprint is not in active devices.json.
- Bootstrap: accept only when BOTH devices.json AND revoked.json are
empty/absent (not just devices.json alone).
Acceptance: 4 integration tests covering the matrix.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Updates each Status: line from "Proposed; needs user decision" to
the actual fix-commit SHA. The audit doc now records the full state:
6 trivial findings fixed in the initial 900ccf1 pass; 8 deeper
findings fixed across ca059e7, 8fd9a05, 1342228, 76d092d, 9c97f9f
during v0.5.0 PM kickoff.
Pre-tag checklist: doc-audit follow-ups item is now done.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The 2026-04-11 design spec lists secure notes, secure documents, TOTP,
Firefox extension, LastPass import, and device authentication as
"Post-V1 Ideas" — most of which shipped over the following weeks.
Per the doc/architecture/overview.md convention, specs are frozen
decision artifacts and shouldn't be retro-edited; instead, add a
one-line status banner pointing readers at CHANGELOG.md and the
overview doc for current state.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The Vault Creation Flow ASCII showed only manifest.enc as init's
encrypted artifact; cmd_init has been writing settings.enc in parallel
since the VaultSettings rollout. Update the encrypt step to show both
artifacts side-by-side with independent nonces.
Below the ASCII, add a short pointer noting that the per-item lifecycle
(typed-item envelope, attachment encryption, field-history) lives in
crates/relicario-core/ARCHITECTURE.md and reuses the same master_key +
XChaCha20-Poly1305 primitives. The doc-audit framing is "this top-level
doc could just point at the per-crate docs" — taking that trim path.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Replaces the popup's regex-chain humanizeError with a total lookup over
every error code returned by extension/src/service-worker/router/. A
generated test discovers codes via grep so the registry can't drift.
The popup keeps its small set of regex translators for Rust/serde error
phrasing that doesn't go through the router's error vocabulary.
Subsumes B2 — fullscreen consumer lands in the next commit.
- F12: Device Authentication section now names the relicario-server crate
and its two subcommands (generate-hook, verify-commit), and notes that
signed commits without the server-side hook provide authorship only —
any pusher can still land an unsigned commit.
- F11: drop the "optional before v0.4.0" version line (v0.4.0 was never
tagged; v0.5.0 is the first release with the hook) and replace with a
one-liner: registration is optional but recommended for shared vaults.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>