# Candidate Findings — Extension SW + content + manifest — audit-snapshot-2026-05-24T15-55Z.md Scope: background.js + content.js + manifest.json Required-reading: ext AGENTS.md / mockup / bug-audit-plan.md / project memory Auditor: fresh Explore agent (read-only Phase 1) --- ## Candidate C-1: Race condition in maybeNotifyHostError rate-limiting - File: background.js:188-193 - Hunch: Concurrent recordRpc calls could trigger multiple notifications within 10min due to get-then-set race. - Trace: Two simultaneous host errors invoke recordRpc() → maybeNotifyHostError(). Both read HOST_ALERT_KEY before either writes. Both pass the now-lastTs check and both post. - Question for verifier: Does code guarantee only one alert fires per 10min window under concurrent error paths? - Contract refs needed: Race condition definition in bug-audit-plan.md; Chrome storage.local atomicity --- ## Candidate C-2: pending Map orphaned on SW eviction mid-call - File: background.js:90, 124-148, 307-365 - Hunch: If SW evicts between request send and response, next instance has empty pending Map. Response arrives with no matching req_id. - Trace: Send stores pending.set(reqId, {resolve, reject}). SW evicts. New SW has empty pending. Response at line 124-127 finds no match, dropped. - Question for verifier: Does keepalive (20s pulse) reliably prevent SW eviction during full 90s timeout on slow remote? - Contract refs needed: MV3 SW eviction timing vs NATIVE_CALL_TIMEOUT_MS (90s) --- ## Candidate C-3: mergeSettings shallow-merge loses missing nested keys - File: background.js:62-76 - Hunch: Deep-merge is one level, but if stored.triggers is incomplete, Object.assign(dv, sv) loses keys not in sv. - Trace: dv={autoPageLoad:true, autoKnownSites:false, …7 keys}. sv={autoPageLoad:true}. Result is only {autoPageLoad:true}. - Question for verifier: Does incomplete settings blob correctly populate all missing triggers keys from defaults? - Contract refs needed: DEFAULT_SETTINGS.triggers shape vs loaded partial settings --- ## Candidate C-4: Discord webhook URL regex insufficient - File: background.js:232 - Hunch: Regex validates only schema+domain, not mandatory ID. URL https://discord.com/api/webhooks/ (no ID) passes validation. - Trace: /^https:\/\/(?:discord\.com|discordapp\.com)\/api\/webhooks\//.test(url) matches prefix only. - Question for verifier: Should regex enforce numeric ID after /api/webhooks/? - Contract refs needed: Discord webhook URL format spec --- ## Candidate C-5: postDiscordAlert silently swallows all errors - File: background.js:215, 268-272 - Hunch: .catch(() => {}) suppresses Discord errors with no logging or diagnostics visibility. - Trace: Line 215 swallows all exceptions. Function catches fetch errors at 268 and records in lastDiscordSend, but callers don't see them. - Question for verifier: Should Discord errors be logged to native RPC log or is silent swallow intentional? - Contract refs needed: AGENTS.md or alerting design docs --- ## Candidate C-6: contextMenu handler doesn't validate tab.id - File: background.js:895-905 - Hunch: Handler checks if (!tab) but not if tab.id is null. Missing tab.id will fail silently in checkTab. - Trace: Line 896 checks tab, not tab.id. Line 898 calls checkTab(tab) which calls extractIdFromTab(tab) which calls chrome.tabs.sendMessage(tab.id). - Question for verifier: Can contextMenus.onClicked pass tab with null id? - Contract refs needed: Chrome contextMenus API contract --- ## Candidate C-12: FC2 ID regex minimum 4 digits too strict - File: src/shared/id-extract.js:16-20 - Hunch: FC2-PPV normalizer requires 4+ digits. Pages with FC2-PPV-123 silently fail to match. - Trace: Line 17 regex /\bFC2-?PPV-?(\d{4,})\b/i. Line 19 /\bFC2-(\d{4,})\b/i. Both need 4+ digits minimum. - Question for verifier: Is 4-digit minimum intentional or should it be 3+? - Contract refs needed: Real FC2 ID formats in AGENTS.md --- ## Candidate C-15: recordRpc read-modify-write race loses entries - File: background.js:162-169 - Hunch: Concurrent recordRpc calls uncoordinated. Two errors get same old log, prepend differently, second write overwrites first. - Trace: get(NATIVE_LOG_KEY), prepend entry, set. If two calls concurrent, second set overwrites first's entry. - Question for verifier: Does Chrome storage.local.set serialize, or can concurrent calls lose entries? - Contract refs needed: Chrome storage.local atomicity guarantees --- ## Candidate C-19: ensureContextMenu not called on SW init - File: background.js:766-782 - Hunch: Context menu may not recreate after SW eviction if only called on settings changes. - Trace: ensureContextMenu defined at line 766. Need to verify it's called at SW startup. - Question for verifier: Is ensureContextMenu called during SW initialization? - Contract refs needed: MV3 context menu persistence after SW eviction --- ## Candidate C-20: escapeOverlay function not found in content.js - File: content.js (various lines use escapeOverlay but definition not visible) - Hunch: showOverlay calls escapeOverlay at multiple points but function is not defined in the 509-line content.js. - Trace: Lines 374-400 call escapeOverlay(...) but no definition found. - Question for verifier: Is escapeOverlay defined in content.js or missing? - Contract refs needed: content.js full file review; module dependencies --- ## Additional light candidates (lower priority, but noted): C-7: nativePort assignment race (theory-level, JS atomic in practice) C-9: Silent catch in recordActivity (observability trade-off) C-11: Content message validation (trusted sender, acceptable design trade-off) C-14: keepaliveTimer stale reference (uncommon race, low impact) C-18: badgeSpinners leak under heavy load (unlikely, has onRemoved handler) --- ## Triage Summary **High-priority for verification**: C-1, C-2, C-3, C-4, C-5, C-6, C-12, C-15, C-19, C-20 **Medium-priority (design review)**: C-7, C-9, C-11, C-14, C-18 **Focus areas for verifier**: 1. Concurrency safety of storage get-then-set patterns 2. Service worker eviction + pending request handling 3. Settings merge correctness with partial updates 4. Input validation in all entry points --- ## VERIFIER NOTES (appended after Phase 1 verification) ### C-2 (SW eviction + orphaned pending) — REFUTED First verifier returned CONFIRMED, but did not consult Chrome `connectNative` keepalive contract. Re-verifier (with explicit contract ref to https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle) returned REFUTED, high confidence. Key finding: an open `connectNative` port keeps the MV3 SW alive per docs. If port closes, `onDisconnect` fires and rejects all pending (background.js:139). The orphaned-pending-Map scenario cannot occur under documented Chrome contract. The in-code `pulseKeepalive` is defensive redundancy, not load-bearing. Caveat: if Brave is observed diverging from this contract, the symptom could manifest as a Brave-specific bug — would need a Brave-observed SW restart trace while a native port stayed active. NOT verified here. Final status: REFUTED. Removed from bugs-extension-bg.md. ### C-15 (recordRpc race) — CONFIRMED Verifier returned CONFIRMED, high confidence. Promoted to bugs-extension-bg.md as S-1. ### CHUNK 3 MODERATE VERIFICATION RESULTS (after stricter prompt) - C-1 (maybeNotifyHostError rate-limit race) — CONFIRMED, M, promoted as M-1 in bugs-extension-bg.md - C-3 (mergeSettings shallow-merge) — REFUTED. Auditor misread Object.assign arg order; defaults fill missing keys correctly. Discarded. - C-5 (Discord errors swallowed) — PARTIAL, demoted M→L. lastDiscordSend storage write present; only passive UI display missing. Promoted as L-1. - C-6 (contextMenu tab.id null) — REFUTED. Chrome contract guarantees non-null tab.id for registered contexts. extractIdFromTab also has defensive null check. Discarded. - C-19 (ensureContextMenu post-eviction) — CONFIRMED, M (very high confidence). Promoted as M-2. - C-20 (escapeOverlay undefined) — REFUTED. Function defined at content.js:451. Auditor missed it. Discarded. CHUNK 3 CALIBRATION SUMMARY: - Severe rejection: 1/2 = 50% - Moderate rejection: 3/6 = 50% - Combined: 4/8 = 50% - Stop condition (>30% rejection) TRIGGERED. Chunk 3 audit should be restarted with tighter framing OR Light candidates should NOT be verified per current pass. - Calibration learning: auditor over-claimed by reading code in isolation without checking platform API contracts (Chrome lifecycle, contextMenus, storage atomicity). Stricter verifier prompt with explicit contract requirement caught 3 of 4 false positives.