Skip to content

fix: 2nd-pass audit — wire log.js, enable PVR, fix popup URL check, harden test execSync#135

Merged
heznpc merged 2 commits into
mainfrom
fix/second-pass-audit-2026-05-21
May 21, 2026
Merged

fix: 2nd-pass audit — wire log.js, enable PVR, fix popup URL check, harden test execSync#135
heznpc merged 2 commits into
mainfrom
fix/second-pass-audit-2026-05-21

Conversation

@heznpc
Copy link
Copy Markdown
Owner

@heznpc heznpc commented May 21, 2026

Why this PR exists

The prior session reported all modernization items "done". An adversarial second-pass — same model, same code, adversarial mindset — found four concrete defects under "previously declared done". This PR fixes them.

Critical findings — fixed in this PR

C-1. src/lib/log.js was 100% dead code with false documentation

The original PR (#129) shipped src/lib/log.js but never added it to manifest.json content_scripts.js. scripts/build-bundle.js:17 only bundles entries from that list, so log.js was not in the runtime bundle. Yet:

  • The simplify pass (chore: pause academy-courses-drift cron until selectors fixed #132) added a chrome.storage.onChanged listener inside log.js — that listener could never fire.
  • CONTRIBUTING.md told contributors to "use it" — anyone following the guidance would hit ReferenceError on first call.
  • Two PR descriptions claimed it was shipped functionality.

Fix: wired log.js into manifest.json content_scripts.js immediately after browser-polyfill.js. Added 13 contract tests in tests/log.test.js including a manifest-wiring regression guard. CONTRIBUTING.md text now matches the actual API shape.

C-2. SECURITY.md promised Private Vulnerability Reporting; feature was off

gh api .../private-vulnerability-reporting returned {"enabled": false}. SECURITY.md confidently directed reporters to the "Report a vulnerability" UI button — which didn't exist. Fix: enabled PVR via API; SECURITY.md text is now true.

C-3. CodeQL HIGH-severity alert never triaged — src/popup/popup.js:8

tab?.url?.includes('skilljar.com') matches evil.skilljar.com.attacker.example/ and prefix-skilljar.com/. Fix: extracted isSkilljarHost(url) that parses URL and checks hostname === 'skilljar.com' || endsWith('.skilljar.com').

C-4. CodeQL MEDIUM x3 — execSync interpolation in test files

tests/{academy-courses,dict-coverage}-checker.test.jsjs/shell-command-injection-from-environment. Fix: switched to spawnSync(process.execPath, [SCRIPT]) array-args form.

Pre-flight

  • npm test411/411 (was 398, +13 from new tests/log.test.js)
  • eslint, prettier, npm run build:bundle — clean
  • gh api .../private-vulnerability-reportingenabled: true

Major findings — awaiting decision (NOT in this PR)

  1. Branch protection has required_pull_request_reviews: null. Solo-maintainer context, but CODEOWNERS is decorative without enforcement.
  2. academy-courses-drift cron paused since chore: pause academy-courses-drift cron until selectors fixed #132, issue 🆕 New Anthropic Academy course detected — terminology update needed #126 open since 2026-05-16. Pre-existing tech debt.

Minor

Out-of-scope (audited, intentional)

  • background.test.js mocked fetch + toHaveBeenCalledTimes(N) — legitimate contract test of retry policy, not mock-tautology.
  • src/bridge/puter.js empty catch {} — vendored 3rd-party, integrity-checked by maintenance.yml.
  • src/lib/page-bridge.js does not consume log.js — different execution world (page main world). Documented in log.js header.

Test plan

  • All gates green locally
  • CI green
  • After merge: CodeQL alert count expected 0 (was 4)

heznpc added 2 commits May 21, 2026 20:40
…arden test execSync

A self-confirming "done" report from the prior session passed an
adversarial second-pass and lost four concrete findings. Same model,
same code, real defects. This commit fixes all four; the rest of the
audit (Major/Minor) is reported separately.

C-1. src/lib/log.js was dead code with false documentation.
The original modernization PR (#129) shipped log.js but never added
it to manifest.json `content_scripts.js`, and scripts/build-bundle.js
only bundles entries from that list. So:
  - The content-script bundle did not include log.js
  - `window._skillbridgeLog` did not exist at runtime
  - The chrome.storage.onChanged listener added in the simplify pass
    (#132) was code that never executed
  - CONTRIBUTING.md told contributors to "use it" — anyone following
    that guidance would hit a ReferenceError on first call
  - Two PR descriptions claimed it was shipped functionality
The fix wires it correctly: `src/lib/log.js` is now listed in
manifest.json `content_scripts.js` immediately after `browser-polyfill.js`
so subsequent scripts can call `window._skillbridgeLog.createLogger`.
Bundled size: 237.1 KB → 122.7 KB (unchanged headroom). Added 13 new
tests in tests/log.test.js covering: module surface, createLogger
type-guard, prefix format, default threshold silencing,
severity routing, multi-arg passthrough, storage.onChanged
registration, level-from-storage live update, unknown-level
rejection, non-extension-context graceful no-op, AND a manifest-
wiring regression guard that asserts log.js stays in the
content_scripts list. CONTRIBUTING.md updated to reflect the actual
shape (`window._skillbridgeLog.createLogger`).

C-2. SECURITY.md promised Private Vulnerability Reporting but the
feature was disabled. `gh api .../private-vulnerability-reporting`
returned `{"enabled": false}` while SECURITY.md confidently directed
reporters to "Report a vulnerability" UI that didn't exist. A would-
be reporter falling back to a public issue would defeat the privacy
intent. Enabled PVR via `gh api -X PUT .../private-vulnerability-
reporting`; SECURITY.md text is now true.

C-3. src/popup/popup.js:8 had a CodeQL HIGH alert
(js/incomplete-url-substring-sanitization, never triaged by the
prior session). `tab?.url?.includes('skilljar.com')` matches
`evil.skilljar.com.attacker.example/` and `prefix-skilljar.com/`.
Replaced with an `isSkilljarHost` helper that parses the URL and
checks `hostname === 'skilljar.com' || endsWith('.skilljar.com')`.
Throws are caught (invalid URL → false).

C-4. tests/{academy-courses,dict-coverage}-checker.test.js had three
CodeQL MEDIUM alerts (js/shell-command-injection-from-environment)
on `execSync(\`node ${SCRIPT}\`, ...)` calls. The SCRIPT path is
__dirname-derived so there's no real injection vector, but the array
form documents that. Switched to
`spawnSync(process.execPath, [SCRIPT], ...)`; semantics preserved,
all 17 self-tests still pass.

Pre-flight: 411/411 jest (+13 from log.test.js), eslint clean,
prettier clean, `npm run build:bundle` produces 122.7 KB content
bundle that now actually includes log.js.

Out of scope for this PR (reported back to the session, awaiting
decision):
- Major: branch protection currently has `required_pull_request_reviews:
  null` (0 reviews). CODEOWNERS routing is decorative without enforcement.
  Solo-maintainer context; toggle is intentional but worth surfacing.
- Major: academy-courses-drift cron has been paused since PR #132
  due to selector drift; issue #126 open since 2026-05-16.
  Pre-existing tech debt, not a regression introduced here.
- Minor: mnao305/chrome-extension-upload v6.0.0 available (we pin v5.0.0).
  Dependabot PR #130 covers it.
@heznpc heznpc merged commit e25aae1 into main May 21, 2026
7 checks passed
@heznpc heznpc deleted the fix/second-pass-audit-2026-05-21 branch May 21, 2026 12:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant