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-bundlefinds 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-payloadflags a mostly-text page that carries 100 KB or more of inline framework state.__NEXT_DATA__, the Next.js app router'sself.__next_fflight 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.hostand, 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_scriptsno 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, soperf/js-file-sizefailed 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.scriptsEvaluatedin 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. Seestore_scripts. (#1280)store_scriptsfollow-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-scriptsturns[storage] store_scripts = trueoff for one run.content-store.dband 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 auditruns no longer fail withSQLite 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 loginopens 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-certificatenow 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 fromissues,passedRulesandnotCheckedin-f json. It is now a skipped check with a reason, listed under "Not checked" in every format. WithHTTPS_PROXYorHTTP_PROXYset, the handshake goes through a CONNECT tunnel with SNI and verification intact (a host inNO_PROXYis 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-certificatewarns 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_daysanderror_daysare unset by default and, when set, replace the derived value with a fixed number of days as before. (#1279)eeat/about-page,eeat/contact-pageandeeat/privacy-policytake apathsoption.[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 noAboutPageschema. Patterns use the same forms asexcludePatterns(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 validateflags apathsvalue that is not an array of strings.eeat/about-pagealso recognises the German slugs/wer-wir-sindand/unsere-geschichte;/team,/agenturand/agencystay 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 auditand the MCPaudit_websitetool accept onlyhttp://andhttps://URLs with a host. Anything else (another scheme such asftp://orfile://, a scheme without slashes such asmailto:a@b.comorjavascript:x@y.com, no host, junk) is refused first:squirrel auditexits 1 before any cloud request, no run is registered or charged, and the MCP tool answersinvalid_urlwithout echoing the URL. A barehost:portsuch aslocalhost:3000still 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--headerfor an authenticated crawl. A URL over 2048 characters is audited but not registered as a dashboard run. The check is shared, sosquirrel crawland 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 auditand the MCPaudit_websitetool now check the start URL first: onlyhttp://andhttps://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 answersinvalid_urlwithout echoing the URL. Credentials in a start URL are no longer sent in the run registration. perf/total-byte-weightleaves lazy images below the initial viewport out of the page weight. Everyloading="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 aslazyImagesin the details; eager images and lazy images in the initial viewport still count. (#1252)images/image-file-sizeandperf/total-byte-weightmeasure the srcset candidate a browser downloads. An<img>withsrcsetandsizeswas measured by itssrcfallback, 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 forimages/responsive-sizebut is no longer reported or counted as page weight. (#1251)images/offscreen-lazycounts 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 addloading="lazy". Images declared with bothwidthandheightat 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: