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 mcpsupports 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 noinitializehandshake and reads the server's capabilities fromserver/discover, and an older client keeps theinitializehandshake. 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 channelpushes cloud audit events into a running Claude Code session. Add it to your MCP config assquirrel 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),--categoriespicks 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 singlelogin_requiredevent 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>-errorcheck (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>-timeoutscheck 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-contactflags contact details a template shipped with and nobody replaced: email addresses atexample.com,test.comoryourdomain.com, the fictional555-01xxand UK07700 900xxxphone ranges,123 Main St, and handles such as@yourhandle. It reads the visible text and every link'shref, so amailto:info@example.combehind an "Email us" link counts. It fails on a contact or checkout page, skips other pages under documentation paths such as/docsand/api, warns everywhere else, and never judges a code sample. See the rule page.content/placeholder-mediaflags images that were never finished: a placeholder service source (via.placeholder.com,placehold.co,dummyimage.comand friends, orplaceholderas a word in the image's filename), a blank 1x1 inline image used as content, and camera-default alt text such asDSC_0001orIMG_1234. A placeholder source or a blank stand-in fails and a camera-default alt warns. A lazy-loaded image is judged on its realdata-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
skippedCheckslist with the rule, the check, the reason and the pages (with the full count when a long list is clipped), andsummary.skippedadds up those skipped page checks. Before, the JSON dropped them, so a page thatcontent/hidden-textorcontent/dev-leakagecould 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-weightscores 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 oneHEADrequest 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.includepatterns 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] domainslist 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 carriesstopReason: "time"next tomaxPages, the llm outputstop-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'shreflangalternates. 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-titleandcontent/duplicate-descriptionno longer report region variants of one page, such as/en-gb/shirtand/en-us/shirt, as duplicates when each lists the other as anhreflangalternate in the same language and neither canonicalises elsewhere. Adeandenpair 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-conflictsno longer reports robots.txtAllowplusnoindexas a conflict, since that is the supported way to deindex a page. Its remaining info check, a page robots.txt blocks without anoindex, now lists the pages.crawl/sitemap-coverageno longer lists a noindex page as missing from the sitemap, andcontent/title-pattern-outlierleaves noindex pages out of its template vote, when the site itself is indexable.- The
dom-max-childrencheck ofperf/dom-sizecounts<body>only, so a<head>full ofhreflangalternates or preload hints no longer trips its 60-children limit. a11y/autocomplete-tokensno longer asks for anautocompletetoken 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-headingskips headings hidden in the markup (hidden, inlinedisplay:noneorvisibility:hidden, or anaria-hiddenor 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-bodyskips schema.org microdata<meta itemprop>tags, which HTML allows in<body>.a11y/video-captionsandvideo/video-accessibleskip decorative video, one that isaria-hiddenor muted with no controls and autoplays or loops, and both rules now agree on what counts as decorative.perf/lcp-hintsuses the same LCP candidate finder asperf/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 andsrcset.content/date-agreementreads dates in French, German, Spanish, Italian, Portuguese and Dutch, year-month-day dates with Japanese, Chinese and Korean markers, and numeric dates, reading an ambiguous08/01/2026both 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-reviewno longer counts as a publication date.security/new-tabno longer warns ontarget="_blank"withoutnoopener, because browsers imply it, andnoreferrerimplies it too. It warns only on an explicitrel="opener", readsrelas a list of tokens rather than a substring, and a missingnoreferreris an info-level privacy note.security/mixed-contentreports onlyhttp://URLs in markup that loads a resource, so canonical and alternate<link>s, which are never fetched, no longer count. Loopback URLs such aslocalhostand127.0.0.1are not mixed content and get their ownlocal-development-urlwarning, 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-inboundskips when the crawl stopped at its page limit, because a page it never reached could hold the link.legal/subprocessor-disclosurewarns 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-stuffingno 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.txtor Markdown response as missing.crawl/robots-txt,crawl/sitemap-exists,ax/llms-txtandax/markdown-responsesay "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-encodingis recorded as a failed page, with an error naming the URL and the encoding, and the audit is marked partial with a count such as2 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
@typeand a@graphkeeps its own type, so aWebPagewrapping anOrganizationis 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 incontent/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: