Skip to main content

v0.0.106

Released Oct 8, 2026


Release Notes

squirrel mcp speaks the new MCP 2026-07-28 protocol, squirrel channel gives your Claude Code session a heads-up the moment a cloud audit lands, every rule now runs against the clock to keep booby-trapped pages from stalling an audit, and two new content rules catch the info@example.com and placehold.co your theme shipped with. Plus a big pile of false positives, swept out of the nest.

Added

  • squirrel mcp supports MCP protocol revision 2026-07-28 and the 2025-era protocol from one process. It moved to version 2 of the MCP SDK and picks the protocol from the client's first message: a 2026-07-28 client connects with no initialize handshake and reads the server's capabilities from server/discover, and an older client keeps the initialize handshake. There is nothing to configure. Tool names, descriptions and input schemas are the same in both, and the schemas are now published as JSON Schema 2020-12 rather than draft-07. The MCP docs have the details.
  • squirrel channel pushes cloud audit events into a running Claude Code session. Add it to your MCP config as squirrel channel, and when a cloud audit finishes, fails or finds new issues (audit_complete, audit_failed, issues_detected), your agent gets a one-line event with the ids it needs to pull the report and start on the fixes, without being asked. It polls your organization's notification feed every 30 seconds (--interval, from 5 to 86400), --categories picks the events, and it backs off on 429 and 5xx responses. Event text is built from validated ids, hostnames, counts and fixed strings, never from notification titles or text on your site, so a crawled page can't talk to your agent through it. It needs a login session (squirrel auth login), because API keys can't read the feed; without one it sends a single login_required event and keeps running. It remembers where it stopped, per organization, and catches up once on the next start (until the cloud feed supports cursors, that catch-up reads back only the latest 50 notifications). Channels are a research preview in Claude Code, so for now you start Claude Code with the development flag. The channel guide has the setup.
  • Every rule's synchronous work runs under a time budget: one second per page for a page rule, and one second per page of the site for a site rule, capped at 60 seconds. A rule that waits on the network is timed up to its first wait, and its requests keep their own timeouts. A rule that runs past its budget is stopped and the audit carries on with the next rule. The report records it once per audit, as a failed <rule>-error check (Rule error: exceeded its 1000 ms time budget and was abandoned, naming the larger budget for a site rule) against the first page it happened on (or the whole site, for a site rule), and a page rule also gets an info <rule>-timeouts check with the number of pages it did not check. Ordinary pages come nowhere near it: measured over 500 pages, the slowest page rule's 99th percentile was about 6 ms. A page where a rule timed out is never stored in the rule cache, so the next audit runs it again. The rules reference has the details.
  • content/placeholder-contact flags contact details a template shipped with and nobody replaced: email addresses at example.com, test.com or yourdomain.com, the fictional 555-01xx and UK 07700 900xxx phone ranges, 123 Main St, and handles such as @yourhandle. It reads the visible text and every link's href, so a mailto:info@example.com behind an "Email us" link counts. It fails on a contact or checkout page, skips other pages under documentation paths such as /docs and /api, warns everywhere else, and never judges a code sample. See the rule page.
  • content/placeholder-media flags images that were never finished: a placeholder service source (via.placeholder.com, placehold.co, dummyimage.com and friends, or placeholder as a word in the image's filename), a blank 1x1 inline image used as content, and camera-default alt text such as DSC_0001 or IMG_1234. A placeholder source or a blank stand-in fails and a camera-default alt warns. A lazy-loaded image is judged on its real data-src, not its stub. See the rule page. That makes 297 rules, 23 of them in Content.
  • The JSON report lists checks a rule skipped because it hit its scan limit, in a new top-level skippedChecks list with the rule, the check, the reason and the pages (with the full count when a long list is clipped), and summary.skipped adds up those skipped page checks. Before, the JSON dropped them, so a page that content/hidden-text or content/dev-leakage could not finish scanning read as clean. A report with no such skips has the same keys as before. The output docs now describe the JSON shape the CLI really writes.

Changed

  • perf/total-byte-weight scores each page on its own weight, as its 1600 KB and 5000 KB thresholds intend, instead of adding up the whole crawl. A page's weight is its HTML, its inline CSS and JS, and the stylesheets, scripts, images and fonts it loads, and a file several pages share counts toward each of them. The HTML and fonts (from a preload link or an inline @font-face) are counted for the first time. The message names the heaviest page and how many pages are over each threshold, and it says how many resources it could not size instead of quietly counting them as zero bytes. A site with one heavy page can now warn or fail where it didn't, and a large site of light pages no longer does. Same-site fonts are sized like images, normally with one HEAD request per distinct font, inside the existing resource check limit. See the rule page.
  • The rule cache is keyed on the rule code as well as the CLI version, so running from a source checkout no longer replays findings from before a rule change. The cache format changed, so the first audit of each site after the upgrade runs every rule fresh, once.
  • crawler.include patterns narrow the crawl within its scope instead of replacing it. A path-only pattern such as /blog/* no longer admits /blog/ URLs on other hosts (they could arrive through a sitemap), while an absolute-URL pattern still reaches the host it names. An explicit [project] domains list is a hard allowlist: an absolute include for a host it does not list is refused, with one warning naming both. A config that relied on a path-only include to reach another host needs an absolute pattern, or that host in [project] domains. The crawler docs have the details.
  • Published reports from a large audit count every page the crawl fetched as audited. Before, a page left out of the published sample, or one no page check looked at, was missing from the audited count and from the page charge. On the next publish of such an audit, pages that were left out of the sample and passed every check count toward the score, so its score can rise once, toward what a complete cloud audit gives it.
  • A rate-limited crawl says how much it never reached. The partial status reads like 437 pages rate limited by host; 1355 more discovered but not fetched, and the text, markdown, llm and JSON reports carry the count of URLs it found but never fetched.
  • Reports can say a cloud crawl stopped because it ran out of time rather than at its page limit: the scan scope line adds stopped early on time, the partial-scan note says the site may have more pages than the score covers (without suggesting a higher page limit, which wouldn't help), and the JSON report carries stopReason: "time" next to maxPages, the llm output stop-reason="time".
  • The render limit warning says how many pages your plan renders at once, per audit: 1 on Free, 5 on Pro and 10 on Team.
  • The credit wall links to the plan comparison on the pricing page next to the upgrade link, and an account already on a paid plan is pointed at a top-up instead of being pitched the plan it already pays for.
  • Installer error reports include the version being installed and, on Linux, whether the system uses glibc or musl, so a failed install can be matched to its release.
  • The local project store (~/.squirrel/projects/<domain>/project.db) upgrades itself to schema version 33 the first time this version opens it, to store each page's hreflang alternates. It only adds a column, so v0.0.105 can still open the upgraded store.

Fixed

Thanks to @deniskern, whose reports are behind the hreflang, noindex, dom-size, autocomplete, empty heading, meta-in-body, video caption and LCP fixes below, and the new skippedChecks list.

  • core/title-unique, content/duplicate-title and content/duplicate-description no longer report region variants of one page, such as /en-gb/shirt and /en-us/shirt, as duplicates when each lists the other as an hreflang alternate in the same language and neither canonicalises elsewhere. A de and en pair with one title is still reported. A page reused from the crawl cache picks this up once it is fetched again, or with --refresh.
  • crawl/indexability-conflicts no longer reports robots.txt Allow plus noindex as a conflict, since that is the supported way to deindex a page. Its remaining info check, a page robots.txt blocks without a noindex, now lists the pages.
  • crawl/sitemap-coverage no longer lists a noindex page as missing from the sitemap, and content/title-pattern-outlier leaves noindex pages out of its template vote, when the site itself is indexable.
  • The dom-max-children check of perf/dom-size counts <body> only, so a <head> full of hreflang alternates or preload hints no longer trips its 60-children limit.
  • a11y/autocomplete-tokens no longer asks for an autocomplete token on a country select that is the only personal-data field in its form, such as a locale or market switcher. An explicit token is still checked.
  • a11y/empty-heading skips headings hidden in the markup (hidden, inline display:none or visibility:hidden, or an aria-hidden or hidden ancestor), and each item shows the heading's tag and its position on the page, so two empty <h2>s can be told apart.
  • content/meta-in-body skips schema.org microdata <meta itemprop> tags, which HTML allows in <body>.
  • a11y/video-captions and video/video-accessible skip decorative video, one that is aria-hidden or muted with no controls and autoplays or loops, and both rules now agree on what counts as decorative.
  • perf/lcp-hints uses the same LCP candidate finder as perf/lcp-fetchpriority, so header and nav images, small logos, icons and tracking pixels no longer count as likely LCP images, and a preload matches its image across relative and absolute URLs, query strings and srcset.
  • content/date-agreement reads dates in French, German, Spanish, Italian, Portuguese and Dutch, year-month-day dates with Japanese, Chinese and Korean markers, and numeric dates, reading an ambiguous 08/01/2026 both ways. It stays quiet about a date it can't read instead of warning that the page shows none, and a year in a slug like /reviews/2024-honda-civic-review no longer counts as a publication date.
  • security/new-tab no longer warns on target="_blank" without noopener, because browsers imply it, and noreferrer implies it too. It warns only on an explicit rel="opener", reads rel as a list of tokens rather than a substring, and a missing noreferrer is an info-level privacy note.
  • security/mixed-content reports only http:// URLs in markup that loads a resource, so canonical and alternate <link>s, which are never fetched, no longer count. Loopback URLs such as localhost and 127.0.0.1 are not mixed content and get their own local-development-url warning, and each finding names its tag and whether a browser would likely block it or upgrade it, and says it comes from the markup rather than an observed load.
  • links/no-contextual-inbound skips when the crawl stopped at its page limit, because a page it never reached could hold the link. legal/subprocessor-disclosure warns only when the crawl shows a commercial offer (a pricing, plans, enterprise or trust page, or a sales or demo link), so a pre-launch waitlist page is skipped. content/keyword-stuffing no longer counts the site's own brand name, taken from its domain.
  • An audit of a site that refuses the crawler (401, 403, 429 or a bot challenge) no longer reports its robots.txt, sitemap, llms.txt or Markdown response as missing. crawl/robots-txt, crawl/sitemap-exists, ax/llms-txt and ax/markdown-response say "not checked" and name who refused, a failed or blocked audit scores no categories, and the text, markdown, llm and JSON reports list the refused requests and, when the response names it, the bot protection behind them.
  • A response whose body doesn't match its declared content-encoding is recorded as a failed page, with an error naming the URL and the encoding, and the audit is marked partial with a count such as 2 pages could not be decoded (bad content-encoding). Before, the page vanished and the audit claimed to be complete.
  • A meta refresh inside <noscript> no longer moves the audit to the no-JavaScript page. A browser with scripting on never follows it.
  • A cached page reused by an incremental crawl follows the same link discovery settings as a freshly fetched one. With link discovery turned off, one reused homepage used to put the whole site back in the queue.
  • A JSON-LD node with its own @type and a @graph keeps its own type, so a WebPage wrapping an Organization is checked as both. A page reused from the crawl cache picks this up once it is fetched again, or with --refresh.
  • Soft-404 detection picks up framework error pages served with a 200, such as a Next.js __next_error__ shell, as candidates in every audit. The engine the CLI uses read that marker from the page's DOM after it had already released it, so it missed them.
  • The entity map published with a report sets aside about a fifth of its node budget (more on a small map, so each kind of problem still gets one) for nodes that show the entity problems the rules report (conflicting properties, dangling references, a missing or malformed @id, a split identity and the rest), taking turns so no one kind crowds out the rest. A finding's evidence is no longer sampled out of the map just because the entity is rarely linked. The map in the HTML report is chosen the same way.
  • Merged report messages no longer turn HTTP statuses into ranges: redirect chains ending in a 404 and a 500 stay separate rows instead of merging into a (404 to 500) status that doesn't exist. Redirect messages now write the status as (HTTP 404).
  • A signed-in audit whose publish gets a 5xx after the report was already saved now returns the report URL instead of failing. The CLI checks whether the run gained a newly linked report, and does not post the report again after a 5xx.

Security

  • Six regular expression scans that a crafted page could make backtrack for seconds now run in linear time and match exactly what they matched before: four in security/leaked-secrets (JSON Web Tokens, Google OAuth client ids, the <script> tag scan and the key-name look-back), the CSS custom property scan behind the template fingerprint, and the title separator in content/title-pattern-outlier. The time budget above is the backstop for anything we haven't found yet.

Update to this version

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

$

Downloads

macOS

Detected
Apple Silicon (M-series)
squirrel-0.0.106-darwin-arm64 · 69.6 MB
Download
SHA-256
Intel
squirrel-0.0.106-darwin-x64 · 76.1 MB
Download
SHA-256

Linux

x64
squirrel-0.0.106-linux-x64 · 87.3 MB
Download
SHA-256
x64 (musl/Alpine)
squirrel-0.0.106-linux-x64-musl · 81.4 MB
Download
SHA-256
ARM64
squirrel-0.0.106-linux-arm64 · 87.2 MB
Download
SHA-256
ARM64 (musl/Alpine)
squirrel-0.0.106-linux-arm64-musl · 80.6 MB
Download
SHA-256

Windows

x64 (Intel/AMD)
squirrel-0.0.106-windows-x64.exe · 93.3 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.