v0.0.144
Released Oct 30, 2026
Release Notes
squirrel audit --max-duration stops at a wall-clock budget and writes a partial report instead of running on, --include-pages adds every crawled page with its status and rule count to the JSON report, and info checks now appear in the JSON and LLM reports (--include-info). The local technology scan reads stylesheet URLs and knows 24 more stacks, query-string copies of a page are counted with their canonical page, rules see controls inside declarative shadow roots, and a successful but empty page is audited and scored. A rule that throws is reported once as not evaluated, a rule that runs past its time budget is listed as skipped, and the docs now keep --offline and --passive apart. Thanks to William for the empty-page fix (#1314, #1330).
Added
squirrel audit --max-duration <seconds>puts a wall-clock budget on a whole audit. A slow or throttling site used to keep an audit running for minutes, and a batch job that killed it at its own timeout got no report and lost every page already crawled. At the limit the audit stops launching new work, finishes the pages in flight, runs the rules on what it fetched and writes the report withstatus: "partial", reason codemax_durationand amaxDurationobject that counts the pages discovered but never fetched, the fetched pages whose rules never ran, and whether the site-wide checks finished. It stops its own work within max(10% of the budget, 15 s) of the limit: link and resource checks are cut inside that margin and so is the rules phase. A partial report exits0like any finished audit (a--fail-ongate still decides pass or fail),squirrel reportshows a stored one the same way, and a run the limit did not cut is a normalcompletedreport. The budget counts from when the command starts, so the reachability check is inside it, and discovery (robots.txt, sitemaps, root files) may use at most 30% of it before the first page.[crawler] max_durationsets it insquirrel.toml, the flag overrides it, unset means no limit, 7 days is the ceiling, and a limit reached with no page fetched fails with exit1. The MCPaudit_websitetool takesmaxDuration. The margin covers waiting, not computation: one multi-megabyte page that takes longer than the margin to parse runs to completion. The flag is in the bash, zsh and fish completions. See Time limit. (#1271)--include-pagesadds the crawled pages to the JSON report. A pipeline that tracks which pages a run really covered had to stream-parse the entity map and guess. Withsquirrel audit -f json --include-pages(andsquirrel report -f json --include-pagesfor a stored audit) the report getspages: one{ url, status, ruleCount }entry per crawled page, the HTTP status it answered with and how many distinct rules reported on it. It is off by default, so existing reports and their size do not change. The list is cut at 2,000 pages, withpagesTotalfor the real count andpagesTruncated: truewhen entries were dropped. Every JSON report now carriesmeta.schemaVersion(1.1, the version that addedpages; a report without it is1.0). Pages that never produced a stored response (connection errors, server errors that survived retries, rate-limited requests) are not listed. See Pages list. (#1260)- Info checks are in the JSON and LLM reports. Notes a rule records with status
info(for exampleredundant-noindex, or "No dialog elements found") were in the local database but never in-f jsonor-f llm. The JSON report now has a top-levelinfoCheckslist (rule, check, message, up to 10 pages and the true page count), and the LLM report an<info-checks>block with--include-info(off by default, to keep the output small). They are not findings and never change the score. (#669) - Technology detection finds more of a site's stack in a logged-out audit. The free local scan now also reads stylesheet and preload
<link href>URLs (up to 40 a page, platform paths such as/wp-content/plugins/first, listed after the scripts), so a signal that lives in a stylesheet path, such as WooCommerce's plugin CSS, is no longer missed when the page also has scripts; the cloud payload is built the same way. It adds 23 first-party fingerprints: consent tools (Borlabs Cookie, Complianz, Real Cookie Banner, Cookie Notice, consentmanager, Klaro, Axeptio, CookieFirst, Cookie Information, Cookie-Script), tag managers (Cloudflare Zaraz, Matomo Tag Manager, Piwik PRO Tag Manager, Commanders Act), CMS and shop systems (Lightspeed eCom, Contao, SilverStripe, Plone, Grav, Odoo, Liferay, Pimcore) and Azure Front Door. The Technologies section now says whether it came from the local scan or the cloud scan (technologies.sourcein JSON, and on the llm and xml tags), and a local one shows where the list came from instead of a "no changes since last scan" line, or first-scan and added or removed counts, it cannot know. The docs explain what each scan reads and why a result can show little more than headers. (#1263) - A rule that ran past its time budget is listed in
skippedCheckswith reasonrule-timed-out, so the report no longer reads that rule as clean on the pages it did not finish. (#518)
Fixed
- A successful HTML page with a zero-byte body is no longer omitted from rule results. It is audited as an empty document, appears in the report, counts toward coverage and scoring, and receives findings such as missing title and H1. (#1314)
- Query-string variants of a page are counted with their canonical page.
/contactand/contact?subject=Billingused to be two affected pages for the same template finding, and two pages in the page total. A crawled page whoserel=canonicalis another crawled page of the same path is now counted with it: a finding on both lists one page, the page total, the scan line and the structure counts agree, and the report says how many variants were folded (meta.canonicalVariants, a note on the scan line,canonical-variantsin the LLM output). Variants with their own canonical (pagination, search, filters) stay separate, every variant is still fetched and checked so scores do not change, and a site with no variants reports exactly as before. See Query-string variants. (#858) - The Markdown report lists every page that carries a broken link. A broken link found on more than three pages showed the first three and "+N more pages", with no way to see the rest in the report. The Markdown report now lists all of them in the collapsed item block; the text, console and LLM reports keep the three-page preview. (#837)
- Rules now see controls inside declarative shadow roots. A page built with web components can put a button, link, image or heading inside
<template shadowrootmode="open">(or"closed"). A browser renders it, but the parser kept it inside the template's inert content, so no rule saw it anda11y/button-namemissed an unnamed button there. The content is now placed where the template was before the rules run; a plain<template>stays inert. See Rules. (#922) - A finding that carries pages from an earlier audit is no longer shown as a share of a page region. A smart audit keeps findings for pages it did not fetch again, and a region line such as "In the site footer, on 49 of 53 pages" could count those pages against this audit's own region totals. Like the page-type line (#857), the region line now steps aside when any of a finding's pages are carried, in every output format, and the finding keeps its page rows. Findings on fresh pages read as before and no score changes. (#860)
- A rule that throws is reported once as not evaluated, not as a failure on every page. A rule that threw on every page of a 30-page site showed 30 failed checks and lowered the score for one defect in squirrel. It is now a skipped
<rule>-errorcheck (not evaluated, like a rule that ran out of time) on each page, plus one<rule>-errorssummary with the first error and the page count, the score is unchanged, and the report prints aNot checked:line saying it is a squirrelscan defect. A rule that throws on every page still scores as if it passed, so--fail-ononwarningsorseveritycounts it as one warning and the JSONnotCheckedlists its first pages. See Rules. (#1291) - The docs and
--offlinehelp now keep offline and passive apart.--offlineis the network scope (no cloud, no other site) and--passiveturns the probe rules off; they are independent, so-C surface --offlinestill sends the GraphQL and form-action probe requests to the audited site. A new "Offline and passive are separate" section in the audit docs has the four combinations and says exactly what a passive offline run still sends (the 28 discovery probes unless--disable-discovery-probes, internal link checks, sitemap path guesses). The passive banner now readspassive · no probe rules. Behaviour is unchanged. (#1225) - Blocked-page detection follow-ups. A seed that redirects to a WAF challenge is one blocked page:
wafBlocked.totalcounts it once, and the URL you typed is no longer listed underwafBlocked.pagesbeside the destination when a sitemap or link names it again. A stored refusal that reads like a known provider's but carries none of its block markers (a bare "Access Denied" behind Cloudflare) is now noted instead of passing silently: the console, text, Markdown and LLM reports say its findings may describe the provider's page (a site's own short 403 behind a CDN reads the same, so it is a pointer, not a verdict), and the JSON report lists it underwafSuspect(total, up to 50pageswithurl,status,provider,providerIdandsignal, andproviders). The page is audited as before. Wordfence is attributed only when the page also carries the footer's generation timestamp or its inlinedocument.write(new Date().toUTCString())script, so a 403 that copies the "Your access to this site has been limited" wording is no longer called a Wordfence block. Netlify and Fastly Next-Gen WAF stay unrecognised, now with the reason in the docs: Netlify answers a firewall block with its ordinary branded 404, and Fastly's default block is a406with no documented body. See blocked by a WAF. (#1258)
Update to this version
squirrelscan will auto-update, or run this command to update now: