Skip to main content

v0.0.143

Released Oct 30, 2026


Release Notes

Two new performance rules, perf/content-in-js-bundle and perf/framework-data-payload, find page content shipped inside JavaScript. Page weight now measures the srcset candidate a browser downloads and leaves lazy images out, squirrel auth login opens a branded page, parallel audits no longer fail with a locked database, and store_scripts no longer changes what an audit finds. A start URL the audit cannot run on is refused before any request, security/tls-certificate shows as not checked when it cannot run, and the about, contact and privacy rules take a paths option.

Added

  • New rule perf/content-in-js-bundle finds page content compiled into a JavaScript chunk. A site that imports its MDX or Markdown bodies eagerly (or imports CMS JSON at build time) ships every page's text to every visitor as script, on top of the server-rendered HTML. squirrel keeps up to five distinctive sentences of each page's main content, then searches the site's same-origin chunks of 50 KB or more for them, through JavaScript string escapes, HTML entities and JSX quoting. A chunk holding one page's own content is information (a hard-coded route component looks the same: 4 of 36 real documentation sites), and one that also holds at least two other pages' content is a warning (its own sitewide check). Browser-rendered pages are not judged, and chunks the audit could not fetch are reported as not checked rather than clean. Each finding gives the chunk URL, its raw and gzip size, how many pages' content it holds and an estimated prose share. Navigation, footer and template text, inline hydration state and chunks under 50 KB are ignored, and the search is bounded (10 largest chunks, 2,000 pages, one linear pass per chunk, 2.5 seconds). See the rule page. (#1200)
  • New info rule perf/framework-data-payload flags a mostly-text page that carries 100 KB or more of inline framework state. __NEXT_DATA__, the Next.js app router's self.__next_f flight chunks, window.__NUXT__ and __NUXT_DATA__, Remix, Qwik, SvelteKit and Astro island props are measured per page. It reports only when the payload is at least 20 times the page's visible text and the page has at most 5 form controls and embeds and 50 buttons, so interactive apps and data-heavy pages are left alone. The thresholds come from a run over 40 public pages and are options (min_bytes, min_text_ratio, max_interactive, max_buttons). See the rule page. (#1143)
  • Technology detection knows Bolt and the WordPress plugin and managed host landscape. bolt-new (Bolt, by StackBlitz) matches apps served from *.bolt.host and, on custom domains, the "Made in Bolt" badge link; it never matches Bolt's checkout product. Twenty-one WordPress fingerprints add caching plugins (WP Rocket, LiteSpeed Cache, W3 Total Cache, WP Super Cache, WP Fastest Cache, Autoptimize, Perfmatters), SEO plugins (Yoast SEO, Rank Math, All in One SEO, SEOPress), image plugins (ShortPixel, Imagify, EWWW Image Optimizer, Smush) and managed hosts (WP Engine, Kinsta, Pressable, WordPress VIP, SiteGround, Cloudways). A plugin is detected from its asset URL, its own HTML comment or its schema block, never from its name in text, and a host from its response header. Three new technology categories group them: Caching & Performance, SEO Plugins and Image Optimization. (#1278, #1033)

Fixed

  • An unreachable start URL's error text no longer echoes credentials. The reason and message in the status: "unreachable" report, the console line and the run record drop userinfo, query tokens and fragments from any URL the probe's error mentions.
  • store_scripts no longer changes what an audit finds. With [storage] store_scripts = true (or --store-scripts) the audit fetched up to 200 scripts and gave all of them to the script rules, so perf/js-file-size failed on 36.5% of the same sites against 29.8% with it off, and scores moved. The rules now evaluate the same scripts they do with the option off (the first 10 to 50, in page order); the extra scripts are fetched and stored only. meta.scriptsEvaluated in the JSON report (and <scripts-evaluated> in the LLM output) says how many first-party scripts the script rules evaluated, so two runs can be compared. See store_scripts. (#1280)
  • store_scripts follow-ups. The warning for scripts that were not stored now lists each reason with its count and the setting that applies (too large, total cap, memory cap, time budget) instead of blaming the size settings for all of them. squirrel audit --no-store-scripts turns [storage] store_scripts = true off for one run. content-store.db and project databases are created owner-only (0600), and existing ones are tightened when opened. The storage docs now state that the time and memory bounds are soft (about 20 seconds, up to 10 bodies in flight, roughly 3x memory for non-Latin1 bundles), that the preload pass has its own counters, and that the empty script tables exist when the option is off. (#1268)
  • Parallel squirrel audit runs no longer fail with SQLite database is busy (locked). Ten audits at once on one machine lost a few to a lock error and wrote no report. The lock timeout is now set before any other statement on every connection to the content store, the link cache and project databases, switching a new database to WAL waits for its lock, and write transactions take the write lock up front instead of failing when they upgrade. A lock that is still held after the timeout names the database file and the wait. See Running several audits at once. (#1226)
  • The page squirrel auth login opens in your browser is squirrelscan branded. It had been a plain white card with a green heading. It now has the squirrel mark and wordmark, the brand palette and type, a signed-in state with your email, the agent setup prompt and copy button restyled to match, and light and dark themes that work at phone width. The expired, already-used and waiting pages use the same layout. The page is self-contained (inline mark and styles, nothing fetched, a Content-Security-Policy that allows only its own copy script), and only the escaped email is interpolated. (#1286)
  • security/tls-certificate now shows up as "Not checked" and no longer disappears behind an HTTP proxy. When no handshake was made the rule returned an info check the report dropped, so it was missing from issues, passedRules and notChecked in -f json. It is now a skipped check with a reason, listed under "Not checked" in every format. With HTTPS_PROXY or HTTP_PROXY set, the handshake goes through a CONNECT tunnel with SNI and verification intact (a host in NO_PROXY is probed directly), and a proxy that refuses or fails the tunnel, or one this check cannot tunnel through, gives a "Not checked" line with that reason instead of a pass. (#1259)
  • security/tls-certificate warns relative to the certificate's lifetime, not a fixed 30 days. Automated CAs renew a 90-day certificate at about 30 days left, so the fixed 30-day warning fired on healthy, auto-renewing certificates before every routine renewal (about 73% of expiry findings in a 38k-site sample sat at 25 to 30 days left). The default warning now waits until a renewal is 14 days overdue: the last third of the certificate's lifetime less 14 days, 16 days for a 90-day certificate. 28 days left passes, 10 warns, 5 fails. Short-lived certificates get proportionate windows (a tenth of the lifetime for the error threshold when under 70 days, so a 6-day certificate does not fail on its third day and a 47-day one keeps a warning step). warn_days and error_days are unset by default and, when set, replace the derived value with a fixed number of days as before. (#1279)
  • eeat/about-page, eeat/contact-page and eeat/privacy-policy take a paths option. [rule_options."eeat/about-page"] paths = ["/pages/manufaktur"] makes a crawled page at that path satisfy the rule when no slug convention fits and the page carries no AboutPage schema. Patterns use the same forms as excludePatterns (substring, /exact-segment/, regex:), are checked before the built-in slugs, and a configured path that pages link to but the crawl did not fetch is reported as linked but not crawled. squirrel config validate flags a paths value that is not an array of strings. eeat/about-page also recognises the German slugs /wer-wir-sind and /unsere-geschichte; /team, /agentur and /agency stay out of the built-in list because those pages are often not an About page. (#1261)
  • A start URL the audit cannot run on is refused before any request. squirrel audit and the MCP audit_website tool accept only http:// and https:// URLs with a host. Anything else (another scheme such as ftp:// or file://, a scheme without slashes such as mailto:a@b.com or javascript:x@y.com, no host, junk) is refused first: squirrel audit exits 1 before any cloud request, no run is registered or charged, and the MCP tool answers invalid_url without echoing the URL. A bare host:port such as localhost:3000 still works. Credentials (user:password@) and the fragment in a start URL are dropped before anything is crawled, logged, registered or sent to the cloud, with a note on stderr; use --header for an authenticated crawl. A URL over 2048 characters is audited but not registered as a dashboard run. The check is shared, so squirrel crawl and the cloud runtime also stop sending Basic auth taken from URL credentials.
  • A start URL the audit cannot run on no longer registers a cloud run. squirrel audit and the MCP audit_website tool now check the start URL first: only http:// and https:// URLs with a host are accepted. Anything else (another scheme, no host, junk) is refused before any cloud request, with nothing registered or charged, and the MCP tool answers invalid_url without echoing the URL. Credentials in a start URL are no longer sent in the run registration.
  • perf/total-byte-weight leaves lazy images below the initial viewport out of the page weight. Every loading="lazy" image was added to the page total, so a page that loads 1.3 MB was reported at 5.4 MB. Lazy images past the first three content images are now reported separately as lazyImages in the details; eager images and lazy images in the initial viewport still count. (#1252)
  • images/image-file-size and perf/total-byte-weight measure the srcset candidate a browser downloads. An <img> with srcset and sizes was measured by its src fallback, so a 960w fallback was reported although visitors get the 400w file. The audit now selects the candidate at a reference 1280 px viewport and 2x density (largest when none covers it) and measures it; the fallback is still fetched for images/responsive-size but is no longer reported or counted as page weight. (#1251)
  • images/offscreen-lazy counts the above-the-fold budget in content images. A header of small icons used up the three-image budget and pushed the first real content image into the below-the-fold set, so top-of-page images were told to add loading="lazy". Images declared with both width and height at icon or logo size (under 20,000 square pixels) no longer fill the budget or count as below the fold until it is full; undeclared images count as content as before. (#1250)

Update to this version

squirrelscan will auto-update, or run this command to update now:

$

Downloads

macOS

Detected
Intel
squirrel-0.0.143-darwin-x64 · 110.4 MB
Download
SHA-256
Apple Silicon (M-series)
squirrel-0.0.143-darwin-arm64 · 101.9 MB
Download
SHA-256

Linux

ARM64 (musl/Alpine)
squirrel-0.0.143-linux-arm64-musl · 113.6 MB
Download
SHA-256
x64
squirrel-0.0.143-linux-x64 · 120.8 MB
Download
SHA-256
ARM64
squirrel-0.0.143-linux-arm64 · 120.9 MB
Download
SHA-256
x64 (musl/Alpine)
squirrel-0.0.143-linux-x64-musl · 114.6 MB
Download
SHA-256

Windows

x64 (Intel/AMD)
squirrel-0.0.143-windows-x64.exe · 126.6 MB
Download
SHA-256

Audit your site in one command

SEO, performance, security, accessibility and agent experience issues, with exact fixes for your coding agent.

Install
$

No account needed for the CLI. Cloud audits include free monthly credits.