Skip to main content

v0.0.118

Released Oct 19, 2026


Release Notes

A release about reading pages more the way a browser does. The accessibility checks that skip hidden controls now read the page's own <style> blocks, so a control a <style> rule hides, such as a closed modal, is skipped as a browser skips it, and accessibility scores move with it. A local audit detects technologies from more of its response headers, a charset sent only in the Content-Type header counts, a redirected page's canonical is judged at its target, four more schema rules follow @id references, a CAPTCHA widget rendered client-side is recognised, and the link checks never request a link whose fragment carries a sign-in token.

Thanks to @WilliamK112, whose pull requests are behind the content/hidden-text and npm install fixes below, and who also hardened the plan lookup, tidied the crawler's page events, kept the squirrel analyze tests away from a developer's real project databases and made CI check that the compiled CLI carries the rules version it was built from. Thanks also to @deniskern, whose reports are behind the hidden-control, security/form-captcha and security/csp-blocks-own-resources fixes.

Pricing

  • squirrel keys create now says how an audit signed in with the new key is billed: no audit or page charge, links checked on the runner, cloud renders 2 credits each (not printed with --json). That billing is not new; the CI guide now spells it out.
  • A dead-link check called directly through the API, outside an audit (POST /v1/services/dead-links with no run), costs 1 credit for each distinct link it verifies, and nothing for a link it cannot verify or does not answer. Links that differ only in their query string or fragment count as one. It used to be free for any caller until 2026-11-12. That free period now applies only to a squirrel CLI from before v0.0.114 signed in with squirrel auth login; from 2026-11-12 those calls are refused and the CLI checks its links itself. See Credits and pricing.
  • A completed cloud audit is refunded for the external links it was charged for that its report lists as not checked because the link check ran out of time.

Changed

  • A link whose fragment carries a credential, written as key=value pairs such as an OAuth sign-in redirect's #access_token=…&token_type=bearer, is now treated like any other token link. The external and internal link checks never request it, and a signed-in audit never sends it to the cloud's link check. The report lists it as not requested, with the fragment left off. On your own site, the crawl can still load the page such a link points to, since a fragment is never sent with a request. A plain anchor (#pricing), a hash route (#/settings) and ordinary state in the fragment (#q=shoes) are checked as before. See What the internal link check never requests.
  • The squirrelscan agent skill that ships with the CLI, which squirrel skills, squirrel setup and squirrel mcp hand out, is now version 1.15. Its access-gateway advice now says that cloud browser renders send a website's custom request headers, so a gated site can keep rendering on. A signed-in CLI audit's cloud renders still never carry -H headers.
  • A published report now carries linkTargetSets: for links/broken-links and links/broken-external-links, hashes of the targets each one reported broken and of those it could not verify or did not request, taken before the report is sampled. Each set holds up to 2,000 hashes and says whether it is complete. A later comparison can then tell a link that broke since the previous audit from one that was already broken, even on a site with more broken links than the report lists. No format renders them, and scores do not change. See Reports.

Fixed

  • The accessibility checks that skip hidden controls (a11y/aria-hidden-focus, a11y/button-name, a11y/form-labels, a11y/aria-labels, a11y/aria-command-name, a11y/aria-dialog-name and a11y/aria-input-field-name) now read the page's <style> blocks, not only inline styles. A control or dialog that a <style> rule hides with display: none or visibility: hidden, such as a closed modal, is skipped as a browser skips it, instead of being reported.
    • squirrel settles a rule itself only when it sits outside any at-rule, in a <style> with no media (other than all or screen) and no title, its declarations are plainly written, and each of its selectors is one compound of a tag, an id, classes and plain attribute tests (.modal, div#menu.closed, [data-state=closed]). It applies those as a browser does: !important first, then an inline style over a rule, then specificity, then source order.
    • Any other <style> rule that sets display or visibility, whatever the value, can make the unnamed controls it could apply to, and those inside them, unconfirmed: a warning rather than a failure. That covers a selector with a combinator (.nav .item) or a pseudo-class (:hover), a rule inside @media, @supports or @layer, and a rule whose declarations squirrel cannot read. A rule using :is(), :where() or :has() can reach any element, so it can make any unnamed control on the page unconfirmed. Hiding that comes from a <style> block on a page that also loads an external stylesheet, which may undo it, is unconfirmed too.
    • On a page whose styles squirrel reads in full (for example, no external stylesheet or @import), a control inside a container whose inline style sets visibility: hidden now counts as hidden instead of unconfirmed.
    • Some controls that were skipped or unconfirmed are now checked, because a <style> rule shows them again, as a browser does: a display value on an element with the hidden attribute, a closed <dialog> or a popover, an !important rule that beats an inline display: none, or visibility: visible on a control inside a hidden container. A control hidden only by its inline style is unconfirmed where an undecided !important rule, or an animation that sets visibility, may show it.
    • Accessibility scores change in both directions. Unconfirmed rows now read may be hidden by a style (unconfirmed) instead of may be hidden by an inline style (unconfirmed), and their items say a style on it or an ancestor sets display or visibility in a way squirrel cannot settle.
    • Known limits, tracked in #938: a :root block of custom properties whose names or values mention display, such as WordPress global styles with a "Display" typography preset, can make every unnamed control on the page unconfirmed. A <style> rule that shows a control is applied even when an external stylesheet may hide it again.
    • See a11y/aria-hidden-focus.
  • An audit no longer keeps an earlier audit's accessibility failure open, as a carried finding, on a page where the same check now reports the control as unconfirmed. This affected the seven hidden-control checks above once they began reading <style> blocks: the page showed both the stale failure and the new unconfirmed warning. An unconfirmed row now counts as the check having run on that page, so the earlier failure is closed unless the page still fails that check, and an earlier unconfirmed warning is closed once the check passes there. A page a rule did not check at all keeps its earlier findings. The fix text of those seven rules now describes how <style> blocks are read.
  • Technology detection in a local audit sees more of the response headers a signature reads. The crawler kept only a fixed set of headers, so a header-only signature such as X-CDN: Imperva, X-Sucuri-ID or X-Varnish never matched. The crawler now stores those headers with each page, each kept value cut to 512 characters, and the CLI passes them to the local scan. A signed-in audit also sends them to the cloud detection, together with X-Powered-By, X-Generator, X-AspNet-Version and the names of the cookies a page sets (up to 50). A header a signature only checks for presence, such as a request id or a bot protection token, is kept as a marker, not its value, and cookie values are never sent. A page an earlier version stored, and the crawl cache reuses, is read with only the headers it kept until it is fetched again. See Technology Detection.
  • core/charset now sees the charset of the Content-Type response header in a real audit. The crawler kept only the media type, so a page declared UTF-8 by its header alone failed with No charset declaration found, and a header that disagreed with the page's meta about UTF-8 was never reported. It now warns, for example Conflicting charset declarations: header iso-8859-1, page utf-8. A page an earlier version stored and the crawl cache reuses is read as before until it is fetched again. See core/charset.
  • core/canonical and crawl/pagination judge a URL that answers with an HTTP redirect by the URL it ended on, as i18n/hreflang already does. /en/old-slug redirecting to /en/new-slug, whose canonical is https://example.com/en/new-slug, no longer reads "Canonical points to different URL", and a paginated URL that redirects is tested, and its relative canonical resolved, at its target. See core/canonical.
  • schema/breadcrumb, schema/local-business, schema/website-search and schema/rating-scope follow JSON-LD node references ({"@id": …}) to the node they name on the same page, as schema/faq, schema/product and schema/review already do. A breadcrumb whose items are references no longer reports items missing their position and name, a business whose address is a reference no longer reads as an incomplete address, a referenced SearchAction is found, and a referenced rating is read for the entity that carries it. A reference to a node the page does not have is judged as written. See schema/breadcrumb.
  • security/form-captcha recognises a CAPTCHA widget rendered client-side. @nuxtjs/turnstile and React reCAPTCHA components mount the widget after hydration, so the static HTML holds only the site key, for example Nuxt's turnstile:{siteKey:"…"} runtime config. A Turnstile, reCAPTCHA, hCaptcha or Friendly Captcha site key, under the provider's name and in that provider's key format, in an inline or JSON data script now protects the page's single public form, like a provider script does, unless another form on the page has a CAPTCHA of its own. A script that only mentions a provider, or switches it off, still does not. See security/form-captcha.
  • security/csp-blocks-own-resources no longer warns about, or fails on, a host that only the page's same-site scripts or preloaded chunks name as a script to load. SDKs ship fallback URLs that load only behind a feature flag, so the page may never request them. That includes a known vendor's host (analytics, ads, payments, CAPTCHA, forms or booking) that only a chunk names, which used to fail the check. Such hosts are now listed in an informational csp-chunk-only-hosts note. A host the page's HTML loads, or that a vendor it uses needs, is reported as before. See security/csp-blocks-own-resources.
  • content/hidden-text reads CSS comments the way a browser does. A comment before a property or its colon (.promo { /* hide */ display: none }) used to stay in the property name, so the declaration was never recognised, in <style> rules and inline style attributes alike. A <style> rule with a comment before or inside its selector, such as a /* Section */ comment between rules, was not applied either, here or in mobile/input-zoom. Text and links hidden either way now count, as other hidden text does. See content/hidden-text.
  • WAF detection stops treating text as quoted at the end of the block an unclosed <code>, <kbd> or <samp> sits in. Since 0.0.117, an unclosed <code> could hide a block page's wording, such as an Imperva incident line, for the rest of the page, so the block page went unrecognised. A block nested inside a code block, as Expressive Code and Starlight write each line, still reads as quoted.
  • The console, llm and XML reports say how many items a check left out, as the HTML, markdown and text reports do. The console's ... +N more after the first 5 items now counts the items the audit left out too, and a check that lists none reads 1,500 items not listed. The llm format no longer drops the count for a rule whose checks list no items (Items (0/1500): none listed), and the XML gives it as a not-listed attribute on the check's <items> element. Scores do not change. See Reports.
  • A signed-in audit checks on your machine a link the cloud link checker refused to request because its name resolves to a private address, when the checker says so, instead of listing it as status unverifiable. An audit of a site on a private network gets the link's real status, and a public site's audit still never requests it and lists it as refused. The cloud's refusal is never itself reported as a broken link. See External links.
  • The npm package's postinstall download follows at most three redirects, refuses a download or redirect URL that is not HTTPS, and resolves a relative Location against the URL that sent it.

Docs

  • The CI guide says what an API-key audit costs, and Credits and pricing gives the price of a dead-link check outside an audit.
  • The README and the docs home count 8 Mobile rules.

Update to this version

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

$

Downloads

macOS

Detected
Intel
squirrel-0.0.118-darwin-x64 · 76.3 MB
Download
SHA-256
Apple Silicon (M-series)
squirrel-0.0.118-darwin-arm64 · 69.8 MB
Download
SHA-256

Linux

ARM64 (musl/Alpine)
squirrel-0.0.118-linux-arm64-musl · 80.8 MB
Download
SHA-256
ARM64
squirrel-0.0.118-linux-arm64 · 87.4 MB
Download
SHA-256
x64
squirrel-0.0.118-linux-x64 · 87.5 MB
Download
SHA-256
x64 (musl/Alpine)
squirrel-0.0.118-linux-x64-musl · 81.5 MB
Download
SHA-256

Windows

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

Audit your site in one command

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

Install
$

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