parse_lastpass_csv is a pure pass-through to the WASM bridge.
import_lastpass_commit re-mints each item's ID via
state.wasm.new_item_id() (same pattern as add_item), encrypts
and writes per-item via git.writeFile, then writes the manifest
last. Per-item commits + a final manifest commit — extension
GitHost has no atomic-batch API, so the single-commit semantics
the CLI provides aren't replicable here.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Single set({vaultConfig, imageBase64?}) instead of two sequential sets,
so a partial-write window can't leave vaultConfig pointing to the new
remote while imageBase64 still references the old vault.
Unpacks .relbak via WASM, writes every vault artifact to the
user-specified fresh remote via writeFileCreateOnly (refuses to
clobber), and updates chrome.storage.local so subsequent unlocks
hit the restored vault. The reference image — when bundled — is
restored to imageBase64; otherwise the user keeps using their
existing reference.jpg.
Reads vault state via GitHost, calls pack_backup_json in WASM, returns
the .relbak bytes back to the panel for chrome.downloads.download.
Reference image inclusion comes from chrome.storage.local.imageBase64.
Git history is never bundled from the extension (CLI is the source of
full backups).
Closes three audit gaps in one pass:
1. Sync now button in the popup settings view (📤). Triggers the existing
{ type: 'sync' } SW message and surfaces success / failure inline. The
SW message was already wired but had no UI entry point.
2. Device registration from the popup. The "Register this device" button
on the devices view used to error out with a "not yet implemented"
message; it now opens an inline name input (default = browser+OS), and
on confirm sends a new register_this_device SW message that generates
an ed25519 keypair via WASM, persists private_key + name to
chrome.storage.local, and writes the public key to the remote
devices.json. No setup-wizard detour.
3. Vault tab is now an authorized sender for popup-only SW messages. The
router accepts vault.html alongside popup.html, so the fullscreen tab
can drive the same flows. Test covers acceptance from the vault tab.
New SW message: register_this_device { name }. Added to PopupMessage and
POPUP_ONLY_TYPES, handled in router/popup-only.ts.
Tests: 5 new vitest cases (3 in settings.test.ts, 2 in devices.test.ts)
+ 1 router test for vault-tab acceptance. All 194 extension tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Implements a service-worker-side session timer that locks the vault
after a configurable period of inactivity (default 15 min). Supports
two modes: 'inactivity' (timer-based) and 'every_time' (no timer).
Config persists via chrome.storage.local and is exposed through
get_session_config / update_session_config popup messages.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Decrypts item and calls WASM get_field_history to extract tracked
field history for the popup's history view.
Co-Authored-By: Claude <noreply@anthropic.com>
Two small follow-ups from code review of 5217d04:
1. Document the cap-enforcement layering in the upload handler. SW
enforces per_attachment_max_bytes via WASM (defense-in-depth);
per_item_max_count and per-vault caps are enforced client-side
in the popup (Task 7's attachments-disclosure).
2. Use ref.id (the validated value found on the item) instead of
msg.attachmentId for blobPath construction in download_attachment.
Eliminates a theoretical path-traversal surface even though the
handler is popup-only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both popup-only. upload_attachment encrypts via WASM, putBlobs via
GitHost (Git Data API fallback for >900 KB), persists the AttachmentRef
on the item + manifest summaries. Duplicate uploads (same content =
same id from sha256) return the existing ref without a re-upload.
download_attachment reads + decrypts and returns plaintext bytes for
the popup to wrap in a Blob. 4 new router tests (accept × 2, reject × 2).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Critical bug caught in T8 code review: the SW's get_totp handler
gated on core.type === 'login' and referenced core.totp, so the
standalone Totp item type (which lands in T8 with core.type === 'totp'
and core.config) had its detail-view ticker silently rejected with
'no_totp' every second. Ticker swallowed the error; rotating code
display stayed at placeholder forever.
Fix: extend the handler to resolve TotpConfig from either carrier:
- Login items: item.core.totp (optional subfield)
- Totp items: item.core.config (required)
Also:
- Add 3 router tests covering both paths + the empty case
- Remove stale '……' placeholder check in types/totp.ts's \`t\`
keyboard shortcut (dead code — the placeholder is '·····' or
'······', never horizontal ellipses)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two security fixes bundled together because they all live on the
icon-click/fill path:
1. Icon + picker + TOFU hint now render inside closed-mode Shadow DOM
(via shadow.createShadowHost). Page scripts can no longer find our
overlay via document.querySelector or rewrite buttons.
2. Icon's get_autofill_candidates call drops the `url` field — router
derives origin from sender.tab.url. Similarly get_credentials.
3. Icon's get_credentials response handling was buggy: the response is a
discriminated union { requires_ack, hostname } | { username, password }
and the old code always read .username (→ undefined when requires_ack).
New code dispatches on the `requires_ack` marker and either shows an
in-page TOFU hint or fills directly.
4. fill_credentials is popup-only in the router — the icon click cannot
(and MUST NOT) issue it from content. The new flow calls fillFields()
directly after get_credentials returns the plaintext: the content
script IS the origin, so no SW round-trip is needed for the typing.
5. TOCTOU on the popup → SW → content fill path: the SW verified the
captured tab's hostname matched capturedUrl, then forwarded blindly.
Between that check and chrome.tabs.sendMessage delivery, the tab can
navigate; chrome.tabs.sendMessage delivers to whatever content-script
principal is loaded at send-time. Closed by:
- Router forwards { expectedHost: currentHost } in the payload.
- fill.ts re-checks location.href.hostname === expectedHost before
typing anything; on mismatch replies { ok: false, error: 'origin_changed' }
and types nothing.
6. Remove @ts-nocheck from icon.ts, fill.ts, and detector.ts — all three
now type-check clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>