Skip to main content

Releases

Release history and changelogs

All releases up to v0.1 will be in the stable channel.

Release channel

Release History

v0.0.133Oct 24, 2026Latest

Release Notes

White-label reports get a website and a footer, and the docs get a report walkthrough.

Added

  • White-label reports can show a website link and footer text. Team and Enterprise organizations that use white-label reports can add a website and a footer line, such as a copyright notice. The report header links the logo or brand name to the website, and the footer shows the text and the website link where the squirrelscan credit was, in HTML, Markdown and text reports, including local reports from a signed-in CLI. Only an https:// website is linked. A report keeps the branding it was published with (HTML report).

Security

  • The zod validation library the squirrel CLI bundles moves to 4.6.7, which hardens validation against oversized input.
  • The docs toolchain is patched for a vulnerable undici. It is docs-site only; nothing in the squirrel binary loads it.

Docs

  • Every rule page links to its glossary terms. Each rule page ends its definition with the glossary entries for the terms it uses.
  • A new guide, How to read your report. A walk through a real report from the health score down to what was not checked. The README and the reports page now show the same current report.
  • Stale claims on rule pages are fixed, and links to Google's crawler and robots.txt documentation point at its current pages.

Downloads

Windows
Linux
v0.0.132Oct 23, 2026

Release Notes

Reports can show what passed, score rings tell the truth about color, and the docs get a glossary.

Added

  • squirrel audit and squirrel report take --include-passed, which lists the rules that passed after the issues, in every format: each rule with at least one passing check and no warning or error, with how many pages it passed on, site-wide for a site rule, or pages not recorded for a page rule whose passes name no page. On the HTML report it is one closed line under the findings. JSON gets a passedRules array (ruleId, name, category, scope, pages). A report read with --input lists none. Without the flag every format renders exactly as before. See report.

Changed

  • Group score rings read by score, not by group. On the HTML report the SEO, Performance, Security and Agents rings, numbers and grades are colored red to green by score, like the health score, so a 45 F looks like one. The group's own color stays on its card's top border and label. Every score ring on the page uses a darker tone of the same red, amber and green bands, so the numbers pass WCAG AA contrast on the white cards.

Fixed

  • Copy-to-agent prompts keep site text fenced. (Security.) The HTML report's fix prompts now strip hidden characters and the internal note placeholder before they defuse quote fences, so text from the audited site can no longer piece a fence marker back together and step outside its untrusted block.

Docs

  • A glossary. docs.squirrelscan.com/glossary has an A to Z index and 64 short definitions, from canonical URL to llms.txt, each linked to the rules that check it.
  • The team page no longer lists SAML, SCIM or verified-domain auto-join as Enterprise features: squirrelscan does not support them today.
  • The CI guide now says --fail-on checks each run against your thresholds, not against an earlier audit, and points at squirrel report --regression-since for comparing two audits.
v0.0.131Oct 23, 2026

Release Notes

Reports are honest about how much of your site they saw, and the HTML page lets you dismiss every notice.

Changed

  • Summaries keep to one score and the report's own counts. When a report shows its editor's summary (HTML, Markdown, text, XML, JSON, LLM and the console), at most one numeric score survives and /100 never appears, and a sentence that states a page count the report does not have is left out. Older stored summaries get the same treatment. If nothing survives, the summary falls back to the report's own totals instead of going blank.
  • Every notice line on the HTML report can be dismissed, not just the coverage notice, each on its own for that report in that browser. This works in a saved offline file too. The "Scan: N pages crawled" line is gone (the header already says what was scanned and at what level), and a published report's "this is a summary" note moves to the footer.
  • The cloud editor's summary now hears how much of the site the audit covered (pages scanned, the page limit, a time stop, pages known), so it can say when a report covers only part of the site and suggest a deeper audit.
  • The HTML report's coverage notice is one line. A report whose crawl hit its page limit, ran out of time or knows more pages than it scanned now shows a single dismissible line at the top: the report may not cover the whole site, how to raise the audit depth (--level full or --max-pages N in the CLI, or the website settings in the cloud) and a link to Audit levels. It adds a count only when more pages are known, so a capped crawl no longer reads "200 of 200" or "the other 0+ pages". Dismissing it hides it for that report in that browser, and with browser storage blocked it hides for that visit only (html format).

Fixed

  • A capped audit counts the pages it found but did not reach. When a crawl stops before it fetches every page it found, the in-scope URLs still queued (sitemap entries and links that passed the scope and robots checks) now count as discovered, so a 200-page audit of a bigger site no longer reads "200 of 200 discovered pages". A local HTML report shows the count as the floor it is ("Scanned 200 of 731+ discovered pages"), the console, text and Markdown scan lines add "531 more discovered but not fetched", JSON carries it in meta.unfetched and the LLM and XML formats in <scan-scope unfetched="...">, and the editor's summary request sends it as pages found but not fetched (JSON output).
v0.0.130Oct 23, 2026

Release Notes

Local HTML reports get a glow-up: same page as the cloud, still fully offline. Plus a docs-site security patch.

Security

  • DOMPurify moves to 3.4.17 for GHSA-cg2g-xh64-v4q7 (critical), GHSA-mrv2-3pqr-j5jr (high) and GHSA-c6gr-qvv4-5v3f (low). It is a docs-site dependency only; nothing in the squirrel binary loads it.
  • The dependency audit now ignores two Astro advisories, GHSA-98hj-9722-w3j8 (moderate) and GHSA-mpr7-6m5w-76rw (low). Astro stays capped below 7.2.10 until tangly moves to @astrojs/mdx 8, and only the static docs build uses it, with no untrusted input. The reasoning is in .github/dependency-audit-allowlist.md.

Changed

  • The HTML report is redesigned, and local reports get the same page as published ones. --format html now writes the page reports.squirrelscan.com shows: the health and group scores up top, a coverage notice with the command for a larger audit when the crawl stopped short, the most widespread errors and warnings to fix first, every finding banded by severity and filterable by group, the same findings by page, and the site profile, tech stack and entity map. Every finding has a Copy fix prompt button that copies a prompt for your coding agent: the rules to fix, their docs, the audit's evidence fenced as untrusted data, and the squirrel audit command that verifies the fix. A signed-out local report also shows what a free cloud account adds, and the checks on the site that need it. The file is still self-contained and makes no network request when opened: no favicon and no technology logos. HTML files written by earlier versions keep the old design; run squirrel report -f html again (or a fresh audit) to get the new page (html format).
v0.0.129Oct 22, 2026

Release Notes

The editor's summary gets a ranked Priorities section, and the summary request now knows your stack.

Added

  • The editor's summary gets a ranked Priorities section. When the cloud summary includes priorities, every output format (console, HTML, Markdown, text, JSON, XML and LLM) shows them as their own section, up to eight, highest impact first: a title, why it matters for the site, the concrete action and an effort rating (quick win, moderate or significant). JSON carries them in editorSummary.priorities. A summary without priorities, such as one from before this release, shows the big-ticket list as before, and the big-ticket list gives way to Priorities when both exist (editor's summary).
  • The summary request sends your stack and every failing rule. The request now carries the technologies the audit detected and the id of every rule that failed or warned (not just the ranked top issues), so actions can say where the fix is made on your platform. Both are optional and left out when empty. No rules or scoring change.
v0.0.128Oct 22, 2026

Release Notes

The CLI moves to Bun 1.4.4, a crawl with a live owner is never retired, and a check that has nothing to check on a page stops keeping an old finding open.

Changed

  • The CLI is built with Bun 1.4.4. A TLS chain that holds any of eight blocked eMudhra intermediate CA public keys is now refused, and the root store moves to NSS 3.131.1. Seven roots are no longer bundled (Entrust Root Certification Authority 2006, ePKI, Atos TrustedRoot 2011, SecureSign Root CA12, emSign Root CA C1, emSign ECC Root CA G3 and emSign ECC Root CA C3), so a site whose chain ends only at one of those can fail TLS. SECOM and Telia TLS roots are added. The binary is about 24 MB larger (73 to 97 MB on macOS arm64).

Fixed

  • a11y: an element a CSS-nesting rule may hide (b { @media (min-width: 0) { display: none } }, .a { & .b { display: none } }) now reads as unconfirmed instead of shown, so an unnamed control a browser hides is reported as <check>-unconfirmed, not as a failure. A nested rule that sets neither display nor visibility changes nothing. The reader's Chromium table gains 13 nesting rows measured in headless Chromium (#1013, repro from #1014 by @dbdefault).
  • A smart audit now closes a finding on a page the check no longer applies to, instead of carrying it forever, where the skip is a property of the page's own headers, URL or markup: no Link header (core/canonical-header), no file extension (content/mime-type), not paginated (crawl/pagination), no canonical tag (crawl/canonical-chain), no viewport meta (mobile/viewport-zoom), no <picture> (images/picture-element), a documentation page (content/placeholder-contact), no form sets novalidate (a11y/input-types), no copyright year (content/stale-copyright), a small 2xx page (ax/token-weight) and a page excluded by pattern (links/dead-end-pages). Those skip with a shared not-applicable reason that the resolution signal and the local and cloud merges read as evaluated and clean. A skip that could be a fetch, parse or rendering artifact (an empty response, no body, no images or form fields on a page a script may fill, no footer) still carries its finding, as does a check that did not run (no timing data, a scan stopped at its work cap). The skipped row's JSON carries details.foldKey: not-applicable and no longer the free-text skipReason; its message keeps the text (#1011).
  • a11y: a page past the style reader's limits (more than 5,000 style rules, an over-long <style> block or selector, a no-break or other non-ASCII space in a style block, or a rule nested inside another) now reads every element as unconfirmed for what a style might hide, instead of as shown. Before, the rules past the limit were dropped, so an element they hid could read as shown and an unnamed control inside it failed; now the naming checks report <check>-unconfirmed there. A page under the limits reads as before (#1015).

Added

  • a11y, links: link-text and anchor-text now recognise generic link text in Spanish, French, German, Portuguese, Italian, Dutch, Japanese, Chinese, Korean and Russian (cliquez ici, mehr, haga clic aquí, 詳しくはこちら and similar), chosen by the page's <html lang>, with English always checked. A page with no or an unknown lang keeps the English list, matching is on the whole text, and the conventional account and legal labels are never reported (#840).
  • content: word-count skips pages kept out of search (noindex meta or X-Robots-Tag), like core/meta-title, core/meta-description and core/h1: a login, sign-up or dashboard page is short by design. The page gets one skipped check naming the source, and nothing is skipped on a staging or preview site whose homepage is noindex (#841).
  • Retention and self disk --prune now read the crawl lease that v0.0.127 started writing (#946). A crawl whose heartbeat is under 5 minutes old, or whose owner process is still alive on this machine (trusted for up to 24 hours of silence, so a suspended laptop is safe and a reused pid cannot hold a crawl forever), is never retired, including when a later audit of the same project finishes first. A killed crawled crawl is retired by the next audit and by --prune once its heartbeat is 5 minutes old, and a killed running crawl, which --resume continues, after 24 hours, in both cases without waiting for a later audit; stopped, paused and failed crawls are never retired sooner by a dead lease: stopped and paused keep their 7 days, failed its 30 minutes. A newer finished audit plus the idle time (30 minutes, or 7 days for stopped and paused) retires any of them as before. An owner on another host or one that cannot be read counts as live until the 5 minutes pass, and the check runs again inside the retiring transaction. Crawls written by older versions have no lease and follow the previous rules, and --prune --dry-run shows a held crawl's owner pid and last heartbeat. A --resume after a kill takes the lease over at once when the old owner's process is gone.
v0.0.127Oct 22, 2026

Release Notes

Custom request headers stay with the audited origin unless you name another host, the default rule time budget doubles, and a smart audit keeps findings it did not get to re-check.

Changed

  • Each crawl records which process drives it: squirrel audit, crawl and analyze write their pid, host name and a heartbeat (every 15 seconds, on a second database connection that skips a beat rather than wait on a busy database) on the crawl, and clear the owner when they end. Nothing reads it yet, so retention and self disk --prune behave as before; a later release will use it to keep a live crawl and retire a killed one sooner. The database gains three nullable columns (schema 35) that older versions ignore, and an audit of 1,000 pages is no slower (#946).
  • a11y: an unnamed control inside an element that display: none hides only inside width-only media queries (a theme's mobile menu, @media (min-width: 990px) { menu-drawer { display: none } }) is still reported as <check>-unconfirmed, but with its own reason, hidden-only-at-some-widths, and the widths where it shows ("shown below 990px"), so it reads apart from a style squirrel cannot settle. Queries that together cover every width (min-width: 990px with max-width: 989.98px, in colon or range syntax, em or rem) leave the element hidden at every width and it is not reported. Any other media feature, not, @container, @supports or a <style media> keeps the generic reason. A control inside a rule whose queries can never both match ((min-width: 990px) and (max-width: 989px)) is shown, so one an earlier audit reported as unconfirmed can now fail, as it does in a browser (#988).
  • The default per-rule time budget is now 2 seconds (was 1 second), so a rule that runs slowly on a busy machine is abandoned less often. A site rule gets 2 seconds per page of the site, still capped at 60 seconds. [rules] time_budget_ms still overrides it, and values below 1000 are still refused.
  • legal/cookie-consent finds its consent signals in one pass over the page's elements instead of 28 selector sweeps, and social/share-buttons no longer lowercases a copy of the page. Both give the same answers as before; on a 1 MB script-heavy page legal/cookie-consent is about 3 to 6x faster (#270).

Fixed

  • Custom request headers (-H, [crawler] headers) are now sent only to the audited origin (scheme, host and port) on the crawl's page fetches, the sitemap and license fetches, and the soft-404 and cloaking re-fetches, as they already were for assets. A page on another scheme, port or host, including one [project] domains or an include pattern admits, is still crawled and reported but is requested without them, and an https: start URL that redirects to http: keeps its https: scope. Name a host in [crawler] header_hosts (or --header-host) to send them there; a named host now gets them over https: only (before, any scheme matched), so write an entry with an http:// prefix to keep sending them over plain http. An entry may carry a scheme, port, path or trailing slash, which are ignored; a wildcard, userinfo, empty label or trailing dot is refused. A www host of an apex audit now needs to be named too (#959).
  • A smart audit no longer marks a finding fixed when its check was skipped on the page in the re-crawl (for example perf/ttfb with no timing data, or a scan that stopped at its work cap) while the check ran on other pages. The finding stays open as carried. This now holds in the local audit and in a cloud publish whose resolution signal is over budget, not only when the signal lists the page (#508). A rule that timed out or threw on a page carries its findings there in the local audit too. A check skipped on every page, a rule disabled in config and a cloud audit that streams its findings to the store (which never sees skipped checks) still close the finding. A few rules also skip a page because the check does not apply to it, and those carry for now (#1011).
  • links: Cloudflare email-protection links (/cdn-cgi/l/email-protection#<hex>) are read as the mailto: links they are. tel-mailto counts them and checks the decoded address (one that does not decode is never reported as invalid or mismatched), nofollow-internal and https-downgrade no longer judge them as page links, and the crawler never queues the path from a sitemap, a carried finding or a cached page's links. /cdn-cgi/image/ URLs are still checked as images (#838).
  • a11y: link-text no longer reports conventional account and site-utility labels (Sign in, Sign up, Log out, Register, Terms, Privacy policy, Contact, FAQ and similar, in 11 languages) as generic link text. click here, read more and the other generic phrases are still reported (#839).
  • links: no-contextual-inbound no longer reports legal pages (privacy, terms, imprint, /legal, in any recognised language and under any path prefix) that are linked only from the footer, which is where they belong (#981).
  • A smart audit no longer marks a finding fixed when its rule threw an error on every page of the re-crawl. The rule never finished, so nothing was checked, and the earlier finding now stays open until a run completes the rule, the same as for a rule that times out (#999). A rule that timed out on only some pages still carries only those pages' findings, and a page it finished still resolves (#1000).
  • a11y: frame-title, object-alt and empty-heading now read which elements are out of the accessibility tree the same way as button-name and the other naming rules. A hidden element is skipped and counted, and one a style may hide (a no-js fallback on a page with an external stylesheet included) is reported as <check>-unconfirmed instead of a failure. A visible one still fails (#1002).
v0.0.126Oct 21, 2026

Release Notes

Seven more accessibility checks stop failing on hidden elements, and a smart audit no longer calls a finding fixed when its rule never finished.

Fixed

  • a11y: aria-meter-name, aria-progressbar-name, aria-toggle-field-name, aria-tooltip-name, aria-treeitem-name, input-image-alt and link-text now read which elements are out of the accessibility tree the same way as button-name and select-name. An element hidden by hidden, aria-hidden, display: none or a closed dialog is no longer reported, and one a style may hide (a no-js fallback on a page with an external stylesheet included) is reported as <check>-unconfirmed instead of a failure. A visible one still fails. Reading hidden elements is also faster on large pages (#993).
  • A smart audit no longer marks a finding fixed when its rule timed out on every page of the re-crawl. The rule never finished, so nothing was checked, and the earlier finding now stays open until a run completes the rule.
v0.0.125Oct 21, 2026

Release Notes

Fixes for false accessibility failures: on themes that hide a no-JavaScript fallback once scripts run, and on hidden <select> elements.

Fixed

  • The accessibility checks that skip hidden controls (a11y/form-labels, a11y/aria-input-field-name, a11y/select-name, a11y/aria-labels, a11y/aria-command-name, a11y/aria-dialog-name, a11y/button-name and a11y/aria-hidden-focus) now report a control they would have decided was shown (an unnamed control, or a focusable one inside aria-hidden) as unconfirmed, not as a failure, when it or an ancestor other than <html> or <body> has a no-js class and squirrel could not read all of the page's styles (for example, an external stylesheet or an @import). Themes may hide such a fallback once JavaScript runs, with a rule like .js .no-js { display: none } in a stylesheet squirrel does not read. Items unconfirmed this way say so: it or an ancestor carries a no-js class, and a stylesheet squirrel did not read may hide it when JavaScript runs. A page whose styles were all read, a control with no such class, another class such as no-js-fallback, and a no-js class on <html> itself keep their verdict. See a11y/form-labels.
  • a11y/select-name now reads which selects are out of the accessibility tree the way the other naming checks do. An unnamed select that the hidden attribute, aria-hidden="true", a closed dialog, or a display: none squirrel can settle (inline or in a page <style> rule) keeps hidden is no longer reported. One a style may hide, a no-js fallback on a page with unread styles included, is reported as select-name-unconfirmed (a warning) instead of a failure. An unnamed select squirrel reads as shown still fails, and one hidden only by an external stylesheet is still reported. See a11y/select-name.
v0.0.124Oct 21, 2026

Release Notes

A small accessibility fix: a <style> rule that may hide elements and uses an operator attribute selector, such as [id$="-drawer"], no longer turns the accessibility failures of unrelated controls into warnings.

Fixed

  • The accessibility checks that skip hidden controls now read attribute selectors with an operator ([id$="-drawer"], [href^="#modal"], [data-state*="closed" i], [class~="a"], [lang|="en"]) in a <style> rule. A bare operator selector used to lose its attribute test, so a rule like [id$="-drawer"] { display: none } that matched nothing still left every unnamed control on the page unconfirmed. It now reaches only elements that have that attribute with a value that could satisfy it, compared without regard to ASCII case, so a11y/button-name, a11y/form-labels and the other checks can fail the rest again. An element whose attribute could satisfy the operator can still be left unconfirmed, with the visible controls inside it, as <html lang="en"> against [lang|="en"] { display: none } shows. A plain [id="drawer"] test inside a selector squirrel cannot settle, such as .is-open [id="drawer"], now compares the value too, so it no longer leaves <input id="q"> unconfirmed. Reading these rules is also faster on large pages. See a11y/button-name.
v0.0.123Oct 21, 2026

Release Notes

A small accessibility fix: the old vendor-prefixed flexbox fallbacks in inline styles and a page's <style> blocks no longer turn accessibility failures into warnings.

Fixed

  • The accessibility checks that skip hidden controls now read vendor-prefixed display values (-webkit-box, -webkit-flex, -ms-flexbox, -moz-box and the like) as values that show the element. The old flexbox fallback chain (display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex) used to make the whole declaration block unreadable, which could leave the unnamed controls under it unconfirmed, so a11y/button-name and the other naming checks can fail those controls again when nothing else hides them. A -ms- or -moz- value stays unconfirmed unless a later standard display overrides it (!important respected), because browsers differ on it. A display: none that wins after the prefixed values still hides the element where squirrel can settle the rule, and a display set from var() stays unconfirmed. See a11y/button-name.
v0.0.122Oct 20, 2026

Release Notes

A release about saying what actually happened. A rule that runs out of time is now reported as not checked, with a Not checked line in every report, rather than as a failure, and a timed-out security rule trips warning gates instead of error gates (see below). squirrel audit without a terminal prints a progress line while it walks the crawled pages, and WordPress's image and lightbox styles no longer soften accessibility failures on controls nothing else hides.

Added

  • squirrel audit without a terminal now prints Walking pages: N/M to stderr, about every 15 seconds, while it walks the crawled pages before the rules run. That step could go minutes without output on a big site, so a supervisor with a no-output watchdog could not tell it from a hang. The line is written at the end of a batch of pages, other than the last, once the interval has passed since the walk started or the last line. Batch length varies with page size, so a walk that fits in one batch prints no line, and a batch that takes longer than the interval shows nothing until it ends. Set SQUIRREL_WALK_HEARTBEAT_MS to a positive number of milliseconds to change the interval. A terminal keeps its spinner, and stdout (--format json, pipes) is unchanged. See audit.

Changed

  • A rule abandoned for its time budget is now a skipped check, not a failed one. It is reported as <rule>-error with skipReason: "rule-timed-out" on every page it timed out on, the first included, with elapsedMs, the wall-clock time it ran before it was stopped. It adds nothing to the failed counts and no longer lowers the health score, so a site where a rule timed out may score higher than before.
    • Every report format, the CLI's own output included, prints a Not checked line for a timed-out rule above info severity, with the rule's name and how many pages it did not check, such as Not checked: External Link Security. External Link Security timed out on 1 page (exceeded its 1000 ms time budget).
    • --fail-on: a timed-out security rule counts as one warning-severity finding, so warnings>0 and severity>=warning trip on it and the gate summary names the rule. It is counted only for the gate, not in the report's warning count or the score. The timeout itself adds nothing to the error count or the score, a security rule's included, so a CI job that gated on errors>0 and failed only because a rule timed out now passes. Add a warning gate to keep catching a timed-out security rule, or raise time_budget_ms. Other rules that time out are not counted by any gate.
    • A page-time extractor that runs past the budget is handled the same way, when the site rule that reads it runs.
    • See Rule time budget.

Fixed

  • The accessibility checks that skip hidden controls no longer leave a control unconfirmed because of a <style> rule squirrel cannot fully decide whose declarations can only show an element (display: inline-block, display: flex, visibility: visible). WordPress core's .wp-block-image>a and .wp-lightbox-container button rules kept a page's links and buttons unconfirmed, so a11y/button-name and the other naming checks can fail them again. Such a rule can still leave a control unconfirmed where it could undo something else that hides it, such as the hidden attribute, a closed dialog, a popover, a display: none or an inherited visibility: hidden. display: contents, inherit and unset do not count as only showing. See a11y/button-name.
  • The <rule>-timeouts info check, which counts the pages a timed-out rule did not check (pagesNotChecked, and maxElapsedMs when it was measured), now reaches the stored results and every report for a page rule too. It was dropped before, so a timed-out page rule left no such row in project.db or the report. See Rule time budget.
v0.0.121Oct 20, 2026

Release Notes

A release about where your custom request headers go. Asset requests now send them to the audited origin only, with a new header_hosts setting for the site's other hosts that need them, and the soft-404 and cloaking probes no longer carry them across a redirect to another site. Also: squirrel analyze applies keep_audits retention, Tailwind v4's preflight no longer softens accessibility failures, and the link checks leave two more shapes of sign-out and credential link alone.

Thanks to @deniskern, whose report is behind the squirrel analyze retention fix below.

Security

  • Custom request headers (-H, [crawler] headers) now go only to the audited origin on asset requests such as stylesheets, scripts and images, not to every subdomain of the audited host. The audited origin is the scheme, host and port the audit runs on: the start URL, after a redirect within the site. A subdomain is not always run by the site's owner, and these headers usually carry credentials. To send them to one of the site's hosts that needs them, such as assets.example.com behind Cloudflare Access, name it with [crawler] header_hosts = ["assets.example.com"] or --header-host assets.example.com (repeatable or comma-separated). Assets on other sites never get them, as before. When an audit has custom headers set, a script or stylesheet on a host that was not sent them and answers 401 or 403 is now listed as unverified by reliability/broken-resources, naming its host, instead of being reported as broken, and mobile/web-app-manifest reports such a manifest or icon as not checked. See Crawler configuration.
  • The soft-404 confirmation and the cloaking probe follow redirects one hop at a time and drop your custom headers, other than standard browser headers such as User-Agent and Accept, on a hop to a different origin. They also refuse a hop to a link-local or cloud metadata address. Private networks still work for an audit of a site on your own network.

Fixed

  • The accessibility checks that skip hidden controls no longer make every unnamed control on a page unconfirmed when the page inlines Tailwind v4's preflight ([hidden]:where(:not([hidden="until-found"]))). A :where(), :is() or :has() beside a tag, class, id or attribute test now only narrows the rule, so the rule reaches elements that have that part, and a11y/button-name and the other naming checks can fail visible unnamed controls again. A :where() or :is() with nothing beside it can still reach any element. See a11y/button-name.
  • content/placeholder-text no longer flags NaN listed beside Infinity, as in Special values: NaN, Infinity or a list of NaN, Infinity and -Infinity on adjacent lines. That is floating-point vocabulary, not a leaked value. NaN shown as a value (a line that is just NaN, Rating: NaN, Price: $NaN) still flags, unless an Infinity sits right next to it. See content/placeholder-text.
  • squirrel analyze now applies [storage] keep_audits retention, unless keep_audits is off, so a project audited with squirrel crawl, squirrel analyze and squirrel report no longer keeps every audit's results. It retires audits beyond the window, and crawls that killed audits left behind, the way squirrel audit does, and prints the same notice on stderr when it retires something. It skips retention for a blocked or failed crawl, for a crawl in another project than the one the directory's squirrel.toml names, and, when no project is named, for a crawl picked with --id. See Storage.
  • The internal and external link checks no longer request two more shapes of action or credential link. A sign-out with a version number after it (/LogOut2, /SignOut1.aspx) now counts as a sign-out, while /oauth2/docs is still a page, and a credential in a matrix parameter (/file;sig=…, /a.zip;X-Amz-Credential=…) now counts as a credential. See External links.
v0.0.120Oct 20, 2026

Release Notes

A small release. A page and its locale variants sharing their own share image are no longer reported as a site template fallback, and squirrelscan's cloud now refuses the free link checks that CLIs older than v0.0.114 still ask for.

Thanks to @deniskern, whose report is behind the social/asset-divergence fix below.

Pricing

  • A squirrel CLI from before v0.0.114, signed in with squirrel auth login, no longer gets free cloud link checks. That was due to end on 2026-11-12 and has ended now. The cloud refuses those checks and charges nothing, and the CLI checks the links on your machine instead, as it already does for any link the cloud leaves unanswered. Run squirrel self update to send them to the cloud again, at 1 credit per distinct link the cloud verifies. See Credits and pricing.

Fixed

  • social/asset-divergence no longer reports a page's own og:image as a divergent fallback only because its hreflang alternates repeat it. Pages linked as hreflang alternates in both directions (directly or through each other, in any language) now cast one vote per image on the default OG image axis. So a collection page and 15 locale variants sharing an image the rest of the site does not use count as one page's image, which the rule leaves out, instead of a 16-page template fallback. A one-way hreflang link merges nothing, the favicon and theme-color axes still count each page, and a pocket of unrelated pages sharing a stale fallback image is still reported. See social/asset-divergence.
v0.0.119Oct 20, 2026

Release Notes

A release about tidying up after audits that never finished, and about how the accessibility checks read <style> blocks. Crawls that killed audits leave behind are now retired instead of piling up in project.db, squirrel self disk --prune carries on past a project it cannot open, Ctrl-C marks the crawl it interrupts as stopped, and a :root block of design tokens no longer turns a page's accessibility failures into warnings.

Thanks to @deniskern, whose report is behind the abandoned-crawl fix below.

Fixed

  • The accessibility checks that skip hidden controls no longer report every unnamed control on a page as unconfirmed because of a :root block of design tokens. Since 0.0.118, a custom property whose name or value mentions display (such as --font-display: swap, or the "Display" typography preset WordPress writes into its global styles) next to a calc() or var() made the whole page unreadable. So a11y/button-name, a11y/aria-labels, a11y/aria-command-name and the other naming checks, and a11y/aria-hidden-focus, warned where they should fail.
    • squirrel now reads a style block one declaration at a time. A declaration other than display, visibility or all, such as a custom property or transition: all .2s next to a var(), no longer makes a rule unreadable, though an escape, an unclosed string or an animation still can. A :root rule now applies to <html> only.
    • A control with the hidden attribute, in a closed <dialog> or in an unopened popover, that a <style> rule or a plain inline style shows again is now unconfirmed, not a failure, on a page that also loads an external stylesheet, since a rule there such as [hidden] { display: none !important } may keep it hidden. An inline display with !important still counts.
    • Still unconfirmed, possibly across the whole page: controls that a <style> rule squirrel cannot settle could reach, when its declarations can only show an element, such as WordPress's .wp-block-image>a { display: inline-block } (#940), and controls on a page whose <style> inlines a hiding rule that uses :where() or :is(), such as Tailwind v4's [hidden]:where(…) preflight (#951).
    • See a11y/button-name.
  • Crawls left behind by audits that were killed before they finished (kill -9, a watchdog, an out-of-memory kill) are now retired: by [storage] keep_audits after an audit, unless keep_audits is off, and by squirrel self disk --prune. A crawl qualifies once a newer audit has finished and the crawl has fetched nothing for more than 30 minutes (7 days for a stopped or paused crawl, which --resume continues), and is retired at the next such pass. These crawls no longer take a slot of the keep_audits window, and a failed crawl follows the same rule. A crawl newer than the newest finished audit, or one that fetched a page in the last 30 minutes, may be live or waiting for analyze, and is never retired. A crawl that no longer qualifies when the pass re-checks it is skipped on its own instead of stopping the rest. Retiring a crawl drops its results and crawl data, and the crawl stays listed. See Storage.
  • squirrel self disk --prune no longer stops at the first project it cannot open. Each project is handled on its own, and one that fails is reported with its reason. After pruning, the run prints how many projects were pruned, had their rebuild skipped and failed, and exits 1 when a project failed. The rebuild is skipped, with a stated reason, when the measured free space is below what it needs. --dry-run lists abandoned crawls apart from finished audits, with their status and age, and the unfinished crawls it leaves alone, with their last activity. See self.
  • Ctrl-C during squirrel audit marks the crawl the audit was still fetching as stopped, the state --resume continues, so it no longer looks like a killed audit's crawl. Marking it is best effort: if another process holds the database locked for more than a second, the crawl stays running.

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.