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>
This commit is contained in:
adlee-was-taken
2026-04-22 19:32:00 -04:00
parent 3238ef4dd4
commit 4341124d38
5 changed files with 369 additions and 27 deletions

View File

@@ -309,10 +309,32 @@ describe('fill_credentials captured-tab verification', () => {
});
});
// --- save_setup exception scope: setup tab is ONLY allowed save_setup ---
// --- setup-tab exception scope ---
//
// Setup is allowed a narrow subset of popup-only messages:
// - save_setup (final wire-up)
// - rate_passphrase (zxcvbn meter during passphrase entry)
// - is_unlocked (step-4 extension detection)
// Everything else popup-only must be rejected from setup.
describe('save_setup exception scope', () => {
it('rejects fill_credentials from the setup tab (setup can only save_setup)', async () => {
describe('setup tab exception scope', () => {
it('accepts rate_passphrase from the setup tab (zxcvbn meter)', async () => {
const state = makeState();
const res = await route(
{ type: 'rate_passphrase', passphrase: 'correct horse battery staple parapet' },
state,
makeSetupSender(),
);
expect(res).toMatchObject({ ok: true });
});
it('accepts is_unlocked from the setup tab (step-4 detection)', async () => {
const state = makeState();
const res = await route({ type: 'is_unlocked' }, state, makeSetupSender());
expect(res).toMatchObject({ ok: true });
});
it('rejects fill_credentials from the setup tab (outside the allowlist)', async () => {
const state = makeState();
const res = await route(
{
@@ -326,6 +348,16 @@ describe('save_setup exception scope', () => {
);
expect(res).toEqual({ ok: false, error: 'unauthorized_sender' });
});
it('rejects unlock from the setup tab (outside the allowlist)', async () => {
const state = makeState();
const res = await route(
{ type: 'unlock', passphrase: 'hunter2' },
state,
makeSetupSender(),
);
expect(res).toEqual({ ok: false, error: 'unauthorized_sender' });
});
});
// --- isContent rejects unknown sender.id ---