v0.0.107
Released Oct 9, 2026
Release Notes
squirrelscan can now probe, politely: a new probing intensity setting decides how much an audit may send, and the first two probing rules (GraphQL introspection and GraphQL mutations over GET) use it. There is also a rule for auth tokens kept in web storage, a rule for a CSP that blocks the page's own resources, quieter secret scanning, and the entity page filter now behaves the same in the CLI, the MCP tools and the hosted API.
Added
security/token-storageflags JWTs and session or access tokens written tolocalStorageorsessionStorage. Anything in web storage can be read by any script on the page, so one XSS bug turns a stored token into an account takeover, where anhttpOnlycookie would not. It is passive: it reads the inline scripts and the external scripts the crawl already fetched and sends no extra request. A key is split into words, and whole words decide, soaccess_token,jwtandidTokenflag whiletheme,dismissedandcsrfdo not; a value shaped like a JWT flags under any key. The report names the script and the key and never prints the value.- Two GraphQL probing rules, the first rules that use probing intensity. They run only when probing is
activeoraggressive, probe only the audited origin, and on apassiverun send nothing. Both probe the GraphQL endpoints the audit found in your pages and scripts, plus/graphqland/api/graphql, at most five per rule, and never a path that names a state-changing action such aslogoutordelete. Belowaggressivethey skip pathsrobots.txtdisallows.security/graphql-introspectionsends oneGETwith the read-only query{__schema{types{name}}}and warns when an endpoint returns its schema, naming the endpoint and the type count; on anaggressiverun it may follow up with onePOSTof the same query when theGETanswer is GraphQL without a schema.security/graphql-get-mutationssends oneGETwith the no-op mutationmutation{__typename}, which cannot write, and fails when the server executes it, because a mutation reachable byGETcan be fired cross-site with your visitors' cookies (CSRF). Disabled introspection, errors, a405and HTML or catch-all answers are not flagged. See Security configuration. That makes 301 rules, 20 of them in Security. security/csp-blocks-own-resourcesflags a Content-Security-Policy that blocks what the page itself loads. It matches each page's own policy (the header and any<meta http-equiv>policy) against the page's scripts, preloaded modules, stylesheets, frames and images using real source matching, so*.example.comallowscdn.example.combut notexample.com. It also looks inside the same-site chunks a page loads for third-party script URLs, and checks the hosts a detected vendor needs at runtime (Google Analytics 4 and Google Ads endpoints, Google Tag Manager, PostHog, Stripe, Cloudflare Turnstile, reCAPTCHA), which the HTML never names. A blocked analytics, ads, payments, captcha, form or booking host fails and any other host warns, naming the directive and the host to add. A page with no enforced policy passes, because a missing CSP stays withsecurity/csp. See the rule page. That makes 298 rules, 17 of them in Security.- Probing intensity, a third audit setting next to coverage and render mode.
--probe(-P) takespassive,activeoraggressive, with--passiveand--aggressiveas shortcuts and--pentestfor--coverage full --probe aggressive.--probe-budgetcaps the wall-clock time of all probing together (default30sactive,2maggressive).[security] probeandbudgetset both in config. A logged-in audit defaults toactiveand a logged-out one topassive;aggressiveis never a default.--disable-discovery-probeskeeps probing passive. No probing rules ship yet, so no audit sends anything new: this adds the setting and the shared budget future rules will use. See Security configuration.
Changed
- Publishing from a large cloud audit no longer slows down as a site's page history grows. The merge indexes the prior pages once instead of copying them several times, builds the full page list only when something reads it, and writes back only the pages the run changed. At 25,000 pages this removes tens of milliseconds of cold-start work per publish.
- The
send_feedbacktool description in the local MCP server is neutral about when to use it, and a test now fails when the MCP docs drift from the tools the server lists. squirrel entitiesand the MCPlist_entitiestool now apply the same page filter rule as the hosted API, and a filtered result keepssummarysite-wide.--pagewith an absolutehttp(s)URL is a prefix of the page URL, a value starting with/is a prefix of the path and query (so--page /blogno longer matcheshttps://example.com/tag/blog), and anything else is a substring. Use--page blogfor the old substring behavior. A filteredsquirrel entities -f jsonandlist_entitiesused to return asummaryrecomputed over the matching entities. They now return the site-widesummaryand a newfiltered: { nodes, edges }block with what the filters kept, so scripts that readsummary.nodeCountafter a filter should readfiltered.nodesinstead.--diffstill compares recomputed summaries.- The audit also fetches the same-site script chunks a page preloads with
<link rel="modulepreload">or<link rel="preload" as="script">, sosecurity/csp-blocks-own-resourcescan read the third-party script URLs inside them. They are kept apart from the scripts every other rule reads, so no other rule's result changes, and they are fetched only when a crawled page enforces a CSP, capped at 20 extra fetches per audit. The rule cache format changed, so the first audit of each site after the upgrade runs every rule fresh, once.
Fixed
security/leaked-secretsraises fewer false alarms. A label that ends in a credential word, such as a translatedPassword:heading in compiled JSX, is no longer read as a key whose value is the markup after it, template expressions and placeholder connection strings are not credentials, and a key that runs past a provider's format is reported as a secret of unknown provider instead of being named as that provider's. Public vendor identifiers in a script's query string (Mida, Klaviyo and reCAPTCHA site keys) are skipped. A Statsigsecret-key is reported as a server secret and aclient-key as the public SDK key it is.- A site that sends response headers and then stalls its body can no longer hang the CLI. The shared request helper used by the reachability check, the robots.txt fetch and the update check disarmed its timeout as soon as the headers arrived, so the body read had no time limit. The timeout now covers the body too, and a stalled body fails like any other timeout.
- The audit engine can keep the cloud results it has already paid for when its caller stops the cloud analysis step at a deadline, instead of discarding all of them. A caller turns this on with the new
deadlineMsoption on the container prefetch (or by passing its own accumulator and callingabandon()). Results that came back are used by the rules, rules still waiting reportskippedwith reasonservice-unavailable, and no new cloud call starts after the deadline. The spend lists each call known to be charged, once: every call that came back, plus a render batch the cloud accepted but had not finished. A call still running at the deadline is not listed and may yet be charged, so the estimated balance after the run is marked approximate (balanceAfterApproximate). Nothing in the CLI sets a deadline, so CLI audits are unchanged, and a prefetch that finishes in time returns exactly what it did before. See credits.
Update to this version
squirrelscan will auto-update, or run this command to update now:
$