Skip to main content

v0.0.117

Released Oct 17, 2026


Release Notes

A release about reading pages the way browsers and search engines do. The new mobile/input-zoom rule finds the form fields that make iOS Safari zoom in, so squirrelscan now ships 309 rules. A strict CSP built on a nonce or 'strict-dynamic' is no longer called weak, the rules that ask whether robots.txt blocks a page read it the way Google does, a sitemap with a mismatched tag or a raw & gets caught, and the FAQ, product and review checks follow JSON-LD @id references to the nodes they name. A site missing compression or caching headers is scored for each once instead of twice, squirrel analyze stops stacking a second copy of every result, and a signed-in audit no longer pays for a link the cloud could not verify.

Thanks to @deniskern, whose reports are behind the schema/faq, crawl/pagination and i18n/hreflang fixes below.

Added

  • New rule mobile/input-zoom: on an iPhone, focusing a text field, select or textarea under 16px makes Safari zoom the page in. The rule reads field sizes from inline styles and <style> blocks and lists up to 20 small fields, each with its size and where it came from, such as 14px (inline style). A field nothing on the page sizes, on a page with no external stylesheet, gets iOS's own 11px (iOS default). A field it cannot read plainly (its size may come from an external stylesheet, a media query, conflicting rules or a relative unit) is counted as not read rather than guessed at, so on many sites built with a CSS framework most fields are not read yet. A page that sets maximum-scale=1 or user-scalable=no in its viewport tag to stop the zoom still gets the warning, with a note that the workaround blocks zoom for everyone. See mobile/input-zoom.
  • perf/total-byte-weight also reports two site figures, for information only: the site total (every scored page's HTML plus each measured external resource once, however many pages share it) and the average page weight. They are at the end of the message (Not scored: site total 80KB across 19 pages, shared files counted once; average page 4KB) and in the details as siteTotalKb and pageAverageKb. They never change the pass, warn or fail result, which is still the heaviest page's, and an audit whose page weights are only estimated reports neither. See perf/total-byte-weight.
  • crawl/sitemap-valid warns about the <lastmod> values of sitemap URLs that are outside the W3C Datetime format the sitemap protocol requires, such as 2024/09/10, 2024-9 or Tue, 10 Sep 2024 12:00:00 GMT (sitemap-lastmod-format), with a sample of URLs and values. A datetime with no zone (2024-09-10T12:00:00) is named as missing its zone (sitemap-lastmod-zone). A search engine may ignore a lastmod it cannot read. See crawl/sitemap-valid.
  • [rules] time_budget_ms in squirrel.toml sets the rule time budget for squirrel audit: how long one rule may spend on one page (its synchronous work) before it is abandoned for that page and the audit moves on (default 1000 ms). It is a backstop against a page built to make a pattern match slowly. Raise it if rules time out on ordinary pages on a slow or busy machine, such as a shared CI runner. 0 turns the budget off. A value below 1000 is refused for now, with time_budget_ms must be 0 (off) or at least 1000. Cloud audits keep the default. See Rule time budget.

Pricing

  • A signed-in audit, cloud or CLI, is no longer charged for an external link squirrelscan's cloud link checker could not verify: one behind a bot wall, a throttled one, one the run's limit on deeper checks for its site left unverified, or a 404 waiting for its confirming check. The report lists those as unverified, as before. A link the checker did not answer in time is not charged either. Only links that work, redirect or are broken cost a credit (at most 1 per page): each batch of links is charged before it is checked, then settled down to the links the checker verified. The settling is done by squirrelscan's cloud, so it applies to every CLI version that pays for cloud link checks. A link to another of your own domains is external and is charged the same way. See Credits and pricing.
  • The CLI's pricing text says the same: 1 credit per external link the cloud verifies, not per link it checks. That covers squirrel credits, the low-balance and credit wall lines, the one-time link pricing notice, the audit estimate and the billing note of the MCP audit_website tool.
  • squirrel credits no longer lists issue_enrich (3 per issue). AI issue enrichment is switched off in squirrelscan's cloud, so nothing is sold or charged under it today. The keyword and content gap add-ons are listed as before.

Changed

  • links/broken-external-links says why a link could not be verified. A link that answered 404 once and waits for its confirming check reads (unverified: awaiting confirmation) on the unverifiable row: it is likely broken, not blocked. Other items keep (unverified) or (unverified: {vendor}). Each item also carries meta.unverifiedReason when the checker knew why: blocked for a wall or challenge, unreachable for a timeout or failed connection, or awaiting-confirmation. An unverifiable link squirrel checks from your machine gets blocked, or unreachable when its check timed out. The cloud link checker sends all three. The reason is stored with the audit, so squirrel report and squirrel analyze keep it. See broken external links.
  • squirrel audit and squirrel report say how many items a check left out in their HTML, markdown and text output, not only how many they list. A check that lists 20 of its 26 items now reads 20 items, 6 more not listed in HTML and 20 item(s), 6 more not listed in markdown, and the text format prints ... and 6 more items not listed after the list. A check that lists no items but left some out says so, such as 1,500 items not listed, where it used to say nothing. The count comes from the check's details.additional, which the JSON and LLM formats already reported. A check with nothing left out reads as before.
  • A report published from the CLI carries the exact number of pages a rule fails or warns on when its issues reach different pages, in a new top-level rulePageCounts field of the JSON. Each issue lists a sample of its pages beside its own true count, but the pages two issues of one rule leave out cannot be told apart, so a rule with one issue on 46 pages and another on 4 different pages could only be counted as at least 46. The count is taken before the report is sampled, over this run's checks (findings carried over from an earlier audit are left out, as in the check tallies), and it is listed only for the rules that need it. See Output formats via URL.

Fixed

  • squirrel analyze now replaces a crawl's stored rule results. It used to add a second copy of every check beside the ones the audit, or an earlier analyze, had stored, and squirrel report then showed the earlier run's items, so a rule change looked like it did nothing.
  • crawl/sitemap-valid no longer passes a sitemap with a mismatched closing tag (</lox> for </loc>), an element left open or a raw & in a <loc> or other element text. It now fails its sitemap-syntax check with an error that names the problem and where it is, such as XML is not well formed: char '&' is not expected. (line 6, column 49). The URLs squirrel can still read are kept. Whitespace before the XML declaration is tolerated, and a sitemap that declares its own entities in a DOCTYPE is not checked. See crawl/sitemap-valid.
  • crawl/robots-meta-conflict reports a page that robots.txt blocks and that carries noindex only in an X-Robots-Tag header, and lists the pages on its finding. It decides "blocked by robots.txt" the way Google does: the longest matching Allow or Disallow wins, an Allow wins a tie, and * and $ in a rule are honoured. Before, Disallow: /dir/ plus Allow: /dir/ok counted /dir/ok as blocked, and Disallow: /*.php matched nothing. The same decision serves crawl/indexability-conflicts, crawl/all-noindex-pages, crawl/schema-noindex-conflict and the GraphQL probe's robots.txt check. The crawler's own robots.txt handling, used when respect_robots is on, is unchanged. See crawl/robots-meta-conflict.
  • security/csp no longer reports a strict CSP as weak. In a script-src (or default-src) that also lists a nonce or a hash, or 'strict-dynamic', 'unsafe-inline' no longer warns csp-unsafe-scripts, and next to 'strict-dynamic' a wildcard host such as * no longer warns csp-wildcard: browsers ignore those sources (CSP Level 3). So a strict policy of the shape Google recommends, script-src 'nonce-…' 'strict-dynamic' https: 'unsafe-inline', gets no warning. 'unsafe-eval' still warns. See security/csp.
  • security/sri no longer reports the scripts and stylesheets Shopify injects into every store from cdn.shopify.com/shopifycloud/ and cdn.shopify.com/storefront/, which Shopify updates in place so no store can pin a hash. Theme and app assets elsewhere on cdn.shopify.com are still checked. A new exempt_hosts option exempts other hosts whose files change at the same URL, by exact hostname: [rule_options."security/sri"] exempt_hosts = ["static.klaviyo.com"]. See security/sri.
  • A site with no compression or no caching headers is no longer scored twice for the same server setting. perf/compression and perf/cache-headers score it page by page, and the bad-caching-compression and bad-caching-freshness checks of perf/bad-caching now report the site's share at info status, which does not move the score. A site missing both now scores a little higher than before: a clean five-page test site served with neither went up 2 points in our smoke test. bad-caching-validators is unchanged. See perf/bad-caching.
  • A page that quotes a WAF's signature is no longer attributed to that WAF or read as its challenge page. A tutorial printing <script src="https://js.datadome.co/tags.js"> in a code block, a support page showing Incapsula incident ID: 1234-5678 in one, or a troubleshooting page that mentions /cdn-cgi/challenge-platform/ could name the provider in the report's technologies, or be set aside as a challenge page and left out of page-level scoring. Script and markup signatures now count only inside a tag (not a link) or an inline script, and block-page wording only outside <code>, <kbd> and <samp> elements, comments and scripts. Real block and challenge pages are recognised as before. See Bot protection or WAF challenge.
  • content/placeholder-text now reads the page's <title> too. A title where undefined, null or NaN fills a whole segment between spaced separators (undefined | Acme) warns, and one holding [object Object] fails, in a new placeholder-title check. A page about the word, whose first <h1> or one of its <code> elements is exactly that word, such as MDN's undefined - JavaScript | MDN, is not reported. See content/placeholder-text.
  • content/mojibake reads the same rendered prose as content/placeholder-text. A sequence that appears only inside a <template> or a code-editor or syntax-highlighter container (CodeMirror, Monaco, highlight.js, Shiki, Prism) is no longer reported, and adjacent blocks such as table cells no longer fuse into a false sequence. See content/mojibake.
  • perf/dom-size counts elements and depth from <body>, as its widest-element check already did and as Lighthouse's DOM size audit does. <head> metadata such as hreflang alternates and preload hints no longer adds to a page's count or depth, so a small page with a large head reports the body's numbers. The total is the elements inside <body>, and <body> is depth 1, so a page's depth usually reads one level less than before. Thresholds are unchanged. See perf/dom-size.
  • i18n/hreflang no longer reports "No self-referencing hreflang found" on a URL that answers with an HTTP redirect to a page that references itself. The crawl reads the redirect target's tags, so the self reference is now compared with the URL the redirect ended on. A redirected page's other hreflang findings, such as a missing x-default, are about the target's tags and stay. See i18n/hreflang.
  • crawl/pagination no longer warns that /blog?page=1 canonicalizes to /blog. Page 1 of a list is the same content as the unpaginated URL, so that canonical is correct. Page 2 and later still warn when their canonical points at a URL with no page marker. See crawl/pagination.
  • schema/faq no longer reports every question of a Yoast-style FAQ as missing its name and answer. When mainEntity lists node references ({"@id": "…#q1"}) to Question nodes elsewhere in the page's JSON-LD, the rule now reads the nodes they name, and a null entry counts as an invalid question instead of breaking the check. schema/product does the same for its first offer, and schema/review for review entries and aggregateRating, judging a review both referenced and present as its own node once. A reference to a node the page does not have is still reported. See schema/faq.
  • The sitemap lastmod checks (crawl/sitemap-lastmod-drift, crawl/sitemap-lastmod-churn) and the cloaking probe read dates outside the W3C format more carefully. A time range such as Sep 10 2024 12:00-14:00 used to be read as 12:00 at offset -14:00, and a value with a time range like that is now not read. An overnight range such as 22:00-02:00 cannot be told from a time and its offset, and still reads as one. A local time that daylight saving skips, such as 2024/03/10 02:30, now reads as written on every machine. It used to come out an hour off on a machine in a zone that skips it.
  • The XML report squirrel audit writes now states the scan scope, as the other formats do. Inside <site>, <scan-scope origin="cli" crawled="42" max-pages="100" capped="false"/> says where the audit ran, how many pages it crawled, the page limit in force and whether that limit stopped the crawl. requested-max-pages is added when the requested limit was lowered. A report from before scan scope existed renders as before. See Reports.
  • An entity map published from the CLI that has more edges than the publish cap (1,500) now keeps the edges between its best-ranked entities. It used to keep the first 1,500 in alphabetical order, which left late-alphabet entities unconnected. A dangling reference ranks with the entity that makes it, so a problem entity's dangling edges are kept with it.

Docs

  • The README and the docs home page carry one feature list, in the same order, each item linked to its page. The rules configuration page lists all 22 rule categories, the content rules page drops the retired content quality score, and the dashboard audit settings page no longer says only owners and admins can change them.
  • The cloud firewall, crawler and crawl settings pages say that cloud browser renders send a website's custom request headers, pinned to the rendered page's own origin, so a gated site no longer needs rendering turned off. A signed-in CLI audit's cloud renders still never carry -H headers.
  • The XML examples in the docs match what the renderer writes.
  • The docs check now fails on dead internal links, dead #anchors, missing anchors that other pages link to, and rule Options rows with an unknown type or undefined default, and the dead anchors it found are fixed.

Update to this version

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

$

Downloads

macOS

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

Linux

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

Windows

x64 (Intel/AMD)
squirrel-0.0.117-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.