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:
@@ -81,11 +81,22 @@ function ratePassphrase(passphrase: string): Promise<number> {
|
||||
chrome.runtime.sendMessage(
|
||||
{ type: 'rate_passphrase', passphrase },
|
||||
(response: { ok: boolean; data?: { score: number; guesses_log10: number }; error?: string }) => {
|
||||
if (chrome.runtime.lastError || !response?.ok) { resolve(-1); return; }
|
||||
if (chrome.runtime.lastError) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[relicario setup] rate_passphrase lastError:', chrome.runtime.lastError);
|
||||
resolve(-1); return;
|
||||
}
|
||||
if (!response?.ok) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[relicario setup] rate_passphrase rejected by SW:', response);
|
||||
resolve(-1); return;
|
||||
}
|
||||
resolve(response.data?.score ?? -1);
|
||||
},
|
||||
);
|
||||
} catch {
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn('[relicario setup] rate_passphrase threw:', err);
|
||||
resolve(-1);
|
||||
}
|
||||
});
|
||||
@@ -428,66 +439,85 @@ function attachStep3(): void {
|
||||
state.error = null;
|
||||
render();
|
||||
|
||||
// Structured logging so silent failures become visible in DevTools.
|
||||
// eslint-disable-next-line no-console
|
||||
const log = (stage: string, detail?: unknown) => console.log(`[relicario setup] ${stage}`, detail ?? '');
|
||||
|
||||
let stage = 'init';
|
||||
try {
|
||||
stage = 'load wasm';
|
||||
log(stage);
|
||||
const w = await loadWasm();
|
||||
|
||||
// 1. Generate 32-byte image secret.
|
||||
stage = 'generate image secret';
|
||||
log(stage);
|
||||
const imageSecret = new Uint8Array(32);
|
||||
crypto.getRandomValues(imageSecret);
|
||||
|
||||
// 2. Embed secret into carrier JPEG.
|
||||
stage = 'embed image secret';
|
||||
log(stage, { carrierBytes: state.carrierImageBytes.byteLength });
|
||||
state.referenceImageBytes = new Uint8Array(
|
||||
w.embed_image_secret(state.carrierImageBytes, imageSecret),
|
||||
);
|
||||
log('embedded', { referenceBytes: state.referenceImageBytes.byteLength });
|
||||
|
||||
// 3. Generate 32-byte salt + KDF params.
|
||||
stage = 'generate salt';
|
||||
const salt = new Uint8Array(32);
|
||||
crypto.getRandomValues(salt);
|
||||
const paramsJson = '{"argon2_m":65536,"argon2_t":3,"argon2_p":4}';
|
||||
|
||||
// 4. Derive a session handle via the typed-item unlock API.
|
||||
// (Single-shot master_key derivation is no longer exposed; the
|
||||
// handle is the only in-JS reference to the master key.)
|
||||
const handle = w.unlock(state.passphrase, imageSecret, salt, paramsJson);
|
||||
stage = 'derive session handle';
|
||||
log(stage);
|
||||
// unlock() takes JPEG bytes with embedded secret (it extracts internally),
|
||||
// not the raw 32-byte secret.
|
||||
const handle = w.unlock(state.passphrase, state.referenceImageBytes, salt, paramsJson);
|
||||
log('handle acquired');
|
||||
|
||||
// 5. Encrypt an empty schema-v2 manifest.
|
||||
stage = 'encrypt empty manifest';
|
||||
log(stage);
|
||||
const manifestJson = '{"schema_version":2,"items":{}}';
|
||||
const encryptedManifest = w.manifest_encrypt(handle, manifestJson);
|
||||
log('manifest encrypted', { bytes: encryptedManifest.length });
|
||||
|
||||
// 6. Push vault files via git API.
|
||||
stage = 'push vault files';
|
||||
log(stage);
|
||||
const hostUrl = state.hostType === 'github' ? 'https://api.github.com' : state.hostUrl;
|
||||
const host = createGitHost(state.hostType, hostUrl, state.repoPath, state.apiToken);
|
||||
|
||||
log('write .relicario/salt');
|
||||
await host.writeFile('.relicario/salt', salt, 'init: vault salt');
|
||||
|
||||
log('write .relicario/params.json');
|
||||
const paramsBytes = new TextEncoder().encode(paramsJson);
|
||||
await host.writeFile('.relicario/params.json', paramsBytes, 'init: KDF parameters');
|
||||
|
||||
log('write .relicario/devices.json');
|
||||
const devicesJson = '{"devices":[]}';
|
||||
const devicesBytes = new TextEncoder().encode(devicesJson);
|
||||
await host.writeFile('.relicario/devices.json', devicesBytes, 'init: device registry');
|
||||
|
||||
log('write manifest.enc');
|
||||
await host.writeFile(
|
||||
'manifest.enc',
|
||||
new Uint8Array(encryptedManifest),
|
||||
'init: encrypted manifest',
|
||||
);
|
||||
|
||||
// 7. Release the handle — the SW's own unlock will re-derive.
|
||||
stage = 'release handle';
|
||||
w.lock(handle);
|
||||
|
||||
// 8. Advance to step 4.
|
||||
log('vault created — advancing to step 4');
|
||||
state.creating = false;
|
||||
state.step = 4;
|
||||
state.error = null;
|
||||
|
||||
// Detect extension.
|
||||
detectExtension();
|
||||
|
||||
render();
|
||||
} catch (err: unknown) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(`[relicario setup] vault creation FAILED during "${stage}":`, err);
|
||||
state.creating = false;
|
||||
state.error = `Vault creation failed: ${err instanceof Error ? err.message : String(err)}`;
|
||||
const detail = err instanceof Error ? err.message : String(err);
|
||||
state.error = `Vault creation failed at "${stage}": ${detail}`;
|
||||
render();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user