# ShakerScan 2.11.1

> ShakerScan documentation at v2.11.1. [Source](https://github.com/andriyze/shakerscan/blob/v2.11.1/docs/releases/2.11.1.md) · [Web version](https://shakerscan.com/docs/open-source/releases/2.11.1)

The client for this release is `shakerscan` 0.11.1.

A patch release: security and correctness fixes, longer time budgets for `thorough` and `deep`
Scans, roomier Hunt profiles, reliable Hunt recording, and dependency updates. No schema changes.

## Security

* **Login attempts and state-changing GETs need the state-changing permission.** Active Nuclei
  templates that sign in by GET ran in any Scan with active testing (`tomcat-default-login` sends
  its password list in an `Authorization` header), and a login attempt can lock the account. A
  template tagged `default-login`, or one that sends a username or password in a header or the body,
  or a password in the query string, now needs state-changing HTTP whatever its method. So do the
  GETs reviewed as changing state: one sets a device password, one restores a default admin
  password, one changes a plugin's settings, one registers a user, one adds an administrator. 56
  bundled GET-only templates of the default selection move behind it. The Scan form and the
  terminal's `--allow-state-changing-http` help say so, and the step's receipt counts them
  (`template_selection.default_login`).
* **Exploit templates need the state-changing permission.** The active template sweep ran exploit
  templates sent by GET in any Scan with active testing: 68 of the default selection, 13 of them
  tagged `rce` (one puts `;curl ...;` in a parameter, another a `Runtime.exec` expression in a
  path). Only a verify Scan held them back. A template tagged `rce`, `lfi`, `sqli`, `ssrf`, `ssti`,
  `xxe`, `deserialization`, `code-injection` or `cmdi`, or one that runs a code, JavaScript or flow
  step, now needs state-changing HTTP in the sweep as well. With the permission the sweep runs the
  same templates in the same windows as before. The Scan form and the terminal's help say so, and
  the step's receipt counts them (`template_selection.exploit_class`).
* **Go-based scanner and Model Intake tools use patched compilers.** Go 1.27.3 and 1.26.10
  address `CVE-2026-111943`, which removes trust in affected intermediate certificates. Builder
  images remain pinned by digest; the image vulnerability gate checks the compiled tools.
* **`shakerscan record` never follows a redirect with the API token** (client 0.11.1). The
  recorder's HTTP client followed 301, 302 and 303 answers and re-sent `Authorization` to the
  `Location` host, including another origin and from HTTPS to HTTP. It now refuses every redirect,
  like the other client commands, and names the refused origin.
* **Copyable commands pass every target-chosen value as one shell word.** The reproduce, verify,
  replay and check commands on finding pages could carry an unquoted URL, parameter or payload
  chosen by the target, which a shell would expand when pasted. Replay commands stored before the
  upgrade are rebuilt from their stored inputs when read.
* **Capture replay withholds credentials carried in URL paths.** Magic-login, share, invite and
  reset links, cookieless session ids, long random tokens and userinfo in a captured URL are no
  longer replayed, and copies of the page URL in `Referer`, `Origin`, `HX-Current-URL`,
  `Next-Url`, query values and bodies lose the credential. Captures recorded before the upgrade
  are treated the same way when replayed. The display mask used elsewhere is unchanged.
* **Session headers stay on the target's origin across redirects.** A Scan authenticated with
  an API-key or other custom session header sent that header to whatever origin a redirect
  named: curl, httpx, aiohttp, sqlmap, dalfox and nuclei drop only `Authorization` and `Cookie`
  on a cross-host redirect, and an inline `-b` cookie string goes everywhere. A request that
  carries such material now follows a redirect only to the same scheme, host and port, or to the
  same host upgraded from `http` on port 80 to `https` on 443; on that origin it follows exactly
  as `curl -L` does (output, request bodies, cookie jar, `--max-redirs`). `Authorization`,
  `Cookie`, `-u` and `--oauth2-bearer` keep curl's own behaviour, since curl 7.83 and later strip
  them itself. Trade-off: sqlmap, dalfox and nuclei runs that carry the session do not follow
  redirects at all, and a curl request with a custom session header stops at a redirect to
  another origin, so a check whose proof sits behind such a redirect is not reached in a
  custom-header session.
* **Browser sessions send their headers to the target's origin only.** Playwright's extra
  headers went with every request a page made, including images and scripts from other origins
  and redirect hops to another origin, which no route handler sees. A browser context that
  carries session headers (the XSS browser proof, the authenticated browser crawl, the
  interactive browser session once a header is set, the AI widget target) is now confined at the
  network layer: all of its traffic goes through a local proxy that admits only its bound
  origins (scheme, host and port, or the same host upgraded from http on port 80 to https on
  443\) and refuses every other connection before it is made. The headers stay the context's own,
  so Chromium keeps its streaming, CORS, cookie and size behaviour; an HTTPS connection elsewhere
  is refused before any byte of the request is sent, and a plain-HTTP one reaches only the local
  proxy. The cost: such a context loads nothing from another origin (a CDN, a third-party
  script, a redirect to another site). Each refusal is listed in the Scan's coverage, the
  browser crawl's and XSS proof's results, the interactive session's action result and the AI
  widget's evidence, without changing a family's status: `session_cross_origin_refused`, and
  `session_navigation_redirect_refused` for a navigation a bound origin redirected elsewhere.
  These confined contexts are granted Chromium's Local Network Access, so an internal
  application on private addresses loads; `private_network_subresource_blocked` is listed where
  Chromium still refuses one. A refused connection is closed unanswered, so the page sees a
  network error (never a response that would read as the other origin's), and WebRTC is
  disabled in these contexts, since it sends UDP outside any proxy. A context that carries only
  cookies is not confined. A custom session header makes a cross-origin request between two bound
  origins non-simple, so Chromium preflights it, as it did before. When an interactive (Hunt or
  AI verification) browser session is given a session header, it moves to a confined context
  that keeps its cookies, localStorage and current URL, but not its sessionStorage or any other
  page the user had open in that session.
* **Scan and Hunt browser actions keep trusted headers on the bound origin.** The browser
  capability's pinned proxy admitted the target's own address written as a host, so a redirect
  to that address as another origin received the trusted headers Chromium carries into redirect
  hops. The pinned proxy of a hostname target now refuses that address as a destination and
  counts it on its receipt (`address_literal_destinations_refused`); every scanner on that path
  (sqlmap, katana, nuclei, dalfox, httpx, ffuf) addresses it by hostname. The browser's tunnel
  also admits only its bound origin's scheme (a TLS record for https, plaintext for http), so an
  https origin's redirect to http on the same port is refused (`scheme_mismatch_refused`), and
  WebRTC is disabled in that browser. The action's result
  and the Scan's coverage list `session_cross_origin_refused` and
  `session_navigation_redirect_refused` when the proxy refused a connection.
* **Dalfox does not use its headless browser with a session.** Its headless verifier and
  `--deep-domxss` run a Chromium outside any proxy that adds the session headers to every
  request a page makes. A session-carrying run now passes `--skip-headless` and no
  `--deep-domxss`, and the Scan's coverage lists `session_header_withheld_from_headless`.
* **Known limit of browser sessions.** A Scan or Hunt browser XSS proof that sends its payload
  in a request body follows a 307 or 308 redirect the way the browser does for the page's own
  navigation, so the redirected request is a GET without the body.
* **A refused-redirect hint names only a plain origin** (client 0.11.1). The suggested
  `shakerscan connect <origin>` / `--url <origin>` used the `Location` host as sent, so a
  crafted `Location` produced a command that ran shell syntax when pasted. The origin is rebuilt
  from a validated host and port and quoted, or the hint says `<origin>`.
* **Path-segment SQL injection replay commands are quoted like the others.** Replay commands for
  a marked path segment (`/products/1*`) passed the stored URL to the shell with control
  characters and a backslash-quote intact.
* **A token used as a bare query name is masked.** A URL carrying a credential as a query key
  without a value (`?<token>`) showed it in capture request lists, replay plans and receipts.
* **Copy commands stored by earlier releases are rebuilt or shown as text only.** A finding's
  stored manual verification commands are rebuilt from the evidence's URL and path when read; a
  stored command that cannot be rebuilt or checked is shown as recorded text without a copy
  button.
* **The client shows server text without terminal control characters** (client 0.11.1). Public
  check results, instance health answers and error messages could carry escape sequences or a
  carriage return that rewrote the terminal; they are shown as visible text now.
* **aiohttp 3.14.5** in the scanner image, for CVE-2026-111064 (GHSA-2g87-pp6c-29x5, high),
  published after 2.11.0 (fixed in 3.14.4).
* **certifi 2026.10.19** in every Python image and tool lock, for GHSA-6q48-6c7w-9gw2 (critical):
  2026.7.22 bundles emSign root certificates that were compromised. The image vulnerability gate
  refused the older version. pip 26.2.2 in the Model Intake image's pip-audit environment, whose
  vendored copy of certifi carries the same fix.
* **filelock 4.2.4** in every Python lock that carries it (the scanner image, the Model Intake
  image's pip-audit environment, the locally built Firecracker guest and the CI pip-audit tool),
  for CVE-2026-112940 (GHSA-v4x8-3wr5-3wq8, high) and the other filelock advisories fixed in
  4.2.1, published after 2.11.0. The image vulnerability gate refused 3.32.2. tldextract,
  CacheControl, huggingface-hub and torch, which pull it in, all accept 4.x. The scanner and API
  images also remove the Playwright base image's virtualenv and its dependencies, which nothing in
  them uses: virtualenv requires filelock older than 4, and the base's filelock 3.32.5 stayed
  installed beside the locked version. Their builds now fail when a Python package is installed
  twice.
* **Next.js 16.4.1** in the UI image, for GHSA-2464-fhr4-w9xw and GHSA-673q-mjm6-r5xr (critical)
  and CVE-2026-94482 (high), published after 2.11.0. The image vulnerability gate refused 16.4.0.
* **Go scanner tools build with patched echo and compress libraries.** echo 4.16.1 in dalfox, for
  GHSA-cxf3-9h37-7rr8 (critical) and GHSA-9969-6676-49h5, GHSA-f48p-c9v4-h7xv and
  GHSA-h7wr-284f-5qvc (high), published after 2.11.0; klauspost/compress 1.18.7 in gungnir, httpx,
  katana, naabu, subfinder and tlsx, for CVE-2026-63209 (high). The image vulnerability gate
  refused the older versions. The tools' own versions are unchanged, and the image build now
  stops if a library lift would move one.

## Fixes

* **A pre-2.6 install is refused instead of starting on a new, empty database.** An install that
  still holds PostgreSQL 16 data in `postgres-data` and has no migrated `postgres-cluster` no
  longer passes the start checks; the refusal names the backups to restore from. This corrects
  2.11.0, whose notes said such a start refuses. `redis reset` refuses in that state before it
  changes anything.
* **A pre-upgrade backup is taken when the release record does not name the running API's
  image** (running or stopped), or when that container cannot be inspected.
* TLS legacy-protocol probes run only when the action's reservation pays for them.
* **A parallel Scan's summary covers every part** when a finding has no CVSS score (a verified
  XSS) or gives it as text, or carries its OWASP or CWE labels as a list; the parent kept one
  part's summary. The merge no longer copies the summary grader's compliance block over the
  report's.
* An open-redirect probe that got no answer stays inconclusive and is probed again on resume,
  instead of counting as examined.
* Exposure service pages give a device and its host target, which share an id, their own
  inventories.
* Opening a browser capture by an IPv6 target URL finds the target.
* A credentials link with `?target=<url>` reads search pages until it finds the origin; a failed
  read is reported as such, not as a missing target.
* **A browser XSS proof or retest finishes when the headless browser stops answering.** Opening
  the proof page had no time limit, so a browser that stayed connected but stopped answering held
  the Scan's proof, or the worker's retest, for good. A browser that does not start within 60 s,
  or open the page within 30 s, now fails that proof and is replaced for the next one; the retest
  of a verified XSS finding is then inconclusive and says so. The browser kept between proofs now
  belongs to the event loop and thread that started it and is closed when that loop ends: a
  process that ran proofs in a second event loop was handed the first one's browser, on which
  every call waited forever.

## Finding accuracy

* **A retest uses only the prover of the finding's own kind of check.** Title words matched
  anywhere chose the prover ("source" contains "rce"), so exposure, header and template findings
  were retested with command-injection or JWT payloads. The finding's tool now decides; a title is
  read, by whole words, only for an earlier scanner's attack checks. A finding with no prover, or
  only an AI verifier, offers no retest and says why (`retest_unsupported_reason`); run a new Scan,
  or the Hunt, again. `GET /findings/{id}` adds `retest_active`.
* **A verified SQL injection, NoSQL injection or XSS finding is retested with the request its proof
  sent.** The retest read neither the tested field the proof recorded nor the route's site, so it
  sent its payloads to the bare route with no parameter and could never conclude; a retest request
  was refused with "The target's authorization does not cover the host". The retest now rebuilds
  the proved request (the route on the target's site with the tested query parameter, or the page
  with the browser proof's own payload, checked against the digest the finding kept) and runs the
  check that proved the finding, a page judged by the browser proof's own marker: still
  vulnerable when it reproduces, likely fixed when it does not and every request was answered,
  inconclusive otherwise (a refusal, a service that is unavailable, rate-limiting or no longer
  serves the page). A finding whose request cannot be rebuilt (a path segment, a templated
  route such as `/products/{int}`, a request body, a recorded request whose values were not kept, a
  fragment) offers no retest and says what is missing (`retest_unsupported_message`); so does a
  potential SQL injection or XSS from a request differential, which keeps no route. New SQL and
  NoSQL proofs record the site they were proved on (`origin`).
* **An AI retest verdict is at most inconclusive.** An AI "likely fixed" or "false positive" could
  refute a Hunt candidate or close a finding; it is now inconclusive, with the model's note. The
  finding page no longer offers an "AI only" retest (`retest_modes` lists `tiered` and
  `deterministic`); the API still takes `mode=ai`.
* **Reflected XSS is browser-proven again.** The Scan's browser proof declared an adapter identity
  the capability registry did not accept, so every proof was rejected before it ran and no
  "Verified cross-site scripting" finding could be produced. A proof that breaks now leaves its
  family partial and the grade provisional, instead of complete.
* **"Nothing to test" needs a crawl that ran.** When every crawl of a Scan failed or never
  started, its selected XSS, SQLi and NoSQLi checks found no candidates and read complete
  (`no_candidates`) with a reliable grade, although nothing was tested. They now read partial with
  the reason `discovery_failed`, so the Scan is not complete and its grade is provisional. When
  one crawl ran (the static crawl on a target without a browser runtime), an empty candidate list
  still means there was nothing to test.
* **A CORS policy that reflects any origin is detected.** The receipt redactor rewrote the echoed
  `Access-Control-Allow-Origin` before the check compared it with the probe origin.
* **Reproduce and check commands work from masked evidence.** Engine-authored probe values (the
  open-redirect target, the GraphQL introspection document) are kept; a stored URL whose query
  the scanner masked is not replayed as stored.
* **Active Nuclei templates run against the scanned URL, not a discovered endpoint's path**, on
  every profile, so host-level templates such as exposed-service checks match. Each active-template
  attempt now targets the directory of the URL the Scan targets (`/app/login.php` runs at
  `/app/`), and then the root of every other origin the Scan discovered; plans, holds and attempts
  are unchanged, only the URL each attempt targets. A host with more endpoints than template
  windows no longer repeats a window at the same URL. A finding a 2.11.0 Scan matched at a
  discovered subpath with a template that asks only its base URL (108 of the permitted templates)
  may not be reproduced by 2.11.1 and then reads as fixed: those templates now run at the Scan's
  base and the other origins' roots, not at discovered subpaths.
* **Active-template coverage counts every origin.** `nuclei_active.template_windows` counts
  (origin, template window) pairs and lists each origin's run and total, so a second origin that
  ran 11 of 39 windows no longer reads as 39 of 39. A thorough or deep Scan whose active templates
  did not all run at one of its allowed origins reads partial, with the reason
  `template_windows_incomplete_on_origin`. Each parallel scan part starts its template windows at its
  own offset, so alone it runs as many windows as before and the parts together run at least as
  many; the merged coverage counts each (origin, window) pair once and says how many pairs two
  parts both ran (`repeated_pairs`);
  an attempt resumed from before the upgrade is kept and never sent again, and a window it ran at
  a discovered endpoint's path is listed apart (`legacy_windows_run`), not counted at an origin.
* **Known limit:** a thorough or deep host whose endpoints span more than one origin and whose plan
  has fewer than 39 positions per origin reads partial on active templates
  (`template_windows_incomplete_on_origin`): 2.11.1 does not change the plan, and a later release
  will plan the windows per origin.
* **Active Nuclei never runs a template that requests a heap dump**, on any profile. A test checks
  the bundled template set, so a template update cannot bring one back.
* **A SQLi point sqlmap marked in a URL path segment** (`/products/1*`, `#1*`): the brief and replay
  commands use the unmarked URL with the payload in that segment, the database name comes from
  sqlmap's summary line, and a retest declines (inconclusive, nothing sent) because the retest
  prover injects only query and form parameters.
* **Each variant of one form is tested as its own candidate.** A form seen as declared, as crawled
  with its hidden fields and as a JSON variant has one candidate identity (its route, method and
  the field that anchors the body), and the three shared one attempt record: a variant could take
  another's finished SQLi techniques as its own, so the crawled form's extra fields went untested
  while the candidate read complete; a variant whose attempt ended differently from another's could
  fail the verifier, the SQLi proof, the XSS browser proof or the NoSQLi check; and one variant's
  verifier signal sent all three to the proof. Each variant now keeps its own attempt, techniques
  and count of what it sent in each of them, and a signal sends only the variant that produced it
  to the proof; the first keeps its earlier record, so a scan resumed across the upgrade keeps what
  it already ran (a signal recorded before the upgrade still sends every variant). The
  anonymous-versus-signed-in route comparison probes a route seen with several bodies once and
  counts each of its entries, so coverage no longer reads them as never attempted, and the
  exposure sweep likewise reads such a URL once instead of once per body.
* **A scan allowed state-changing requests no longer fails to plan its verifiers when two forms
  post to one action** with the same rank (a login page's form and a sidebar sign-in widget, a
  search form seen with and without a filter): building the candidates raised `TypeError` at
  admission, in a continuation round and in the parallel fan-out. Both forms are now candidates.
* **TLS:** a weak signature or key no longer also raises "chain is not trusted"; the certificates the
  server sent are read correctly on Python 3.12; weak-cipher evidence comes from the weak handshake;
  reproduce commands show the weakness.
* **Finding pages:** a page titled "Directory listing" is no longer a verified listing without a
  real listing; GraphQL findings get GraphQL commands and advice; JDK 9+ stack frames are
  recognized; non-secret key names are not masked; weak-CSP advice never loosens framing; Tomcat's
  Apache-Coyote is not reported as Apache httpd; DNS posture findings use the target's origin; the
  coverage page and the stored grade agree.

## Authenticated scans and Hunts

* **A credential profile left with no capability list now covers the crawl.** On an HTTP identity
  it grants every read-only capability that uses the identity (probe, crawl, browser crawl,
  content discovery, spec ingest, passive templates, safe collection replay, browser navigation);
  sign-in kinds also keep their session capabilities. Active verifiers are never a default and
  still need an approved elevation. Profiles saved with a list keep it. New Scan says when the
  attached profile leaves the crawl or a selected verifier running anonymously.
* **A crawl or content discovery that carries a session leaves action links alone:** sign-out,
  delete, disable, reset and similar paths and query actions are not requested with the session.
* **XSS and SQLi verifiers no longer fail with zero requests** when a replayed collection request
  and a declared endpoint share a route.
* **A signed-in crawl starts where the signed-in area is.** A crawl or browser crawl that carries
  the session also starts at the page the sign-in landed on and at the GET seed endpoints on its
  origin (pages before API routes, at most 20), so an area linked from nowhere the target's root
  reaches (a `/hub/` behind `/hub/login`) is crawled with the session. The target keeps its whole
  time box: the static crawl runs its inputs side by side under its one rate limiter and request
  ceiling, so the seeds share the target's requests while they run (a seed that finishes returns
  its share), and the browser crawl adds only its first seed, so the target keeps half of that box.
  Action paths (also percent-encoded), route templates and the profile's own sign-in page are
  never a seed, and a seed the crawler cannot take is left out instead of failing the crawl.
  A static primary identity (a bearer token, a header or a cookie), which has no sign-in step,
  starts from the same seed endpoints, whether the Scan carries one profile or two.
* **The credential profile's own sign-in endpoint is tested at a lockout-safe pace.** An
  authenticated balanced scan of a test application sent about 1,460 injection requests to the
  sign-in form it used. On that one endpoint (a request with a body to the sign-in path, its
  routing query included, or to the form's action) the SQLi and XSS verifiers keep their full
  payloads (sqlmap level 2 / risk 2, Dalfox's own payloads for the body), send at most 20 requests
  a minute, one attempt at a time, and share one probe set per verifier across every candidate
  and round of the scan, sized at twice what a verdict was measured to cost: 946 sqlmap requests
  per tested field (twice its level-2 ladder of 473) and 64 Dalfox requests per field, at most
  four fields, Dalfox stopping at its first verified finding. A check the probe set leaves without
  a verdict is inconclusive, never negative, and its family reads partial with the reason
  `auth_endpoint_probe_limited`. A GET to the sign-in page and every other endpoint keep their
  full holds, and plans are unchanged. sqlmap's stages on the endpoint are paced and recorded at
  its pace, so the latency they measure is that endpoint's and paces no other candidate. The
  checks that send from the worker itself (the SQLi and XSS request verifiers, the SQLi proof,
  the NoSQLi check and the XSS browser proof) keep the same pace there and each draw from a probe
  set of their own, twice what one attempt sends per field, and three times that again for the
  checks whose attempts test every field (the NoSQLi check and the request verifiers): 28 for the
  SQLi proof, 24 for NoSQLi, 12 for each request verifier and 2 for the XSS browser proof. Each
  set is the endpoint's four-field set whichever variant of the form draws from it, so the
  declared form, the crawled form and a JSON variant all fit, twice over, in any order (sized by
  each variant's own fields, the crawled form's NoSQLi attempt could spend a two-field variant's
  set, and a fresh scan left up to two variants untested). Such a check runs on the endpoint
  only when its slice can pace the whole attempt (a sign-in form's SQLi proof is 8 requests, so
  32 seconds); otherwise it waits rather than running short, and a proof no slice could pace
  leaves the family partial (`auth_endpoint_probe_limited`).
* **How much the sign-in endpoint may receive.** Up to 4,352 requests per scan (the four-field
  sets: 3,784 sqlmap, 256 Dalfox, 112 SQLi proof, 96 NoSQLi, 48 for each request verifier and 8 XSS
  browser proof), at most 20 a minute, so about 3 h 38 min if the scan's budget lets all of them
  run; clearing a two-field form with no SQL injection takes about 950 sqlmap requests (about 47
  minutes). In a scan split across parallel parts each part paces the endpoint by the number of
  parts, so together they stay at 20 a minute, but each part holds its own probe sets: N parts may
  send N times that total. The bound covers the injection verifiers and the checks above; the
  active templates (run by default on `thorough` and `deep`) are neither paced nor counted there,
  so a template for a product whose sign-in path is the profile's own (the `default-login` ones,
  for example, when state-changing HTTP is allowed) may send its sign-in attempts to it as well.
* **Expect XSS to read partial on an authenticated scan that tests its sign-in form**, and the
  grade provisional, unless an XSS is verified there. Dalfox sends its whole payload set to a
  request body whether or not a field reflects (1,347 requests for a two-field sign-in form,
  measured in the scanner image), ten times the 128 its probe set holds there, so its attempt on
  the sign-in endpoint cannot end negative: it is inconclusive (`auth_endpoint_probe_limited`).
  Every other endpoint is unaffected.
* **A credentialed scan admitted on the target's standing authorization records a credential-tier
  receipt**, as New Scan does, whether or not the request named that authorization: the scan is
  bound to a receipt derived from it (same scope and approver, a seven-day bound, the scan and the
  source named in it), which covers the scan's credentials and the source's tier only and
  authorizes nothing else. It ends with its source: every revocation path revokes it (also one
  filed under an application service from its host's authorization), and the running-scan check
  and the worker's credential check, the only check a passive credentialed scan has, refuse it
  once the source no longer stands. Who may start the scan is unchanged.
* A browser session the session store refuses ends the Hunt action with its reason.
* A scan refused at admission leaves no target behind and keeps its blocked audit record.
* **ASM:** a completed recon fills the target's endpoint inventory from what the scan observed,
  without probing the target again; a target without an inventory can be opened from the ASM page;
  the continuous-testing controls are hidden where the deployment does not offer them. Several web
  apps on one host (different ports) are each listed in their domain group, so every target with
  an inventory appears on the Coverage page. `GET /targets/grouped` therefore lists a domain's
  other root targets, such as other ports of one host, in `subdomains` and counts them in
  `subdomain_count`, `total_count`, `total_findings` and `total_targets`; `best_score` and
  `latest_scan` remain the group root's own values (the root is the last root target in URL
  order). Only an ASM batch or recon scan is labelled as hidden from the Scans list, in the
  view and in the 409 an ASM action gets while a scan runs.
* **Client 0.11.1:** `shakerscan scan help` prints the options instead of submitting a scan, under
  its own name (`usage: shakerscan scan`); `hunt call` exits 2 when the action was refused; a
  certificate the client does not trust names `SSL_CERT_FILE`, once per line; a state-changing
  scan from the terminal relies on the target's standing authorization; a submitted scan prints
  the families it runs and, on `fast` or `balanced` without them, how to add active templates
  (`--include-family nuclei_active`); `hunt start --credential-ref` takes the short slot names
  (`primary=`, `secondary=`, ...). The Hunt page keeps decided permission requests visible.

## Hunt recording and budgets

* **Only the duration ends a Hunt.** A used-up safety ceiling (state-changing requests, TCP
  ports, hosts, UDP ports, out-of-band interactions, device fragility) or any other used-up limit
  refuses only the actions that need it, each with a `budget.raise` request a person can approve
  (a dimension whose permission is off is refused as before), and the Hunt keeps running: on
  2.11.0 it stopped the whole Hunt, read-only work included. Only `max_duration_seconds` running
  out ends a Hunt as `budget_exhausted`. The ceilings keep their values. Hunts that stopped on
  another dimension under 2.11.0 resume as before.
* **Ports are counted once.** A port a Hunt already attempted (listed in a sweep, or reported open
  by one) costs nothing more to fingerprint, check or inspect, and a `tls.inspect` is one port
  instead of its ten handshakes. Device fragility still counts every connection: on a device
  Hunt a re-sweep or a `tls.inspect` handshake costs a fragility point and a device request even
  when the port costs nothing more. A network action stopped part way is charged the new hosts and
  ports of the commands that started. A mutating `candidate.verify` is charged the requests and
  state-changing requests its workflow reports it sent, not its whole 24 / 12 hold.
* **SNMP inspection is charged what it uses.** `service.snmp.inspect` is charged its UDP port and,
  on a device, its fragility estimate for each address it reached; 2.11.0 charged no UDP port
  and one fragility point.
* **Limits that outlast the duration.** Every call that runs is charged at least one second of the
  duration, and call, verification, browser and HTTP limits now cover the whole duration at the
  most one call can be charged in that second: `fast` 1 h, 3,600 calls and verifications, 115,200
  browser actions, 460,800 requests; `balanced` 4 h, 14,400, 460,800 and 1,843,200; `thorough`
  24 h, 86,400, 2,764,800 and 11,059,200. Every call stays paced by its tool. A request refused
  before it sends is charged the wall it took (at least a second), not its 15-second hold, and a
  paced Dalfox or sqlmap run without its own request log is charged its rate over the seconds it
  ran, not its whole hold.
* **Approval-gated calls may use the whole call allowance** (`max_active_actions` 3,600 / 14,400 /
  86,400, was 4 / 20 / 80); every such call still checks its approval receipt. TCP ports,
  state-changing requests, hosts, UDP ports, out-of-band interactions and device fragility keep
  their values.
* **The default profile needs no raise for ordinary web work.** Logging in, crawling, content
  discovery, verification and recording draw no start warning; a remaining warning names a safety
  ceiling that matters only when the plan needs that capability.
* **Useful verifier reservations on every profile:** `sqli.verify` reserves 900 requests / 540 s
  and `xss.verify` 1,800 requests / 480 s. A Hunt that cannot fund one asks for a budget raise
  instead of starting a shortened verification.
* **Recording never stops a Hunt.** Findings and candidates share `max_candidates` (one record per
  call: 3,600 / 14,400 / 86,400) and need no calls or traffic; a full allowance refuses only that
  write, a refused write spends nothing, and an unfinished budget-exhausted Hunt can still record.
  A Hunt's findings hold at most 64 MiB, and its candidate writes stop at 256 MiB of candidate
  records; a write past either is refused and the Hunt keeps running.
* **Findings stay on the service their evidence came from.** A relative path keeps the target's
  own service when it is evidenced and otherwise follows the single evidenced service, including a
  nonstandard port; a finding created without a path is located on the evidenced service too.
  Device Hunts can save findings with a path: a device's address or name is a host, and a cited
  device HTTP probe names its service. A location is stored in one spelling, so
  `HTTPS://EXAMPLE.TEST:443/x` and `https://example.test/x` are the same finding. Malformed
  context origins and invalid ports are refused as input errors.
* **A repeated finding returns the existing record** unchanged, spends nothing and says whether
  newly cited evidence was attached; `findings.update` attaches it.
* **Finishing proposes reusable steps; it never saves them.** The Hunt's newest completed steps
  that can be replayed join one rolling proposal per target for `shakerscan knowledge review`,
  outside the Hunt's own proposal allowance, for a person to review before accepting. A step is
  left out when its inputs carry query values, headers, bodies, browser input, sessions,
  credentials or other hosts, or its path names an action, a credential, a code or a token, or
  carries a payload character or dot segment. Accepting the proposal names the digest of the text
  reviewed, so steps a later finish added are never accepted unseen. The debrief and results page
  report the outcome, and a failure does not lose the debrief.
* **Known limit:** on a slow sign-in endpoint a Hunt `sqli.verify` can count about twice the
  requests that reach the target. With the longer 540 s window it can then reach its 900-request
  ceiling, end with "above the hard ceiling" and be charged its full hold; run it again on a
  narrower parameter set. Counting it once is planned for the next patch.

## Time budgets on `thorough` and `deep`

`fast` and `balanced` Scans plan exactly as in 2.11.0; a test fixture generated on 2.11.0 pins
them.

* **Active Nuclei runs every template window.** Each endpoint attempt runs one window of 30
  templates. 2.11.0 ran as many windows as a host had endpoints, so a small host ran 1-6 of the 39
  windows of the active set. `thorough` and `deep` direct Scans that include active templates
  now run all 39. Coverage shows `nuclei_active.template_windows` (windows run of total) on every
  profile, and so does the scan page; a sweep whose every (origin, window) pair ran is complete
  in the report and on the page, not partial for endpoints the plan left without a position
  (the templates run at the origins, not at each endpoint).
* **The standard active set runs the active templates on `thorough` and `deep`.** The
  `standard_active` preset (New Scan's "Standard active", and `--active-testing` in the terminal)
  now adds `nuclei_active` on those two profiles; `fast` and `balanced` resolve and plan exactly
  as before. A scan whose request ceiling was lowered below 8,000 (twice the 4,000 the sweep holds
  at admission), since that hold might not fit beside the scan's own admission traffic, or with
  state-changing HTTP whose mutation ceiling was lowered below 5,000 (the sweep is the only
  mutation held at admission, and `thorough`'s own ceiling is 6,000), keeps the earlier set;
  `--include-family nuclei_active` still adds it, and `--exclude-family nuclei_active` removes it
  on any profile.
  The scan contract states the addition and its floors (`family_preset_profile_additions`,
  `family_preset_addition_floors`); New Scan's preview takes the ceilings typed under Advanced,
  and the terminal names the lowered ceiling when it leaves the active templates out.
* **SQLi extensions are sized to the techniques left.** 2.11.0 gave an extension the lane's whole
  time share and paced its requests to fill it (6.25 s per request on `thorough`). Extensions now
  hold what the remaining techniques need, at about 0.5 s per request.
* **Later rounds step down to a slice the remaining budget affords** instead of planning one the
  allocator refuses.
* **Request-body candidates get at least one attempt** when the Scan may send state-changing
  requests, through a floor in the first slice or a floor extension.
* **A lowered `max_duration_seconds` also lowers the tool wall** when no tool wall is given.

Expect `thorough` Scans of small hosts to take about 25 minutes instead of about 4.

## Documentation

* **The OWASP coverage matrix counts only what a Scan runs.** It marked SSRF, command injection,
  SSTI and XXE as covered, but no Scan family tests them: the `ssrf` and `rce` families are planned
  and a Scan refuses them, and SSTI and XXE have no family. A10 and API7 (SSRF), A04, A08 and API6
  now read "not covered". A06, A07, A09, API2, API3 and API5 drop from covered to partial. The
  other rows name the family or baseline check behind them and say which are opt-in, need
  credentials or need state-changing HTTP. API3 names its one narrow check, the plaintext-password
  account-record exposure. The matrix also gives the number of active Nuclei templates for named
  products that touch other categories; no row is marked covered for them. It says how much of the
  active pack a Scan runs: a `fast` or `balanced` Scan runs one 30-template window per endpoint, so
  a small host runs only part of the pack. The functionality reference lists all eleven Scan
  families, and a test keeps both documents in step with them.
* **The Scan contract describes `xss`, `sqli` and `bola` as they run.** `GET /scan/contracts`
  described `xss` as "Reflected, stored, and DOM XSS probes" and `sqli` as having extraction depth.
  No Scan action looks for stored XSS or extracts data, and the descriptions now say so. It said
  `bola` "Requires Lab/deep policy"; a Scan needs active testing and two distinct credential
  profiles that allow `authz.verify`, and only a focused Continuous ASM batch needs Lab/deep
  policy.

## Dependencies

* UI: lucide-react, postcss, Playwright 1.64.
* CI: CodeQL actions 4.38.3, `actions/download-artifact` 8.0.2, `actions/upload-artifact` 7.0.2
  (with the workflow changes it needs).
* posture: `@types/node` 26.6.5.

## Upgrade notes

* No database schema changes and no new settings.
* A Scan with active testing but without state-changing HTTP no longer sends default-login or
  password attempts, or exploit templates, so it reports no default-login findings and no findings
  from those templates; allow state-changing HTTP to test them. Authentication-bypass checks that
  name an account but send no password still run. Its active template sweep has 22 windows instead
  of 26 (636 templates instead of 760).
* Template, exposure, posture and Hunt findings no longer offer a retest: run a new Scan, or the
  Hunt, to check them again. A Hunt's verified XSS was retested with a Scan prover's default
  payload and check; it is now checked again by running the Hunt. Neither do verified injection
  findings whose request cannot be rebuilt, potential injections from a request differential, nor
  a SQL injection sqlmap found in a URL path segment (its retest sent nothing).
* `thorough` and `deep` Scans run longer and send more requests to each host than on 2.11.0 (for
  the active template windows, about 2,200-2,600 requests per small host at the unchanged 2
  requests per second). Their standard active set now includes the active templates, so a
  `thorough` or `deep` scan with active testing runs them unless it excludes `nuclei_active`.
