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>
This commit is contained in:
adlee-was-taken
2026-05-01 17:29:10 -04:00
parent 79b10d6a18
commit 39ae2ecbf3
44 changed files with 91 additions and 91 deletions

View File

@@ -96,7 +96,7 @@
### Fixed
- **Setup wizard could silently overwrite an existing vault.** Pointing the
wizard at a remote that already contained a relicario vault would clobber
wizard at a remote that already contained a Relicario vault would clobber
`manifest.enc`, `.relicario/salt`, and friends with no warning. The wizard
now probes the remote after the connection test and refuses to create a
new vault on top of an existing one. Affected users whose vault was wiped

View File

@@ -1,4 +1,4 @@
# CLAUDE.md — relicario
# CLAUDE.md — Relicario
## Working with the user
@@ -8,7 +8,7 @@
## What is this
relicario is a git-backed, self-hostable password manager with a Rust core. Two-factor vault decryption: passphrase + a reference JPEG carrying a 256-bit secret embedded via DCT steganography. The server only ever sees opaque ciphertext.
Relicario is a git-backed, self-hostable password manager with a Rust core. Two-factor vault decryption: passphrase + a reference JPEG carrying a 256-bit secret embedded via DCT steganography. The server only ever sees opaque ciphertext.
## Build and test

View File

@@ -1,8 +1,8 @@
<p align="center">
<img src="extension/icons/relicario-logo.svg" alt="relicario" width="128" height="128">
<img src="extension/icons/relicario-logo.svg" alt="Relicario" width="128" height="128">
</p>
# relicario
# Relicario
A git-backed, self-hostable password manager where decryption requires two independent factors: a passphrase you memorize and a reference JPEG that carries a hidden secret. Compromise of either factor alone is insufficient.
@@ -23,7 +23,7 @@ Your reference photo (something you have)
your device (opaque ciphertext)
```
At vault creation, relicario embeds a random 256-bit secret into a carrier JPEG using DCT steganography. This photo becomes your **reference image** — a second factor that lives on your devices (and optionally as a "dead drop" on social media, since it survives JPEG re-encoding and mild cropping).
At vault creation, Relicario embeds a random 256-bit secret into a carrier JPEG using DCT steganography. This photo becomes your **reference image** — a second factor that lives on your devices (and optionally as a "dead drop" on social media, since it survives JPEG re-encoding and mild cropping).
To unlock the vault, you provide your passphrase and point the client at the reference image. The client extracts the hidden secret, concatenates it with your passphrase, and runs Argon2id to derive the master key. Everything else follows from there.
@@ -58,7 +58,7 @@ No single point of failure. The two-factor design means the passphrase alone can
| LastPass | ~40-60 bits (master password only) | 1 |
| Bitwarden | ~40-60 bits (master password only) | 1 |
| 1Password | password + 128-bit Secret Key | 2 |
| **relicario** | **password + 256-bit image secret** | **2** |
| **Relicario** | **password + 256-bit image secret** | **2** |
### What we don't protect against

View File

@@ -1,4 +1,4 @@
//! relicario CLI — the platform layer for the relicario password manager.
//! Relicario CLI — the platform layer for the Relicario password manager.
//!
//! See module docs for the unlock flow and vault layout.
@@ -14,7 +14,7 @@ use clap::{Parser, Subcommand};
#[command(
name = "relicario",
version,
about = "Git-backed password manager with reference-image two-factor unlock"
about = "Relicario — git-backed password manager with reference-image two-factor unlock"
)]
struct Cli {
#[command(subcommand)]
@@ -462,7 +462,7 @@ fn cmd_init(image: PathBuf, output: PathBuf) -> Result<()> {
".relicario/salt", "manifest.enc", "settings.enc",
]).status()?;
let status = crate::helpers::git_command(&root, &[
"commit", "-m", "init: new relicario vault (format v2)",
"commit", "-m", "init: new Relicario vault (format v2)",
]).status()?;
if !status.success() { anyhow::bail!("git commit failed"); }
@@ -1477,7 +1477,7 @@ fn cmd_backup_restore(input: PathBuf, target: PathBuf) -> Result<()> {
if target.join(".relicario").exists() {
anyhow::bail!(
"target dir already contains a relicario vault; restore refuses to overwrite — use an empty directory: {}",
"target dir already contains a Relicario vault; restore refuses to overwrite — use an empty directory: {}",
target.display()
);
}

View File

@@ -66,7 +66,7 @@ fn restore_refuses_non_empty_target() {
.unwrap();
assert!(!out.status.success());
let err = String::from_utf8(out.stderr).unwrap();
assert!(err.contains("already contains a relicario vault"), "stderr: {err}");
assert!(err.contains("already contains a Relicario vault"), "stderr: {err}");
}
#[test]

View File

@@ -1,4 +1,4 @@
//! Unified error type for the relicario-core crate.
//! Unified error type for the Relicario core crate.
//!
//! Every fallible function in this crate returns [`Result<T>`], which is an alias
//! for `std::result::Result<T, RelicarioError>`. Using a single error enum keeps the
@@ -7,7 +7,7 @@
use thiserror::Error;
/// All errors that can originate from relicario-core operations.
/// All errors that can originate from Relicario core operations.
///
/// Variants are ordered roughly by the pipeline stage where they occur:
/// KDF -> encryption -> decryption -> format parsing -> item lookup -> image
@@ -40,15 +40,15 @@ pub enum RelicarioError {
UnsupportedFormatVersion { found: u8, expected: u8 },
/// Backup file's first 4 bytes don't match the "RBAK" magic.
#[error("not a relicario backup file")]
#[error("not a Relicario backup file")]
BackupBadMagic,
/// Backup format version is newer than this binary supports.
#[error("backup created by a newer relicario; upgrade required")]
#[error("backup created by a newer Relicario; upgrade required")]
BackupUnsupportedVersion { found: u8, expected: u8 },
/// Backup envelope schema version doesn't match.
#[error("backup envelope schema v{found}; this relicario reads v{expected}")]
#[error("backup envelope schema v{found}; this Relicario reads v{expected}")]
BackupSchemaMismatch { found: u32, expected: u32 },
/// CSV header doesn't match the LastPass column layout.
@@ -157,7 +157,7 @@ mod tests {
#[test]
fn backup_errors_carry_useful_messages() {
let bad = RelicarioError::BackupBadMagic;
assert!(format!("{}", bad).contains("not a relicario backup file"));
assert!(format!("{}", bad).contains("not a Relicario backup file"));
let ver = RelicarioError::BackupUnsupportedVersion { found: 0x02, expected: 0x01 };
let s = format!("{}", ver);

View File

@@ -1,6 +1,6 @@
//! # relicario-core
//!
//! Platform-agnostic core library for the relicario password manager.
//! Platform-agnostic core library for the Relicario password manager.
//!
//! This crate is intentionally **bytes-in/bytes-out** -- it performs no filesystem
//! access, no network I/O, and no git operations. All inputs arrive as byte slices

View File

@@ -1,4 +1,4 @@
# relicario — Architecture
# Relicario — Architecture
## System Overview

View File

@@ -1,6 +1,6 @@
# Architecture overview — relicario
# Architecture overview — Relicario
This is the cross-codebase entry point. It describes how the three relicario codebases fit together, the contracts that flow between them, and the conventions they share. It is **deliberately thin**; the deep content lives in per-codebase docs.
This is the cross-codebase entry point. It describes how the three Relicario codebases fit together, the contracts that flow between them, and the conventions they share. It is **deliberately thin**; the deep content lives in per-codebase docs.
> If you are about to make a change in a single codebase, read its `ARCHITECTURE.md` first:
>

View File

@@ -1,4 +1,4 @@
# relicario Security Audit Report
# Relicario Security Audit Report
**Date:** 2026-04-18
**Scope:** Full static review of `crates/relicario-core/`, `crates/relicario-cli/`, `crates/relicario-wasm/`, `extension/src/`, both manifests, both webpack configs, and the design spec at `docs/superpowers/specs/2026-04-11-relicario-design.md`.

View File

@@ -1,4 +1,4 @@
# relicario Core + CLI Implementation Plan
# Relicario Core + CLI Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,4 +1,4 @@
# relicario Credential Capture Implementation Plan
# Relicario Credential Capture Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,4 +1,4 @@
# relicario Firefox Extension Port Implementation Plan
# Relicario Firefox Extension Port Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,8 +1,8 @@
# relicario Vault Initialization Wizard Implementation Plan
# Relicario Vault Initialization Wizard Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Build a browser-based wizard that creates a new relicario vault, pushes it to Gitea/GitHub via API, downloads the reference image, and optionally configures the Chrome extension.
**Goal:** Build a browser-based wizard that creates a new Relicario vault, pushes it to Gitea/GitHub via API, downloads the reference image, and optionally configures the Chrome extension.
**Architecture:** Single HTML page (`extension/setup.html`) bundled by webpack as a new entry point. Reuses the existing git API layer and WASM module. New `embed_image_secret` function added to the WASM crate. The wizard runs entirely client-side — all crypto happens in the browser via WASM.

View File

@@ -1,4 +1,4 @@
# relicario WASM + Chrome MV3 Extension Implementation Plan
# Relicario WASM + Chrome MV3 Extension Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,4 +1,4 @@
# relicario Extension 1C-α (Foundation) Implementation Plan
# Relicario Extension 1C-α (Foundation) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,8 +1,8 @@
# relicario Extension 1C-β₁ (Typed-Item Forms) Implementation Plan
# Relicario Extension 1C-β₁ (Typed-Item Forms) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add the 5 remaining typed-item forms (SecureNote, Identity, Card, Key, Totp incl. Steam Guard) so the relicario extension can daily-drive every typed item the Rust core supports except Document.
**Goal:** Add the 5 remaining typed-item forms (SecureNote, Identity, Card, Key, Totp incl. Steam Guard) so the Relicario extension can daily-drive every typed item the Rust core supports except Document.
**Architecture:** 5-slice bottom-up sequencing. Slice 1 patches the Rust core's `compute_totp_code` to emit Steam's 5-char alphabet output. Slice 2 extracts a shared `popup/components/fields.ts` helper module (row / concealed-row / signature-block primitives) and refactors Login onto it as the reference implementation. Slices 3-5 land the 5 new types in pairs: SecureNote+Identity (no signature block), Card+Key (signature block, no live state), Totp (signature block + countdown + Steam toggle).

View File

@@ -1,8 +1,8 @@
# relicario Extension 1C-β₂ (Custom Fields + Settings + Generator UI) Implementation Plan
# Relicario Extension 1C-β₂ (Custom Fields + Settings + Generator UI) Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add custom-fields editing, a full vault-settings view, and an inline generator popover to the relicario browser extension. Completes the β phase of Plan 1C.
**Goal:** Add custom-fields editing, a full vault-settings view, and an inline generator popover to the Relicario browser extension. Completes the β phase of Plan 1C.
**Architecture:** 5-slice bottom-up. Slice 1 adds read-only `Item.sections` rendering in every type-detail view via a new `fields.ts` helper. Slice 2 adds the collapsible "▸ custom sections & fields" disclosure + add/remove UI in every type form. Slice 3 wires new popup-only messages (`get_vault_settings` / `update_vault_settings`, plus `generate_passphrase` for BIP39) — landing BEFORE the popover so Slice 4's "save as default" action is fully functional the moment it ships. Slice 4 builds the generator popover and wires it to every "gen" button. Slice 5 builds the Settings screen, wires the ⚙ picker, and connects the popover to Settings via the save-as-default action.

View File

@@ -1,4 +1,4 @@
# relicario Backup & Restore — Implementation Plan (v0.3.0 — Plan 3A)
# Relicario Backup & Restore — Implementation Plan (v0.3.0 — Plan 3A)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,4 +1,4 @@
# relicario LastPass Importer — Implementation Plan (v0.3.0 — Plan 3B)
# Relicario LastPass Importer — Implementation Plan (v0.3.0 — Plan 3B)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

View File

@@ -1,10 +1,10 @@
# relicario — Design Specification
# Relicario — Design Specification
A git-backed, self-hostable password manager with a Rust core, CLI, and Chrome browser extension. The reference image as a DCT-embedded secret carrier is the core differentiator.
## Overview
relicario is a password manager where vault decryption requires two independent factors: a passphrase the user memorizes and a reference JPEG that carries a 256-bit secret embedded via DCT steganography. The vault lives in a git repository (self-hosted on the user's own Gitea instance), and the server only ever sees opaque ciphertext. Compromise of either factor alone is insufficient to decrypt the vault.
Relicario is a password manager where vault decryption requires two independent factors: a passphrase the user memorizes and a reference JPEG that carries a 256-bit secret embedded via DCT steganography. The vault lives in a git repository (self-hosted on the user's own Gitea instance), and the server only ever sees opaque ciphertext. Compromise of either factor alone is insufficient to decrypt the vault.
Primary goals: portfolio project for adlee.work, architectural elegance, legibility-as-security (the README should read as the security proof), learning Rust, and fun to tinker with.
@@ -23,7 +23,7 @@ A collection of credentials (usernames, passwords, URLs, TOTP seeds, notes) belo
| Stolen device | Filesystem: reference image, device key, cached vault | Decrypt vault | Attacker has image_secret but not passphrase. Argon2id makes brute-force expensive. |
| Stolen device + weak passphrase | Same + feasible brute-force | Decrypt vault | Enforce minimum passphrase strength at vault creation. Universal worst case. |
| Shoulder surfer | Observed passphrase | Decrypt vault (if they also get image) | Passphrase alone insufficient — still need image_secret. |
| Credential stuffing | Leaked email/password from other breaches | Access user's accounts | relicario generates unique passwords per site. Breach of site A doesn't compromise site B. |
| Credential stuffing | Leaked email/password from other breaches | Access user's accounts | Relicario generates unique passwords per site. Breach of site A doesn't compromise site B. |
### Out of scope
@@ -79,7 +79,7 @@ With a 4-word diceware passphrase (~51 bits) and Argon2id at 64 MiB, brute-force
Compared to competitors:
- LastPass/Bitwarden: server breach exposes ~40-60 bits (master password only)
- 1Password: server breach exposes password + 128-bit Secret Key
- relicario: server breach exposes password + 256-bit image_secret
- Relicario: server breach exposes password + 256-bit image_secret
### Authenticated encryption

View File

@@ -1,4 +1,4 @@
# relicario — Credential Capture Design
# Relicario — Credential Capture Design
Experimental feature that detects login form submissions and prompts the user to save or update credentials in the vault. Configurable prompt style (notification bar or toast). Off by default.
@@ -60,7 +60,7 @@ A fixed-position bar at the top of the page, injected into the DOM:
```
┌──────────────────────────────────────────────────────────────────┐
relicario: Save login for github.com? (alee) [Save] [Never] [✕] │
Relicario: Save login for github.com? (alee) [Save] [Never] [✕] │
└──────────────────────────────────────────────────────────────────┘
```
@@ -77,7 +77,7 @@ A floating element in the bottom-right corner:
```
┌─────────────────────────────────┐
relicario │
Relicario │
│ Save login for github.com? │
│ alee │
│ [Save] [Never] [✕] │

View File

@@ -1,4 +1,4 @@
# relicario — Firefox Extension Port Design
# Relicario — Firefox Extension Port Design
Port the existing Chrome MV3 extension to Firefox. Shared TypeScript source, separate manifests, separate build outputs. No code changes to components, popup, or content script.

View File

@@ -1,6 +1,6 @@
# relicario — Standalone Vault Initialization Wizard Design
# Relicario — Standalone Vault Initialization Wizard Design
A browser-based wizard that guides new users through creating an relicario vault from scratch. Lives at `extension/setup.html`, uses the same WASM module as the extension, same terminal dark aesthetic. No server, no Rust toolchain required.
A browser-based wizard that guides new users through creating a Relicario vault from scratch. Lives at `extension/setup.html`, uses the same WASM module as the extension, same terminal dark aesthetic. No server, no Rust toolchain required.
## Scope
@@ -81,9 +81,9 @@ Two things happen:
- Show warning: "Keep this image safe. You need it alongside your passphrase to unlock the vault. Store it somewhere you won't lose it."
**Push config to extension (if available):**
- Try to detect the relicario extension via `chrome.runtime.sendMessage` with a `get_setup_state` message
- Try to detect the Relicario extension via `chrome.runtime.sendMessage` with a `get_setup_state` message
- If extension responds: push `save_setup` message with `{ config: { hostType, hostUrl, repoPath, apiToken }, imageBase64 }`. Show "Extension configured! You can now open the extension and unlock your vault."
- If extension not detected: show the config as a copyable JSON blob with instructions: "Install the relicario extension, then paste this into the setup wizard." (Or just tell them to run through the extension setup manually with the same host/token/repo.)
- If extension not detected: show the config as a copyable JSON blob with instructions: "Install the Relicario extension, then paste this into the setup wizard." (Or just tell them to run through the extension setup manually with the same host/token/repo.)
## WASM Crate Change

View File

@@ -1,6 +1,6 @@
# relicario — WASM + Chrome MV3 Extension Design
# Relicario — WASM + Chrome MV3 Extension Design
The browser extension for relicario. Compiles `relicario-core` to WASM, wraps it in a Chrome MV3 extension with a terminal-aesthetic popup, conservative autofill, and direct Gitea/GitHub API access. No CLI dependency, no native messaging bridge.
The browser extension for Relicario. Compiles `relicario-core` to WASM, wraps it in a Chrome MV3 extension with a terminal-aesthetic popup, conservative autofill, and direct Gitea/GitHub API access. No CLI dependency, no native messaging bridge.
## Scope
@@ -330,7 +330,7 @@ No shadow DOM traversal. No heuristic scoring. No iframe inspection. If the form
### 2. Field Icon Injection
When a password field is detected:
- Small relicario icon (16x16, inline SVG) appears at the right edge of the password field
- Small Relicario icon (16x16, inline SVG) appears at the right edge of the password field
- Click triggers: send page URL to service worker → get matching entries
- Single match: fill immediately
- Multiple matches: show inline picker (small dropdown below the icon)

View File

@@ -1,6 +1,6 @@
# relicario — Typed Item Data Model Design
# Relicario — Typed Item Data Model Design
Foundational data-model rewrite for relicario. Replaces the single `Entry` type with a polymorphic typed-item system supporting Login, SecureNote, Identity, Card, Key, Document, and TOTP — with sections, custom fields, attachments, password history, soft-delete, and the security architecture needed to support 1Password-style daily-driver UX.
Foundational data-model rewrite for Relicario. Replaces the single `Entry` type with a polymorphic typed-item system supporting Login, SecureNote, Identity, Card, Key, Document, and TOTP — with sections, custom fields, attachments, password history, soft-delete, and the security architecture needed to support 1Password-style daily-driver UX.
This is **Phase 1** of the broader 1Password-parity roadmap. Phase 0 (audit remediation) is the precursor implementation pass; Phase 2+ (admin portal, importers, Watchtower checks, etc.) build on top of this model.

View File

@@ -1,4 +1,4 @@
# relicario — Extension Plan 1C-α (Foundation) Design
# Relicario — Extension Plan 1C-α (Foundation) Design
First of three sub-plans that port the browser extension from the v1 single-`Entry` data model to the typed-item model landed in Plans 1A + 1B. 1C-α is the **foundation slice**: rebuild the WASM artifact, migrate shared types, rewrite the service worker against the opaque `SessionHandle` surface, split the message router with sender checks, wire the full security architecture from the typed-items spec, and achieve Login-parity on the new stack. Other six item types show "Coming in 1C-β" placeholders.

View File

@@ -1,4 +1,4 @@
# relicario — Extension Plan 1C-β₁ (Typed-Item Forms) Design
# Relicario — Extension Plan 1C-β₁ (Typed-Item Forms) Design
Second of three sub-plans porting the extension to the typed-item core. 1C-α (foundation) shipped Login-parity; 1C-β₁ adds the **other 5 typed-item forms** so the extension can daily-drive every typed item the Rust core knows about (except Document, deferred to γ for attachment dependencies). Custom-fields editor, vault-settings view, and advanced generator UI move to **β₂**.

View File

@@ -1,4 +1,4 @@
# relicario — Extension Plan 1C-β₂ (Custom Fields + Settings + Generator UI) Design
# Relicario — Extension Plan 1C-β₂ (Custom Fields + Settings + Generator UI) Design
Third of three β sub-plans porting the extension to the typed-item core. 1C-α shipped the security architecture + Login parity; 1C-β₁ added the 5 remaining typed-item forms; **1C-β₂** (this spec) adds the cross-cutting UI surfaces: custom fields editor, full vault-settings view, and an inline generator popover.

View File

@@ -5,7 +5,7 @@
## Goal
The current logo reads as "modern shrine with a blue diamond" — visually correct in concept (a vessel that holds something precious) but blue-techy enough that the project's name (*relicario* — Spanish/Italian for *reliquary*) no longer comes through. The user wants more catholic-reliquary authenticity (gold, deep red, decorative finial) without the cross — closer to the user-supplied references of round-chapel theca reliquaries.
The current logo reads as "modern shrine with a blue diamond" — visually correct in concept (a vessel that holds something precious) but blue-techy enough that the project's name (*Relicario* — Spanish/Italian for *reliquary*) no longer comes through. The user wants more catholic-reliquary authenticity (gold, deep red, decorative finial) without the cross — closer to the user-supplied references of round-chapel theca reliquaries.
The popup currently uses GitHub's dark-blue accent palette throughout. Once the logo shifts to gold, leaving the popup's blue accents in place would create visual whiplash between the toolbar icon and the popup body. The palette shift converts blue → gold and tunes the danger red toward the logo's theca tone, while keeping the dark backgrounds, monospace-ish text, and CLI restraint that define the project's voice.

View File

@@ -7,7 +7,7 @@
## Background
Today the setup wizard (`extension/src/setup/setup.ts`) has one flow: create a brand-new vault. Step 2 only checks that the configured remote is reachable; it does not detect whether that remote already contains a relicario vault. Step 3's "create vault" then writes `.relicario/salt`, `.relicario/params.json`, `.relicario/devices.json`, and `manifest.enc` unconditionally — silently overwriting any existing vault on the remote.
Today the setup wizard (`extension/src/setup/setup.ts`) has one flow: create a brand-new vault. Step 2 only checks that the configured remote is reachable; it does not detect whether that remote already contains a Relicario vault. Step 3's "create vault" then writes `.relicario/salt`, `.relicario/params.json`, `.relicario/devices.json`, and `manifest.enc` unconditionally — silently overwriting any existing vault on the remote.
**Observed failure:** uninstalling and reinstalling the extension while pointed at a populated test repo wipes the manifest with no warning. The user's test entries are gone.
@@ -64,7 +64,7 @@ The progress bar grows from 5 to 6 segments; Step 0 is the new leading segment.
Two large buttons. No host configuration, no other inputs. Sets `state.mode` to `'new'` or `'attach'`. Helper copy under each:
- *create new vault* — "I'm setting up relicario for the first time. This will create a fresh encrypted vault on a new or empty git repository."
- *create new vault* — "I'm setting up Relicario for the first time. This will create a fresh encrypted vault on a new or empty git repository."
- *attach this device* — "I already have a vault on another device. Connect this browser to it using my passphrase and reference image."
### Step 1: host type
@@ -93,7 +93,7 @@ The "switch mode" buttons preserve all entered host config so the user does not
**Warning card copy (mode=new, vault present):**
> ⚠ This repository already contains a relicario vault.
> ⚠ This repository already contains a Relicario vault.
> Last commit: `<sha7>` by `<author>` on `<date>`.
>
> Creating a new vault here would overwrite the existing one and **destroy all data inside**. To use this vault on this device, switch to *attach* mode instead.

View File

@@ -1,12 +1,12 @@
# relicario import / export — design
# Relicario import / export — design
Date: 2026-04-27
Status: design (not yet implemented)
Scope: backup / restore (round-trippable to relicario itself) + LastPass CSV import. Migration **out** to other tools is explicitly out of scope.
Scope: backup / restore (round-trippable to Relicario itself) + LastPass CSV import. Migration **out** to other tools is explicitly out of scope.
## Motivation
Self-hosting a password vault without a backup story is unacceptable for production use. Today, a relicario user has no way to:
Self-hosting a password vault without a backup story is unacceptable for production use. Today, a Relicario user has no way to:
1. **Snapshot** their vault for disaster recovery (git remote going away, repo corruption, account loss).
2. **Onboard** from an existing manager — there's no migration path for a user with credentials in another tool.
@@ -18,13 +18,13 @@ The following choices were brainstormed and approved before this spec was writte
| # | Decision |
|---|---|
| D1 | Two features, one spec: backup/restore round-trippable to relicario, plus a LastPass CSV importer. Migration out is out of scope. |
| D1 | Two features, one spec: backup/restore round-trippable to Relicario, plus a LastPass CSV importer. Migration out is out of scope. |
| D2 | Backup file format: single-file `.relbak` container. Magic header + version + salt + nonce + AEAD-encrypted, zstd-compressed JSON envelope with base64'd binary blobs. |
| D3 | AEAD: XChaCha20-Poly1305 (same primitive used for vault items, but the backup format uses its own envelope with magic header + version byte; it does **not** reuse the `crypto.rs` `encrypt`/`decrypt` helpers, which assume the vault-master-key format). KDF: Argon2id with the same parameters as v1 of the live vault (m=64MiB, t=3, p=4) — but the params are tied to **backup format version**, not read from the vault's `params.json`. |
| D4 | Backup passphrase is independent of the vault passphrase. User picks one at export; user types it at restore. Reusing the vault passphrase is allowed but not auto-filled. |
| D5 | Reference image inclusion is optional. `--include-image` flag (CLI) / checkbox (UI). When included, the image is base64'd into the encrypted envelope — never in the clear inside the file. |
| D6 | Git history (`.git/`) is included **by default**. `--no-history` opt-out for users who want a smaller file at the cost of audit trail and remote URL. |
| D7 | Restore semantics: refuse if the target directory already contains a relicario vault. Restore is a fresh round-trip operation, not a merge. |
| D7 | Restore semantics: refuse if the target directory already contains a Relicario vault. Restore is a fresh round-trip operation, not a merge. |
| D8 | Backup passphrase strength: zxcvbn score ≥ 3, same gate as `init`. Backup is single-factor (one passphrase decrypts the container), so it must be at least as strong as a vault factor. |
| D9 | The user is responsible for deleting the backup file after restore is verified. The encryption protects it in transit / at rest while it exists; it is not a defense against forensic recovery of deleted copies. Documented in CLI help text and the extension UI. |
| D10 | LastPass import: parse the standard LastPass CSV (`url,username,password,totp,extra,name,grouping,fav`). Logins → `Login` items (with embedded TOTP if present); rows with `url == http://sn``SecureNote`; structured LastPass notes (cards, SSH keys, addresses) are **not** auto-parsed — they fall through as `SecureNote` with `extra` as the body. |
@@ -215,7 +215,7 @@ Future format v2 may change these; v1 readers will see `version != 0x01` and pro
## LastPass field mapping
| LastPass column | relicario destination | Notes |
| LastPass column | Relicario destination | Notes |
|---|---|---|
| `name` | `Item.title` | Required; row skipped with warning if missing |
| `grouping` | `Item.group` | `None` if empty |
@@ -246,11 +246,11 @@ Atomicity: output uses the existing `atomic_write` helper (write `.tmp` → rena
| Error | Detection | User-facing message | Recovery |
|---|---|---|---|
| Bad magic | First 4 bytes ≠ `"RBAK"` | `"not a relicario backup file"` | Verify file |
| Unsupported version | Version byte > current (1) | `"backup created by a newer relicario; upgrade required"` | Update binary |
| Bad magic | First 4 bytes ≠ `"RBAK"` | `"not a Relicario backup file"` | Verify file |
| Unsupported version | Version byte > current (1) | `"backup created by a newer Relicario; upgrade required"` | Update binary |
| Wrong backup passphrase | AEAD authentication fails | `"wrong backup passphrase, or the file is corrupt"` (deliberately ambiguous) | Retry |
| Target dir already has a vault | `target/.relicario/` exists | `"target dir already contains a relicario vault; restore refuses to overwrite — use an empty directory"` | Choose empty dir |
| Schema mismatch | envelope.schema_version != current | `"backup is schema v<N>; this relicario reads v<M>"` | Use matching binary |
| Target dir already has a vault | `target/.relicario/` exists | `"target dir already contains a Relicario vault; restore refuses to overwrite — use an empty directory"` | Choose empty dir |
| Schema mismatch | envelope.schema_version != current | `"backup is schema v<N>; this Relicario reads v<M>"` | Use matching binary |
| Mid-restore crash | (no detection) | — | User deletes target dir, retries |
Atomicity: best-effort. If interrupted mid-write, target dir has partial files — user cleans up and retries. Documented limitation. Restore is rare enough that engineering atomic-rename of multiple files is not worth the complexity.

View File

@@ -24,7 +24,7 @@ Sidebar + detail pane, similar to 1Password's desktop app:
```
┌──────────────────────────────────────────────────┐
│ 🔒 relicario [lock] [settings] │
│ 🔒 Relicario [lock] [settings] │
├────────────────┬─────────────────────────────────┤
│ [search...] │ │
│ │ (detail view for selected │

View File

@@ -7,7 +7,7 @@
## Background
relicario's two-factor model derives `master_key = Argon2id(len-prefixed(passphrase) || image_secret, salt, params)` (`crates/relicario-core/src/crypto.rs:207`). Lose either factor and the vault is unrecoverable. The reference image is the more loseable factor — it lives outside the user's head, often as a "dead drop" on social media or a personal site, and a single platform takedown or accidental deletion permanently bricks the vault.
Relicario's two-factor model derives `master_key = Argon2id(len-prefixed(passphrase) || image_secret, salt, params)` (`crates/relicario-core/src/crypto.rs:207`). Lose either factor and the vault is unrecoverable. The reference image is the more loseable factor — it lives outside the user's head, often as a "dead drop" on social media or a personal site, and a single platform takedown or accidental deletion permanently bricks the vault.
The original design spec already sketched a post-V1 recovery path (`docs/superpowers/specs/2026-04-11-relicario-design.md:342-349`): a small encrypted file containing only `image_secret`, locked under the passphrase via a separate Argon2id derivation, stored offline. This spec finalizes that sketch with three refinements landed during brainstorming:

View File

@@ -1,6 +1,6 @@
{
"manifest_version": 3,
"name": "relicario",
"name": "Relicario",
"version": "0.2.0",
"description": "Two-factor encrypted password manager",
"icons": {
@@ -32,7 +32,7 @@
},
"commands": {
"open-vault": {
"description": "Open relicario vault"
"description": "Open Relicario vault"
}
},
"web_accessible_resources": []

View File

@@ -66,7 +66,7 @@ export function injectFieldIcons(
const icon = document.createElement('div');
icon.textContent = 'id';
icon.setAttribute('role', 'button');
icon.setAttribute('aria-label', 'relicario autofill');
icon.setAttribute('aria-label', 'Relicario autofill');
icon.style.cssText = [
'width: 20px', 'height: 20px', 'line-height: 20px',
'text-align: center', 'font-size: 10px', 'font-weight: 700',
@@ -177,7 +177,7 @@ function showPicker(
/// TOFU origin-ack hint: credentials exist for this host but the user has
/// never explicitly acknowledged autofill here. Instruct them to open
/// relicario to confirm — we do not (and cannot) fill until ack-autofill
/// Relicario to confirm — we do not (and cannot) fill until ack-autofill
/// has been called from the popup.
function showAckHint(hostname: string): void {
closeOverlay();
@@ -201,7 +201,7 @@ function showAckHint(hostname: string): void {
const title = document.createElement('div');
title.style.cssText = 'font-weight: 700; margin-bottom: 4px; color: #d2ab43;';
title.textContent = 'relicario';
title.textContent = 'Relicario';
hint.appendChild(title);
const body = document.createElement('div');
@@ -209,7 +209,7 @@ function showAckHint(hostname: string): void {
const hostSpan = document.createElement('strong');
hostSpan.textContent = hostname;
body.appendChild(hostSpan);
body.appendChild(document.createTextNode(' — open relicario to confirm.'));
body.appendChild(document.createTextNode(' — open Relicario to confirm.'));
hint.appendChild(body);
const close = document.createElement('div');

View File

@@ -9,7 +9,7 @@ export function renderUnlock(app: HTMLElement): void {
app.innerHTML = `
<div class="pad" style="text-align:center; padding-top:40px;">
<img class="brand-logo" src="icons/relicario-logo.svg" alt="">
<div class="brand">relicario</div>
<div class="brand">Relicario</div>
<p class="muted" style="margin:8px 0 24px;">two-factor vault</p>
<div class="form-group">
<input

View File

@@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=360">
<link rel="stylesheet" href="styles.css">
<title>relicario</title>
<title>Relicario</title>
</head>
<body>
<div id="app"></div>

View File

@@ -1,4 +1,4 @@
/* relicario extension — terminal dark theme */
/* Relicario extension — terminal dark theme */
:root {
/* Brand */

View File

@@ -445,7 +445,7 @@ export async function handle(
try {
const meta = await vault.fetchVaultMeta(newHost);
if (meta.salt && meta.paramsJson) {
return { ok: false, error: 'remote already contains a relicario vault' };
return { ok: false, error: 'remote already contains a Relicario vault' };
}
} catch {
// No vault present — expected for a fresh remote.

View File

@@ -191,7 +191,7 @@ function render(): void {
app.innerHTML = `
<div class="pad" style="padding-top:12px;">
<img class="brand-logo" src="icons/relicario-logo.svg" alt="" style="margin-bottom:12px;">
<div class="brand" style="margin-bottom:4px;">relicario vault setup</div>
<div class="brand" style="margin-bottom:4px;">Relicario vault setup</div>
${progressHtml}
${state.error ? `<div class="error">${escapeHtml(state.error)}</div>` : ''}
${stepHtml}
@@ -215,15 +215,15 @@ function renderStep0(): string {
const isAttach = state.mode === 'attach';
return `
<div class="wizard-step">
<h3>set up relicario</h3>
<h3>set up Relicario</h3>
<p class="muted" style="margin-bottom:16px;">
How are you using relicario on this device?
How are you using Relicario on this device?
</p>
<div class="mode-cards">
<button class="mode-card ${isNew ? 'active' : ''}" data-mode="new">
<div class="mode-card-title">create new vault</div>
<p class="mode-card-blurb">
I'm setting up relicario for the first time. This will create a fresh
I'm setting up Relicario for the first time. This will create a fresh
encrypted vault on a new or empty git repository.
</p>
</button>
@@ -481,7 +481,7 @@ function renderProbeBanner(): string {
if (state.mode === 'new' && probe.exists) {
return `
<div class="banner banner-warn">
<strong>⚠ This repository already contains a relicario vault.</strong>
<strong>⚠ This repository already contains a Relicario vault.</strong>
<p>${meta}</p>
<p>Creating a new vault here would overwrite the existing one and <strong>destroy all data inside</strong>.
To use this vault on this device, switch to <em>attach</em> mode instead.

View File

@@ -1,4 +1,4 @@
/* relicario vault — terminal dark theme (tab layout) */
/* Relicario vault — terminal dark theme (tab layout) */
:root {
/* Brand */

View File

@@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>relicario — vault</title>
<title>Relicario — vault</title>
<link rel="stylesheet" href="vault.css">
</head>
<body>

View File

@@ -195,7 +195,7 @@ function render(): void {
function renderLockScreen(app: HTMLElement): void {
app.innerHTML = `
<div class="vault-lock-screen">
<span class="brand">relicario</span>
<span class="brand">Relicario</span>
<div class="vault-lock-screen__form">
<input type="password" id="vault-passphrase" placeholder="passphrase" autocomplete="off" />
<button class="btn btn-primary" id="vault-unlock-btn" style="width:100%;">unlock</button>
@@ -241,7 +241,7 @@ function renderShell(app: HTMLElement): void {
app.innerHTML = `
<div class="vault-sidebar">
<div class="vault-sidebar__header">
<span class="brand">relicario</span>
<span class="brand">Relicario</span>
</div>
<div class="vault-sidebar__search">
<input type="text" id="vault-search" placeholder="/ search..." />