Releases
Release history and changelogs
All releases up to v0.1 will be in the stable channel.
Release History
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
zodvalidation library thesquirrelCLI 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 thesquirrelbinary 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
Release Notes
Reports can show what passed, score rings tell the truth about color, and the docs get a glossary.
Added
squirrel auditandsquirrel reporttake--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-widefor a site rule, orpages not recordedfor a page rule whose passes name no page. On the HTML report it is one closed line under the findings. JSON gets apassedRulesarray (ruleId,name,category,scope,pages). A report read with--inputlists 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-onchecks each run against your thresholds, not against an earlier audit, and points atsquirrel report --regression-sincefor comparing two audits.
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
/100never 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 fullor--max-pages Nin 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.unfetchedand 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).
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
squirrelbinary 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/mdx8, 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 htmlnow 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 thesquirrel auditcommand 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; runsquirrel report -f htmlagain (or a fresh audit) to get the new page (html format).
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.
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 neitherdisplaynorvisibilitychanges 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
Linkheader (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 setsnovalidate(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 sharednot-applicablereason 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 carriesdetails.foldKey: not-applicableand no longer the free-textskipReason; itsmessagekeeps 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>-unconfirmedthere. A page under the limits reads as before (#1015).
Added
- a11y, links:
link-textandanchor-textnow 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 unknownlangkeeps the English list, matching is on the whole text, and the conventional account and legal labels are never reported (#840). - content:
word-countskips pages kept out of search (noindexmeta orX-Robots-Tag), likecore/meta-title,core/meta-descriptionandcore/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 --prunenow 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 killedcrawledcrawl is retired by the next audit and by--pruneonce its heartbeat is 5 minutes old, and a killedrunningcrawl, which--resumecontinues, after 24 hours, in both cases without waiting for a later audit;stopped,pausedandfailedcrawls are never retired sooner by a dead lease:stoppedandpausedkeep their 7 days,failedits 30 minutes. A newer finished audit plus the idle time (30 minutes, or 7 days forstoppedandpaused) 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-runshows a held crawl's owner pid and last heartbeat. A--resumeafter a kill takes the lease over at once when the old owner's process is gone.
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,crawlandanalyzewrite 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 andself disk --prunebehave 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: nonehides 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: 990pxwithmax-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,@supportsor 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_msstill overrides it, and values below 1000 are still refused. legal/cookie-consentfinds its consent signals in one pass over the page's elements instead of 28 selector sweeps, andsocial/share-buttonsno longer lowercases a copy of the page. Both give the same answers as before; on a 1 MB script-heavy pagelegal/cookie-consentis 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] domainsor an include pattern admits, is still crawled and reported but is requested without them, and anhttps:start URL that redirects tohttp:keeps itshttps:scope. Name a host in[crawler] header_hosts(or--header-host) to send them there; a named host now gets them overhttps:only (before, any scheme matched), so write an entry with anhttp://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. Awwwhost 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/ttfbwith 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 themailto:links they are.tel-mailtocounts them and checks the decoded address (one that does not decode is never reported as invalid or mismatched),nofollow-internalandhttps-downgradeno 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-textno longer reports conventional account and site-utility labels (Sign in,Sign up,Log out,Register,Terms,Privacy policy,Contact,FAQand similar, in 11 languages) as generic link text.click here,read moreand the other generic phrases are still reported (#839). - links:
no-contextual-inboundno 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-altandempty-headingnow read which elements are out of the accessibility tree the same way asbutton-nameand the other naming rules. A hidden element is skipped and counted, and one a style may hide (ano-jsfallback on a page with an external stylesheet included) is reported as<check>-unconfirmedinstead of a failure. A visible one still fails (#1002).
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-altandlink-textnow read which elements are out of the accessibility tree the same way asbutton-nameandselect-name. An element hidden byhidden,aria-hidden,display: noneor a closed dialog is no longer reported, and one a style may hide (ano-jsfallback on a page with an external stylesheet included) is reported as<check>-unconfirmedinstead 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.
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-nameanda11y/aria-hidden-focus) now report a control they would have decided was shown (an unnamed control, or a focusable one insidearia-hidden) as unconfirmed, not as a failure, when it or an ancestor other than<html>or<body>has ano-jsclass 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 asno-js-fallback, and ano-jsclass on<html>itself keep their verdict. See a11y/form-labels. a11y/select-namenow reads which selects are out of the accessibility tree the way the other naming checks do. An unnamed select that thehiddenattribute,aria-hidden="true", a closed dialog, or adisplay: nonesquirrel can settle (inline or in a page<style>rule) keeps hidden is no longer reported. One a style may hide, ano-jsfallback on a page with unread styles included, is reported asselect-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.
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, soa11y/button-name,a11y/form-labelsand 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.
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
displayvalues (-webkit-box,-webkit-flex,-ms-flexbox,-moz-boxand 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, soa11y/button-nameand the other naming checks can fail those controls again when nothing else hides them. A-ms-or-moz-value stays unconfirmed unless a later standarddisplayoverrides it (!importantrespected), because browsers differ on it. Adisplay: nonethat wins after the prefixed values still hides the element where squirrel can settle the rule, and adisplayset fromvar()stays unconfirmed. See a11y/button-name.
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 auditwithout a terminal now printsWalking pages: N/Mto 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. SetSQUIRREL_WALK_HEARTBEAT_MSto 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>-errorwithskipReason: "rule-timed-out"on every page it timed out on, the first included, withelapsedMs, 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 checkedline for a timed-out rule above info severity, with the rule's name and how many pages it did not check, such asNot 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, sowarnings>0andseverity>=warningtrip 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 onerrors>0and failed only because a rule timed out now passes. Add a warning gate to keep catching a timed-out security rule, or raisetime_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.
- Every report format, the CLI's own output included, prints a
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>aand.wp-lightbox-container buttonrules kept a page's links and buttons unconfirmed, soa11y/button-nameand 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 thehiddenattribute, a closed dialog, a popover, adisplay: noneor an inheritedvisibility: hidden.display: contents,inheritandunsetdo not count as only showing. See a11y/button-name. - The
<rule>-timeoutsinfo check, which counts the pages a timed-out rule did not check (pagesNotChecked, andmaxElapsedMswhen 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 inproject.dbor the report. See Rule time budget.
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 asassets.example.combehind 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 byreliability/broken-resources, naming its host, instead of being reported as broken, andmobile/web-app-manifestreports 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-AgentandAccept, 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, anda11y/button-nameand 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-textno longer flagsNaNlisted besideInfinity, as inSpecial values: NaN, Infinityor a list ofNaN,Infinityand-Infinityon adjacent lines. That is floating-point vocabulary, not a leaked value.NaNshown as a value (a line that is justNaN,Rating: NaN,Price: $NaN) still flags, unless anInfinitysits right next to it. See content/placeholder-text.squirrel analyzenow applies[storage] keep_auditsretention, unlesskeep_auditsis off, so a project audited withsquirrel crawl,squirrel analyzeandsquirrel reportno longer keeps every audit's results. It retires audits beyond the window, and crawls that killed audits left behind, the waysquirrel auditdoes, 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'ssquirrel.tomlnames, 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/docsis 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.
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
squirrelCLI from before v0.0.114, signed in withsquirrel 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. Runsquirrel self updateto send them to the cloud again, at 1 credit per distinct link the cloud verifies. See Credits and pricing.
Fixed
social/asset-divergenceno longer reports a page's ownog:imageas 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.
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
:rootblock of design tokens. Since 0.0.118, a custom property whose name or value mentionsdisplay(such as--font-display: swap, or the "Display" typography preset WordPress writes into its global styles) next to acalc()orvar()made the whole page unreadable. Soa11y/button-name,a11y/aria-labels,a11y/aria-command-nameand the other naming checks, anda11y/aria-hidden-focus, warned where they should fail.- squirrel now reads a style block one declaration at a time. A declaration other than
display,visibilityorall, such as a custom property ortransition: all .2snext to avar(), no longer makes a rule unreadable, though an escape, an unclosed string or an animation still can. A:rootrule now applies to<html>only. - A control with the
hiddenattribute, in a closed<dialog>or in an unopenedpopover, 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 inlinedisplaywith!importantstill 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.
- squirrel now reads a style block one declaration at a time. A declaration other than
- 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_auditsafter an audit, unlesskeep_auditsis off, and bysquirrel 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 astoppedorpausedcrawl, which--resumecontinues), and is retired at the next such pass. These crawls no longer take a slot of thekeep_auditswindow, and afailedcrawl 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 foranalyze, 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 --pruneno 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-runlists 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 auditmarks the crawl the audit was still fetching asstopped, the state--resumecontinues, 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 staysrunning.