Includes: - cli.py path fix (parents[1]) for config/catalog resolution - Library cleanup feature design docs (TODO.md, mockup) - Audit + bug-queue markdowns from May 2026 reliability pass - .gitignore expanded for transient artifacts
8.5 KiB
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:
- Concurrency safety of storage get-then-set patterns
- Service worker eviction + pending request handling
- Settings merge correctness with partial updates
- 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.