Files
relicario/extension/src/service-worker/router/index.ts
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

71 lines
2.8 KiB
TypeScript

/// Single chrome.runtime.onMessage entry. Classifies the sender and dispatches
/// to popup-only or content-callable handlers. Unauthorized senders are
/// rejected with { ok: false, error: 'unauthorized_sender' }.
import type { PopupMessage, Request, Response } from '../../shared/messages';
import { POPUP_ONLY_TYPES, CONTENT_CALLABLE_TYPES } from '../../shared/messages';
import type { Manifest } from '../../shared/types';
import type { GitHost } from '../git-host';
import * as popupOnly from './popup-only';
import * as contentCallable from './content-callable';
export interface RouterState {
manifest: Manifest | null;
gitHost: GitHost | null;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
wasm: any;
}
/// Popup-only messages the setup tab is also allowed to send.
/// - save_setup: wires vault config + image into chrome.storage.local at end of init.
/// - rate_passphrase: drives the zxcvbn strength meter during passphrase entry.
/// - is_unlocked: setup step-4 pings the extension to detect "save config to extension" availability.
const SETUP_ALLOWED: ReadonlySet<PopupMessage['type']> = new Set<PopupMessage['type']>([
'save_setup',
'rate_passphrase',
'is_unlocked',
]);
export async function route(
msg: Request,
state: RouterState,
sender: chrome.runtime.MessageSender,
): Promise<Response> {
const popupUrl = chrome.runtime.getURL('popup.html');
const setupUrl = chrome.runtime.getURL('setup.html');
const senderUrl = sender.url ?? '';
const isPopup = senderUrl === popupUrl;
const isSetup = senderUrl.startsWith(setupUrl);
const isContent = sender.tab !== undefined
&& sender.frameId === 0
&& sender.id === chrome.runtime.id;
if (POPUP_ONLY_TYPES.has(msg.type as never)) {
if (!(isPopup || (isSetup && SETUP_ALLOWED.has(msg.type as PopupMessage['type'])))) {
// eslint-disable-next-line no-console
console.warn('[relicario router] rejected popup-only message from wrong sender', {
type: msg.type, senderUrl, isPopup, isSetup, isContent,
});
return { ok: false, error: 'unauthorized_sender' };
}
return popupOnly.handle(msg as never, state, sender);
}
if (CONTENT_CALLABLE_TYPES.has(msg.type as never)) {
if (!isContent) {
// eslint-disable-next-line no-console
console.warn('[relicario router] rejected content-only message from wrong sender', {
type: msg.type, senderUrl, isPopup, isSetup, isContent,
frameId: sender.frameId, senderId: sender.id,
});
return { ok: false, error: 'unauthorized_sender' };
}
return contentCallable.handle(msg as never, state, sender);
}
// eslint-disable-next-line no-console
console.warn('[relicario router] unknown message type', { type: (msg as { type: string }).type });
return { ok: false, error: 'unknown_message_type' };
}