# ShakerScan 2.11.0

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

The client for this release is `shakerscan` 0.11.0 (`shakerscan record`).

## New

* **Browser capture (`shakerscan record`).** The client opens Chromium on a target, records the
  requests to the target's exact origin through the DevTools Protocol (no proxy, no certificate)
  and uploads them in batches to a *browser capture*, a request collection that Scans and Hunts
  use like an imported HAR. The server re-checks every request's origin, keeps the document
  encrypted and shows only the masked request index. Follow a capture live with
  `GET /request-collections/{id}/requests?since_seq=` or the MCP tool `shakerscan_captures`.
  Playwright is the optional `shakerscan[record]` extra. See
  [Browser capture](https://github.com/andriyze/shakerscan/blob/v2.11.0/docs/browser-capture.md).
* **New routes:** `POST /request-collections/captures`,
  `POST /request-collections/{id}/capture-batches` (idempotent per `seq`, body at most 16 MiB),
  `POST /request-collections/{id}/capture-close` (a capture is selected, scanned or Hunted only
  once closed); `GET /request-collections` takes
  `source_format`, and `GET /request-collections/{id}/requests` serves list/v1 pages with `seq`,
  `since_seq` and `cursor`.
* **`./scanner.sh record` on the same machine.** The launcher hands the recording to a client
  installed by pipx or pip, or behind a `shakerscan` client command on `PATH`, pointed at this
  install, and names the browser download with that client's own Python when it is still needed.
* **Archive search by path.** `GET /scans/{id}/http-transactions?search=` (and the Hunt archive)
  also matches a call's masked origin and path, never its query, userinfo or a value the masked
  archive withholds. Calls archived before the upgrade match on capability, adapter and method only.

## Upgrade notes

* **PostgreSQL 18 only.** The PostgreSQL 16 -> 18 data migration and `shakerscan db-upgrade` are
  removed. An install still holding PostgreSQL 16 data (the `postgres-data` volume of releases
  before 2.6) must be reinstalled; its start refuses rather than create an empty database.
* **The first start of 2.11 installs the new database schemas and count summaries.** Existing
  findings receive their stored severity order in small, resumable batches after the API starts
  serving. Until that finishes, lists use the equivalent computed order; cursors continue across
  the transition. Large databases can take longer to initialize their count summaries. Let the
  launcher finish before using the instance.
* **List indexes are built after the API is serving.** The indexes behind the bounded findings,
  targets and scans lists are created in the background, one at a time, without blocking writes
  (about 15 seconds at 500,000 findings). Until they exist, large lists answer more slowly.
* **A Scan running across the upgrade keeps running.** Its continuation leaves out optional work
  of a capability its allocation, frozen at submission, does not grant (such as the new open
  redirect verifier) before admitting the round, so that work holds no budget, and its report
  states each one in `coverage.reasons` as `capability_not_in_allocation:<capability>`.
* **Statement time limits now apply.** Database statements of the API (30 s) and workers (60 s)
  are bounded as configured; before 2.11 a pooled connection could lose the limit. A request that
  reaches the limit fails instead of holding the database.
* **Heavy reads are admitted, not queued.** When the API is already serving its share of heavy reads
  (large lists, the dashboard, exposure), another one is answered `429` with `Retry-After`; the UI waits
  and retries. Lists accept a `cursor`, `limit`, `sort`, `fields` and `count`; the
  older offset form still answers for one minor line and is deprecated.
* **The deprecated list shapes are bounded and say what they left out.** `GET /targets/grouped`
  without list/v1 answers at most 50 domain groups with at most 20 subdomains each (`has_more`,
  `next_cursor` continuing on list/v1, `truncated`, per group `subdomains_truncated`; the totals
  still count everything). `GET /targets/inventory?group_by=domain` holds at most `member_limit`
  assets per group (default 20) and 200 per page (`member_count`, `members_truncated`,
  `has_more`, `truncated`). `GET /exposure/assets` answers at most 500 assets (it was 1,000 by
  default and up to 2,000); `limit` echoes the bound and `truncated` marks a partial page. Each
  carries `list_v1_hint`. Read the rest with list/v1 (`count=none`, then `cursor`).
* **Exposure classification of a large backlog runs in the background.** A read classifies at
  most 1,000 assets it has not seen (targets added in bulk, or an upgraded database); the rest
  are finished by a background task and the summary compactor, and until then they are ranked
  with provisional values and the response says `classification_pending: true`.
* **Before an upgrade the launcher now also checks** that the install directory has room for the
  backup (`SHAKERSCAN_INSTALL_DIR_MIN_FREE_GB`, default 2), and that the running PostgreSQL and
  Redis are saving their data to their volumes. Either problem stops the upgrade with nothing
  changed and the running release still running. In a terminal it offers a complete
  `shakerscan backup` first (`SHAKERSCAN_UPGRADE_BACKUP=full` or `database` decides without
  asking); the automatic database dump is still taken otherwise.
* **A start never creates an empty database over a lost one.** When an install that has run before
  has no database volume, `start` stops and points to `backups/`. Start empty on purpose with
  `SHAKERSCAN_ALLOW_NEW_DATABASE=1 shakerscan start`.
* **Images.** `shakerscan images` lists every ShakerScan image and why it is kept;
  `shakerscan images prune` removes the ones nothing keeps. `SHAKERSCAN_KEEP_RELEASES` (default 2)
  sets how many releases stay for rollback, this one included. The release kept for rollback is the
  one that was actually running before the upgrade, even after a rollback with an older launcher.
  Installs on one host now protect each other's images, running or stopped.
* **Redis lost.** `shakerscan redis` shows Redis's health and persistence; `shakerscan redis reset`
  recreates it empty (keeping a copy) when its data is lost or it cannot start. Waiting Scans are
  queued again automatically.
* **No backup of a new database.** A first start and `shakerscan reset` no longer take a full
  backup of the empty database.
* **Broker-only fleets.** `shakerscan fleet init --workers 0` is accepted, with a warning: nodes
  join with no scan workers until the fleet is scaled.
* **Model Intake enabled later needs an API restart** for its automatic review to start
  (`shakerscan restart`).
* **Finding kinds a Scan no longer reports are resolved automatically.** 2.11 stops reporting:
  a passive-template "Missing HTTP response header" for a header the baseline response already
  proves missing on the same origin (Content-Security-Policy, Referrer-Policy,
  Permissions-Policy, and Strict-Transport-Security on HTTPS) -- one finding per header and
  origin remains, with the template's matched pages as evidence; the same template's match for an
  opt-in isolation header whose absence is the browser default (`Cross-Origin-Embedder-Policy`,
  `Cross-Origin-Opener-Policy`, `Cross-Origin-Resource-Policy`,
  `X-Permitted-Cross-Domain-Policies`, `Clear-Site-Data`); a missing Strict-Transport-Security on
  a plain-HTTP origin (browsers ignore HSTS there); and a passive template candidate
  (`git-config`, `git-credentials-disclosure`, `web-config`, `openapi`) at the URL of a verified
  exposure of the same class, which is now evidence on that exposure finding. Rows of these kinds
  stored by earlier Scans are retired by the first completed 2.11 Scan of the target, and only
  where that Scan proved them covered: header rows on the origin whose response headers its
  baseline request recorded (a superseded duplicate only for a header the baseline reported
  missing there, and only while that baseline finding is stored for the Scan, active or triaged),
  template rows only at the origin and path of an exposure the Scan verified and stored, of a class
  the template detects and at no higher severity. A Scan whose final coverage is not complete
  (including one the action receipts downgraded to partial) or whose findings did not all save
  retires nothing. Rows anyone touched are never retired: a status other than active (accepted
  risk, false positive), an analyst verdict, notes or verdict notes, an approved or active
  exception, a retest queued or running, or a retired row an operator set back to active; nor is
  a row the Scan reported again. A row an operator reopened from any other resolved state carries
  no durable marker and can be retired; add a note or a verdict to keep it. **A retired row is
  labelled "Retired by release 2.11.0: <reason>", not counted as a fix**: it is `resolved` with
  `resolution_reason: retired_by_release` and a `retirement` record on the list and detail, and
  the Exposure "Findings resolved" digest, the Hunt knowledge "what changed" resolved count and
  the findings `resolved_within_days` filter leave it out. It keeps an `evidence.retired` record
  naming the kind, the reason, the release, the Scan and what superseded it, and reopens if a
  later Scan reports it again. In a mixed fleet, a pre-2.11 node's Scan still reports the
  duplicates beside the finding that superseded them; the control plane does not save those
  duplicates, so they neither reopen a retired row nor keep one from retiring. At most 5,000 rows
  are retired per Scan; a larger backlog clears over the next Scans.

## Detection

* **Active templates rotate across endpoints.** An active Nuclei attempt (one endpoint, 120
  requests, 45 s) used to be handed the whole active allowlist and, stopped by its wall, ran the same
  \~25 templates on every endpoint: about 97% of the 1,146 permitted active templates never ran.
  Each endpoint attempt now runs one window of 30 templates (a cross-section of the allowlist, picked
  by the endpoint's manifest position) that finishes within the attempt. A Scan with at least 39
  endpoints in its active manifest runs every permitted template once; one with fewer runs that many
  windows. Each attempt records its `template_window`. A given template no longer runs on every
  endpoint; most default-login and exposure templates address the host, not the endpoint path.
  The trade-off: a template that tests the endpoint's own path or parameters runs on about one
  endpoint in 39, so a Scan is not an exhaustive template-by-endpoint sweep, and a Scan with one
  endpoint runs 30 of the permitted templates. The window depends only on the endpoint's manifest
  position, so rescanning the same manifest runs the same windows rather than more templates.

## Discovered secrets: trying one on another login, and the agent may see them

* **A secret found on one target can be tried on another target's login, only when a person
  allows it.** The Hunt asks (a `secret.try` permission request; `shakerscan approve <id>`); the
  grant covers one secret on one login (origin, path, method) for that Hunt, 3 attempts unless the
  person chooses 1-10, and at most 3 tries per 15 minutes on one host across Hunts. Every attempt
  is recorded with the login's answer (`discovered_secret_uses.permission_grant_id`, `outcome`).
  No start bound covers it, it is never remembered, and only related targets' secrets (same root
  domain or host asset) can be asked about. A request carrying a discovered secret follows no
  redirect. Shared logins can be excluded outright
  (`SHAKERSCAN_SECRET_TRY_DENY=host/path,...`; `honey.shakerscan.com/hub` is built in).
* **"Agent may see found secrets", per Hunt, off by default.** `shakerscan hunt start --agent-may-see-secrets` (`secret_visibility: agent_may_see` on `POST /hunts`) lets that Hunt's
  `secrets.inspect` answer carry the values of secrets found on that target. It is accepted only
  with the person who chose it (`allow_asserted_by`: a step-up, or `local` on a loopback-only
  install, where anyone with local API access, a local agent included, can assert it); the MCP
  start tool drops it; each value shown is recorded as a reveal; stored actions, receipts, records,
  logs and the retry store never hold the value. A connected instance refuses the option until its
  gateway records the person's step-up for it.
* **Schema.** The permission kind constraint gains `secret.try` (replaced once on the first 2.11 start,
  a short lock on two small tables; later starts take no lock) and `discovered_secret_uses` gains two nullable
  columns.

## Recon

* **Corporate surface.** Scan reports list mail portals, identity providers, VPN gateways, edge
  appliances, CI servers and administration consoles the HTTP evidence names, under
  `discovery.corporate_surface`, as unconfirmed leads. An exactly reported version feeds the
  service view's advisory candidates; known-exploited matches remain candidates.
* **Content discovery with active authority** reads the common list and then a corporate list
  (mail, identity, VPN and console paths) in the same reservation.
* **Active recon family (`recon_active`).** Select it explicitly (it is in no preset) with active
  testing and the target's authorization: DNS name enumeration and virtual-host discovery on the
  target's own address, both only when the target is the apex domain itself (or its `www` name)
  and subdomain discovery is enabled, and parameter discovery on observed routes. A target below
  someone else's domain (`acme.vendor.example`) never has names guessed under that domain, and
  virtual-host discovery does not run on a CDN or shared hosting edge. Routes whose names are
  actions (log out, delete...) or carry token-like segments are not probed for parameters. Bundled authored lists, wildcard, catch-all and
  random-parameter controls, metered and cancellable. Results are leads; enumerated names are
  recorded as discovered targets for their own authorized Scans.
* **Discovered names keep their provenance.** A name a discovery run adds records which sources
  named it, whether only a certificate did (historical issuance, not current ownership), its DNS
  status, and that no service was checked. Runs report names that were already targets, and a
  completed run with a failed source is marked partial.

## Fixes found in acceptance

* **macOS local builds with Docker Compose 5.6.** Compose 5.6 refuses host-network builds, which
  the launcher used on macOS, so `build`, `rebuild` and a first `start` from a checkout failed. The
  launcher now builds on the default network where Compose refuses host networking (an explicit
  `SHAKERSCAN_BUILD_NETWORK` is never changed). On such a Compose, macOS builds no longer route
  through a host VPN.
* **Content discovery reports 303 and 308 answers.** A path answering with a redirect such as a
  login page (`303 See Other`) was dropped before its result was read.

## Known limitations

* **SQLi rounds take candidates in rank order.** A slow, high-ranked request (several seconds per
  response) takes each SQLi round's share first, so lower-ranked candidates are tested in a later
  round rather than early. Every selected candidate is still tested within the Scan's budget, or
  reported inconclusive for budget.

* **Interrupted examination stays partial.** Cancellation and authorization withdrawal keep
  settled findings and available checkpoints; they do not establish the outcome of unfinished
  probes. A cancelled Scan is distinct from a completed Scan.

* **The dashboard can be up to 15 seconds old.** Its counts are cached per API process.

* **An install not yet registered is not protected from another install's cleanup.** Installs
  register themselves when they first start with a 2.11 or later launcher; an install that has not
  (other than the default `~/.shakerscan`), or one under another user account, is not visible to
  this install's image cleanup and pulls its images again if they were removed.

* **Redis reset and running Scans.** A Scan that was running when Redis was reset is marked with an
  unknown outcome and is not restarted automatically; start it again from the Scans page.

* **Browser captures do not promote credentials.** Cookies, authorization headers and passwords
  recorded by `shakerscan record` stay in the encrypted capture and are left out of every replay;
  Scans and Hunts authenticate with the target's credential profiles. Only the target's exact origin is captured, and a capture
  open with no batch for 30 minutes is closed by the server. On a rollback to 2.10.x a capture is
  listed as a collection of format `capture` and cannot take new batches.

* **Rollback.** Rolling back to 2.10.x keeps the 2.11 database additions (the stored severity order,
  the count tables and their triggers, new indexes); 2.10.x ignores them. A rollback launcher
  older than 2.11 does not read `.release-images.previous.2` and later records.

* **Authenticated assurance stays a fail-closed preview.** A Scan cannot select a reviewed
  authenticated-assurance profile revision (`scan_selection: unsupported`), credential-consuming
  Scan capabilities are not certified for it, and no product-level authenticated Scan runs in CI.
  Authenticated Scans use credential profiles as before.

* **A Hunt proves cross-principal access only with two managed sessions.** `authz.verify` needs two
  session references from `auth.session.establish` (interactive login or OAuth profiles); static
  header or cookie profiles cannot yet serve as the two principals, and a profile's
  `principal_slot` must match the role it is selected for.

* **Active recon is Scan-only.** Hunts do not yet have the `recon_active` capabilities.
