Files
relicario/docs/superpowers/test-runs/2026-04-20-1c-alpha-manual-matrix.md
adlee-was-taken 4341124d38 fix(ext): allow rate_passphrase + is_unlocked from setup tab; add diagnostic logging
Bug: setup tab's zxcvbn meter silently stayed at score=-1 because the
router's isSetup exception only allowed save_setup, so rate_passphrase
got unauthorized_sender. Result: the "create vault" button stayed
disabled forever even with a strong passphrase.

Fix: add a narrow SETUP_ALLOWED set containing save_setup,
rate_passphrase, and is_unlocked (step-4 extension detection). Reject
everything else from the setup tab. Also clean up setup.ts's unlock
call — it was passing the raw 32-byte imageSecret where JPEG bytes with
embedded secret are required; the Rust-side unlock calls imgsecret::
extract internally.

Diagnostic logging across the message path so the next silent failure
speaks up:
- [relicario setup]    staged logs through vault-init; console.error
                       with the failure stage name in the UI banner.
- [relicario setup]    rate_passphrase lastError / rejected / threw
                       branches each log their own warning.
- [relicario router]   console.warn on unauthorized_sender (with sender
                       classification) and unknown_message_type.
- [relicario sw]       first-message wasm init announced; per-message
                       non-ok result logged; thrown errors console.error'd.

Tests: +3 setup-allowlist tests (rate_passphrase accepted, is_unlocked
accepted, fill_credentials + unlock rejected). 55/55 green.

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

10 KiB
Raw Blame History

Plan 1C-α — Manual Test Matrix

Walkthrough for validating the extension on both Chrome and Firefox after the six-slice implementation.

Branch: feature/typed-items-1c-alpha @ 3238ef4 (tag candidate: plan-1c-alpha-complete) Worktree: /home/alee/Sources/relicario/.worktrees/typed-items-1c-alpha


Pre-flight

  • P1. Bundles built:

    cd /home/alee/Sources/relicario/.worktrees/typed-items-1c-alpha/extension
    bun run build:all
    

    Expected: "compiled with 2 warnings" (WASM size only) for each bundle. dist/ and dist-firefox/ populated.

  • P2. Fresh-profile browsers ready (or existing profile's chrome.storage.local for this extension cleared). Stale vaultConfig/imageBase64 from the pre-rename idfoto era must not persist.

  • P3. Test git repo for the vault is reachable (SSH key / HTTPS PAT working). Use a throwaway repo to avoid polluting your real vault history.

  • P4. Reference image ready (any JPEG; DCT-steg secret is embedded at init time).


Loading

Chrome

  • L1. chrome://extensions → Developer mode ON → "Load unpacked" → select extension/dist/.
  • L2. Toolbar icon visible (pin if needed).
  • L3. Click icon → first open triggers setup tab (not a popup-embedded wizard).

Firefox

  • L4. about:debugging#/runtime/this-firefox → "Load Temporary Add-on…" → select extension/dist-firefox/manifest.json.
  • L5. Toolbar icon visible.
  • L6. Click icon → setup tab opens.

11-step core matrix — Chrome

Notes column: write what you saw. Check box only when matching expected.

1. Setup tab opens from popup (audit C1)

  • Do: Fresh install, click toolbar icon.
  • Expected: setup.html opens in a new tab; popup closes immediately; WAR is empty so this MUST work via extension-origin tab, not WAR.
  • Notes: ___

2. zxcvbn gate in setup (audit H3)

  • Do: Type weak passphrase like password.
  • Expected: Submit disabled, bar shows red/orange segments, feedback "Too weak…".
  • Do: Type stronger phrase until bar fills.
  • Expected: At score ≥ 3, submit enables, feedback "Strong enough."
  • Notes: ___

3. Setup completes → unlock → list renders

  • Do: Upload reference JPEG, fill vault config (git host/URL/repo/token), submit. Then open popup, enter passphrase, unlock.
  • Expected: Manifest decrypts client-side. Empty list view appears with toolbar (search, + New, sync, lock, ⚙).
  • Notes: ___

4. Add Login with TOTP (typed-item wire format)

  • Do: "+ New" → Login form. Fill:
    • title: GitHub
    • url: https://github.com
    • username: your handle
    • password: click "gen" (uses DEFAULT_PASSWORD_REQUEST — 20 chars, safe symbols)
    • totp: JBSWY3DPEHPK3PXP (well-known base32 test vector)
    • Save.
  • Expected: Row appears with 🔑 icon + title + favorite star position.
  • Expected (CLI cross-check, optional): From main worktree:
    relicario list
    relicario get "GitHub" --show
    
    Should show the same item. TOTP secret should decode identically.
  • Notes: ___

5. TOFU origin-ack prompt (audit C4 first half)

  • Do: Navigate to https://github.com/login. Click the blue id icon next to the password field.
  • Expected: Closed Shadow DOM hint appears ("First autofill on github.com / Open relicario to confirm"). In DevTools, verify document.querySelector('[data-rel]') finds the host but .shadowRoot is null (closed mode).
  • Expected: No credentials fill on this click.
  • Notes: ___

6. Confirm origin + autofill fills correctly

  • Do: Open popup (on the github.com tab). Look for a pending-ack prompt OR (α behavior) just confirm manually: any get_credentials call after the hostname is acked in VaultSettings.autofill_origin_acks will return credentials.
    • Simplest α path: click the item in the popup list, click "autofill" button. This uses the popup-captured tab state path (audit M5).
  • Expected: Username + password fields fill. On React/Vue sites, the native-setter trick fires input+change events.
  • Notes: ___

7. Multiple candidates → picker

  • Do: Add a second Login for github.com with a different username. Back on github.com/login, click the id icon.
  • Expected: Picker shows both titles. Click one → fills that set.
  • Notes: ___

8. Capture prompt → capture_save_login flow (Slice 5 critical-fix)

  • Do: Go to a site not in your vault. Fill signup form (or real trial). Submit.
  • Expected: Capture prompt appears inside closed Shadow DOM. No stable element IDs — running document.querySelector('#relicario-save-btn') in the page console returns null.
  • Do: Click "Save" in the prompt.
  • Expected: ✓ Saved confirmation; prompt dismisses. Open popup → item present in list with the new hostname as title.
  • CRITICAL: If "Save" silently fails, the capture_save_login content-callable handler is broken — file a bug before proceeding.
  • Notes: ___

9. Edit Login → password rotates; field history captured

  • Do: Select the GitHub item → edit → change password → save.
  • Expected: Detail view shows new password on reveal. List's "modified" time updates.
  • Expected (CLI cross-check):
    relicario get "GitHub" --show
    # confirm field_history now has entry for the old password
    
  • Notes: ___

10. Delete Login → soft-delete

  • Do: Select an item → "trash" → confirm.
  • Expected: Row disappears from list immediately. Popup list filters trashed_at !== undefined.
  • Expected (CLI cross-check): relicario list --trashed shows the item.
  • Notes: ___

11. Lock → re-unlock

  • Do: Click "lock" in the toolbar. Try to open the popup again.
  • Expected: Unlock screen. Session handle was cleared in WASM (not just JS).
  • Do: Re-unlock.
  • Expected: Same list (including the item from step 10 still in trash, invisible).
  • Notes: ___

11-step core matrix — Firefox

Re-run 111 on Firefox. Critical Firefox-only check: the background script runs as a persistent script (not MV3 service worker); WASM loads via initDefault(wasmUrl) not initSync. Anything broken here that works in Chrome indicates WASM-loading drift.

  • FF1FF11. Re-run the 11 steps above. Summarize anomalies:
  • Notes: ___

Security probes (bonus)

Open DevTools on any page (not extension origin) and try to defeat the router:

SP1. Content-script-originated popup-only message

  • Do: In a page console (not popup DevTools):
    chrome.runtime.sendMessage({ type: 'unlock', passphrase: 'guess' }, console.log)
    
  • Expected: { ok: false, error: 'unauthorized_sender' } (audit C2).
  • Notes: ___

SP2. Cross-origin get_credentials attempt

  • Do: Pick an item id from the popup (e.g., via popup DevTools: copy(currentState.selectedId)). Go to a different-origin page's console:
    chrome.runtime.sendMessage({ type: 'get_credentials', id: '<the-id>' }, console.log)
    
  • Expected: { ok: false, error: 'origin_mismatch' } (audit C4). No item data leaks.
  • Notes: ___

SP3. Closed Shadow DOM verification

  • Do: Trigger the capture prompt (step 8). In the page console:
    const hosts = document.querySelectorAll('[data-rel]');
    for (const h of hosts) console.log(h, h.shadowRoot);  // shadowRoot should be null
    console.log(document.querySelector('#relicario-save-btn'));  // should be null
    console.log(document.querySelector('.relicario-capture'));   // should be null
    
  • Expected: All shadowRoot values are null; no stable selectors match (audit C3).
  • Notes: ___

SP4. Captured-tab navigation during fill (audit M5)

  • Do: Open popup on https://github.com/login. Select a github item, click "autofill", but BEFORE the fill lands, rapidly navigate the github tab to https://example.com.
  • Expected: No credentials typed on example.com. SW rejects with tab_navigated; if somehow the message reaches the content script, fill.ts re-checks expectedHost and rejects with origin_changed.
  • Notes: ___ (this one's hard to time; skip if not easily reproducible)

SP5. WAR probe

  • Do: In a page console on any site:
    fetch('chrome-extension://<your-extension-id>/setup.html').catch(e => console.log('blocked:', e))
    
  • Expected: Blocked (either CORS error or net::ERR). WAR is empty, so no resource is web-accessible. <all_urls> pages cannot reach setup.html.
  • Notes: ___

Final acceptance

  • A1. cargo test --workspace green (should still be 151+ Rust tests).
  • A2. cd extension && bun run test green (should be 52 passing — 11 base32 + 41 router).
  • A3. cd extension && bun run build green (Chrome bundle).
  • A4. cd extension && bun run build:firefox green (Firefox bundle).
  • A5. Lint greps clean:
    git grep -n 'innerHTML\|insertAdjacentHTML' extension/src/content/  # zero hits
    git grep -n 'idfoto' extension/                                      # zero hits
    git grep -n '@ts-nocheck' extension/src/                             # zero hits
    
  • A6. WAR empty:
    grep -A2 web_accessible_resources extension/manifest.json          # []
    grep -A2 web_accessible_resources extension/manifest.firefox.json  # []
    

Sign-off

  • All 11 core-matrix steps pass on Chrome
  • All 11 core-matrix steps pass on Firefox
  • All 5 security probes pass (or SP4 skipped, others pass)
  • All 6 final acceptance checks pass
  • Ready to tag plan-1c-alpha-complete and decide on merge path

Findings / issues

Use this space to log anything weird:

(fill in as you go)

Decision

  • Merge straight to main
  • Open a PR first for review
  • Need rework on: ___

Generated 2026-04-20 — source: spec 2026-04-20-relicario-extension-1c-alpha-design.md §5.4, plan 2026-04-20-relicario-extension-1c-alpha.md Task 27.