ShakerScan 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.
  • 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.

This page is rendered from docs/releases/2.11.0.md in the open-source repository at v2.11.0. When documentation and implementation disagree, the code, database schema, and tests at that tag are authoritative.