1.8.0: engine hardening — precision, badge = popup, 18 new technologies, real-site proof - #2
Conversation
…validated versions - MAIN-world probe: one try per detector, safe reads, clipped strings, never String() page objects; rejects DOM-clobbered globals (Node/HTMLCollection) and requires library shape (axios.get, bootstrap.Modal, gtag(), ...) - window.next is a version hint only (weight 1); Next.js is promoted to high only with strong proof. Fixes 'Next.js v1.0.0-beta.9' on linear.app and <a id="next"> producing Next.js + React - script/css rules match host+path with the query stripped (opt-in matchQuery for ?dpl=); unanchored vendor words anchored to hosts/packages - inline rules are code-shaped; content script samples executable scripts only (no JSON/ld+json data islands) - Tailwind: no double count; Tailwind-only syntax => medium, --tw-* vars => high, generic utility soup => low (fixes Tailwind MEDIUM on getbootstrap.com) - versions: package-boundary regexes, per-tech major ranges, build metadata dropped (fixes remix->react, @tanstack/react-router->react-router, react-bootstrap->bootstrap, preact->react, pagination cursor->next) - correct globals: TURBOPACK, __sveltekit_<hash>, __svelte.v, webpackJsonp, _reactListening, el.__vue__; _ngcontent attribute scan now works - new: AngularJS, Astro, Gatsby, Qwik, Preact, Lit, Polymer, Ember, Alpine.js, htmx, WordPress, Shopify, Webflow, Framer, PostHog, Plausible, Fathom, Amplitude (new 'CMS / Site builder' category) - build tools seen only in third-party scripts, or conflicting with a meta-framework's bundler, are demoted to low - exports: headline cap, Markdown escaping, JSON schemaVersion, report evidence without query strings
…uards
- every LIGHT_SCAN schedules a debounced background MAIN-world probe; light
results never overwrite a deep one for the same document (badge = popup)
- deep scan always re-collects signals; cache is only a fallback
- service worker always answers (try/catch); probe/collect time out
- results are labelled with the document that answered
- LIGHT_SCAN ignored for non-top frames and non-active documents; SPA route
changes clear the badge and rescan; bfcache/prerender re-post; open tabs
re-injected on install/update; content-script guard survives reloads
- shared URL policy (file:, data:, blob:, Web Store...) and an honest
'unreachable' state instead of 'nothing found'
- popup: sequenced loads, Rescan disabled while scanning, focus-visible,
pluralised tooltip ('1 technology')
…ches, adversarial browser fixtures
…ules) The gate found three more prose false positives, fixed here: - webpack: any path containing 'webpack-' (an image named webpack-logo.png) - tanstack-start: any URL containing 'tanstack-start' - turbopack: case-insensitive inline match on the word 'Turbopack' Identifier-based inline rules are now case-sensitive.
scripts/capture-corpus.mjs loads the real extension, captures the content script's light signals and the MAIN-world probe globals (via a new extension-page-only CAPTURE_SIGNALS message), scrubs query strings and non-evidence inline text, and writes tests/corpus/<host>.json. tests/corpus.test.js replays them through merge + detect() with hand-reviewed must/mustNot/mustNotSolid/noVersion expectations and a full snapshot (UPDATE_CORPUS=1 to accept reviewed changes).
- GitHub Actions: lint, type check, unit + corpus (Node 20/22), coverage gate (>=80% lines), real-extension browser suite in headless Chromium, store zip artifact - ESLint 9 flat config; tsc --checkJs over shared/, background/, popup/ - local-only audit scans every shipped file for fetch/XHR/sendBeacon/ WebSocket/EventSource/importScripts/remote import/beacons and unexpected URLs; manifest audit follows side-effect and dynamic imports - scripts/check-version.mjs: manifest, package, lockfile, release notes - replace two no-op tests (conditional Tailwind assertion, SHA length)
There was a problem hiding this comment.
📋 Review Summary — Not ready to merge
2 medium findings would be worth fixing before merging.
Findings
pushState route changes never invalidate or rescan the tab ▶ Images and fetches are still treated as script evidence ▶
Fix with agent prompt
These are the findings from a code review of this pull request.
## 1. pushState route changes never invalidate or rescan the tab
Path: background/service-worker.js
Line: 74
Issue: History API navigations emit `tabs.onUpdated` with `changeInfo.url`, not `changeInfo.status === 'loading'` or `'complete'`. Since the content script explicitly does not patch `history.pushState`/`replaceState`, an SPA route change that changes the route stack leaves the old cache and badge in place until some unrelated scan happens.
Suggested fix:
- Handle `changeInfo.url` in this listener: ignore same-document hash changes, invalidate the tab for a new route, clear the badge, and schedule a deep scan for `changeInfo.url`.
## 2. Images and fetches are still treated as script evidence
Path: content/content-script.js
Line: 219
Issue: Every Resource Timing entry is pushed into the `scripts` pool, and the detector later runs script rules over any http(s) URL. A page that only loads `https://cdn.shopify.com/s/files/.../photo.jpg` as an image is therefore reported as Shopify with high confidence even though no Shopify script or runtime was present; the same failure mode applies to other host-only vendor rules.
Suggested fix:
- Filter performance entries by `initiatorType` before adding them: keep script-like entries in `scripts`, stylesheet/css entries in `stylesheets`, and exclude image/fetch/beacon/font/media resources from signature matching.
---
For each finding above, determine whether it is valid and should be fixed. If so, fix it directly. Where a finding offers several remedies, pick one and say why. Leave the pull request's own changed files alone unless a fix requires touching them.Summary
The change hardens WhatStack’s detection engine, adds background deep scans so the badge should match the popup, expands the signature set, and adds CI/corpus/browser coverage. Two correctness issues remain: SPA pushState navigations are not actually handled by the service-worker listener, and non-script resource timing entries are still fed into script evidence, allowing high-confidence vendor false positives from images or fetches.
Commands
Re-review the latest changes:
@xhawk-ai review again
Resolve all review threads and post a summary:
@xhawk-ai resolve all
| if (changeInfo.status === 'loading') { | ||
| // A new document (including a reload of the same URL) or an SPA route | ||
| // change. Drop what we knew; the next LIGHT_SCAN or onTabComplete rebuilds it. | ||
| orchestrator.invalidateTab(tabId); | ||
| } | ||
| if (changeInfo.status === 'complete' && tab && tab.url) { | ||
| orchestrator.onTabComplete(tabId, tab.url); |
There was a problem hiding this comment.
pushState route changes never invalidate or rescan the tab
History API navigations emit tabs.onUpdated with changeInfo.url, not changeInfo.status === 'loading' or 'complete'. Since the content script explicitly does not patch history.pushState/replaceState, an SPA route change that changes the route stack leaves the old cache and badge in place until some unrelated scan happens.
Suggestions
Handle changeInfo.url in this listener: ignore same-document hash changes, invalidate the tab for a new route, clear the badge, and schedule a deep scan for changeInfo.url.
| var perf = performance.getEntriesByType ? performance.getEntriesByType('resource') : []; | ||
| var limit = Math.min(perf.length, 600); | ||
| for (var i = 0; i < limit; i++) { | ||
| if (perf[i] && perf[i].name) scripts.push(String(perf[i].name).slice(0, 2000)); |
There was a problem hiding this comment.
Images and fetches are still treated as script evidence
Every Resource Timing entry is pushed into the scripts pool, and the detector later runs script rules over any http(s) URL. A page that only loads https://cdn.shopify.com/s/files/.../photo.jpg as an image is therefore reported as Shopify with high confidence even though no Shopify script or runtime was present; the same failure mode applies to other host-only vendor rules.
Suggestions
Filter performance entries by initiatorType before adding them: keep script-like entries in scripts, stylesheet/css entries in stylesheets, and exclude image/fetch/beacon/font/media resources from signature matching.
Implements every finding in the Sep 2026 audit (docs/AUDIT-2026-09.md) and bumps to 1.8.0. Every fix comes with a test that reproduces the 1.7.2 input and fails without the fix. Full notes: docs/RELEASE-1.8.0.md.
Results on 48 real sites (same harness before and after)
Fixed examples: linear.app no longer shows "Next.js v1.0.0-beta.9", clerk.com no longer shows "Bootstrap HIGH", getbootstrap.com no longer shows "Tailwind MEDIUM", and hotjar.com's Parcel (from an embedded widget) is now low. Newly detected: youtube.com → Polymer 3.5.0, astro.build → Astro, and discord.com → Webflow.
What changed
Precision (
fix(detect))window.nextis a version hint, never proof. A DOM-clobbered<a id="next">no longer produces Next.js + React.--tw-*variables → high, Tailwind-only syntax → medium, generic utility classes → low.Runtime (
fix(runtime))file:,data:and PDF pages show an honest "can't read this page" message instead of "nothing found". The popup and service worker share one list of such pages.Recall (18 new technologies)
Frameworks: Astro, Gatsby, Qwik, Preact, Lit, Polymer, Ember, AngularJS, Alpine.js, htmx.
CMS / site builders (new category): WordPress, Shopify, Webflow, Framer.
Analytics: PostHog, Plausible, Fathom, Amplitude.
Detectors now looking for the right names:
TURBOPACK__sveltekit_<hash>__svelte.v_reactListeningel.__vue__webpackJsonp_ngcontent-*stylesProof (
test:/chore:)audit-regressions: one test per audit finding.main-probe: the real probe, run innode:vmagainst throwing getters, Proxies and DOM clobbering.signature-coverage: every rule has a positive case and a prose / file-name / search-query case. This gate found three more false positives, which are fixed here.npm run corpus:captureand replayed offline innpm test.scan-orchestrator: 22 tests covering the background probe, badge behaviour, timeouts, re-injection and SPA rescans.Permissions unchanged:
scripting,tabs, http/https host access.Verified locally
npm run check: lint, type check, 433 tests, manifest and local-only audits all pass.npm run test:coverage: 85 % line coverage.HEADLESS=1 npm run test:browser: every case passes.Before publishing
npm run assets.npm run corpus:capture -- <host>.