# ShakerScan 2.10.0

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

The client for this release is `shakerscan` 0.10.0 (`shakerscan secrets`).

## Exposure is the service view

The Exposure page now shows only its service view: per target, each listener, the product and
version observed on it, and the weaknesses known for that version. The **Triage**, **Map** and
**Attack paths** views are removed: they restated what the Targets and Findings pages show, and
their rankings were not decisions an operator could act on. An old `/exposure?lens=...` link opens
the service view.

* **Editing a target's owner, environment and reporting cohort moved to the target page**
  (`/targets/{id}`, **Edit details**). It was only possible from the removed triage drawer. The
  target detail API (`GET /targets/{id}/asset`) now returns `owner` and `cohort`.
* **The dashboard's "What changed" links** for new and stale assets open the Targets page (newest
  first, or scanned targets) instead of the removed triage view; a domain scope becomes the Targets
  search.
* **API**: `GET /exposure/nodes` and `GET /exposure/attack-paths` are removed (only the removed
  views called them). `GET /exposure/graph` (an Arsenal read for agents), `GET /exposure/assets`
  (the dashboard's posture), `GET /exposure/changes` and `GET /exposure/services` are unchanged.

### Known vulnerabilities per service version

* **A real vulnerability snapshot.** The pinned snapshot the service view matches against now
  covers the network and web services the scanner fingerprints (OpenSSH, Apache httpd, nginx, IIS,
  Tomcat, PHP, MySQL/MariaDB, PostgreSQL, Redis, Elasticsearch, Jenkins, mail, DNS and file
  servers, common edge appliances, and the embedded products it already covered), generated from
  NVD at release time. Before, it held 24 curated records, so almost every listener showed no
  candidates.
* **Exploitation context on each candidate**: the CVSS base score, whether CISA lists it as known
  exploited (with the date added and ransomware use), the EPSS likelihood of exploitation in the next
  30 days, and the nuclei checks this release ships for that CVE. Candidates are ordered known
  exploited first, then likelihood, then severity, and each listener shows how many are known
  exploited. All of it is metadata in the pinned snapshot: nothing is fetched at run time and
  nothing is executed from it.
* **A product named without a CPE is matched** when its version is known (for example `nginx
  1.18.0` from HTTP fingerprinting); a product with no usable version is not.
* **Less noise**: listeners whose probe got no answer and that have nothing linked are folded
  behind "show N inconclusive" on each target.
* A version match is still a **candidate**, never a finding: confirm the exact build and its
  backports, or run the check as part of an authorized Scan.

## Model Intake is an optional module

Model Intake (scanning AI models before deployment) ships as two images of about 4.8 GB per
release, more than half of what an install downloads. **A new install now leaves it off**; an
existing install keeps it exactly as it was (an install that predates the setting keeps Model
Intake on, and nothing is removed by the upgrade).

* `shakerscan model-intake enable` pulls its two images (from the same verified release lock) and
  starts its services; `shakerscan model-intake disable` stops and removes them (`--remove-images`
  also deletes its images); `shakerscan model-intake status` shows the setting. The setting is
  `SHAKERSCAN_MODEL_INTAKE_ENABLED` in `~/.shakerscan/.env`.
* With the module off, the Model Intake page explains what it does and shows the command to install
  it, and makes no Model Intake request. Its sidebar item is under **Show all sidebar sections**.
* The API refuses changes to Model Intake (`409`, `reason_code: module_not_installed`, naming the
  enable command); existing Model Intake records stay readable. `/health` reports
  `model_intake_worker: {"enabled": false, "status": "disabled"}` instead of a missing worker.
* Compose: the four Model Intake services are in the `model-intake` profile; nothing else depends on
  them.

## Hunt can use secrets found earlier on a target

A value a Hunt withholds from its output (a password in a configuration file, an API key in a
response) used to be usable only in that Hunt, for at most 24 hours. It is now also kept for the
target, so a later Hunt can use it too, still without ever seeing it.

* **Kept per target, encrypted** under the same key as credential profiles, once per value (repeat
  sightings update when it was last seen), with a 30-day expiry that a new sighting extends. Scans
  keep the secrets they prove as well: a verified exposure (an `.env` or other configuration file, a
  credential store, a SQL dump) keeps the values it found.
* **Used by reference.** `secrets.inspect` lists a target's usable secrets for the Hunt (reference,
  kind, field name, length, when seen; never the value), and an `http.request` binding takes
  `{"secret_ref": "secret://<id>"}` beside `withheld_ref`. The worker sends the value; every later
  response of that Hunt that echoes it has it withheld. A reference resolves only for a live Hunt of
  the secret's own target while it is active and unexpired; another target, a revoked or expired
  secret, or a malformed reference is refused with a reason code and no value. Every use is recorded
  with the action and the destination host.
* **Operator routes**: `GET /targets/{id}/discovered-secrets` (metadata only),
  `POST /targets/{id}/discovered-secrets/{secret}/state` (revoke or reactivate),
  `POST /targets/{id}/discovered-secrets/{secret}/expiry` (1–365 days) and
  `DELETE /targets/{id}/discovered-secrets` (purge). No route returns a value, and a Hunt planner
  cannot use them. An operator's revocation or expiry stands: later sightings do not undo it.
* **Granting to another target**: an operator can let Hunts of another target use a secret
  (`POST .../{secret}/grants {"target_id": ...}`, `DELETE .../grants/{target}`). Only the target it
  was found on grants it, at most 50 targets per secret; a grant never widens a Hunt's scope (the
  value is sent only where that Hunt may already go).
* **Promoting and revealing**: `POST .../{secret}/promote` moves the value into a new credential
  profile of the target (validated like any typed profile; the discovered secret becomes
  `superseded` and points at it). `POST .../{secret}/reveal` shows the value to a person
  (`cache-control: no-store`). Both are recorded with who did it, apart from Hunt uses. Behind the
  Enterprise gateway, revealing, granting and promoting each need the person's step-up for that one
  decision.
* **In the Hunt briefing**: the live sections say how many usable secrets the target holds, how many
  were never tried, by kind, and how to list and use them.
* **Terminal**: `shakerscan secrets list <target> [--usable]`, `revoke`, `reactivate`, `expiry --days`, `ungrant --to`, and `grant --to`, `promote --name --auth-kind`, `reveal`, the last three
  decided in your own terminal (a keypress on an open-source engine, your step-up on Enterprise).
* **Target page**: a Discovered secrets card lists them (never a value) with the same actions; a
  revealed value is shown in its dialog only. Behind the Enterprise gateway the card names the
  terminal command for a step-up decision.
* **Lifecycle**: an hourly sweep marks expired secrets and deletes their ciphertext 30 days after
  they stopped being usable; a target keeps at most 2,000. Deleting a Hunt record deletes the secrets
  it found (use records stay); deleting a target deletes its secrets; merging duplicate targets moves
  them to the surviving target.

## Secrets no longer stay in finding evidence

The JavaScript-secrets and API data-exposure checks of the standalone scanner (`scanner.py`, its
CLI and `--server` mode) put the matched text into a finding's evidence: 100 characters around a
JavaScript secret (the whole value), or the raw response snippet. The JavaScript context now shows
the value only as its preview (`AIza...4567`) with any other token-like run near it masked, and the
data-exposure snippet keeps no response text (the finding family, the match's length and offset).

As a second line, before the worker writes a Scan's result anywhere (the result artifact, a scan's
stored result, the findings table, a fleet node's posted result), every self-evident provider secret
still in a finding's evidence (AWS, Stripe, Slack, GitHub, npm and SendGrid keys, credentialed
database URLs, password hashes) is kept for the target as above and replaced by its `secret://`
reference, or by `[REDACTED:<kind>]` when it cannot be kept.

## Upgrades look after themselves

An upgrade on a host short of disk space could leave Redis unable to start ("dependency failed to
start: redis is unhealthy") and its operator choosing Docker volumes to delete. The launcher now
handles it, and never asks you to manage Docker:

* **Disk space first.** Before it downloads or stops anything, `shakerscan start` checks that the
  images this release still needs (plus room for the databases) fit in Docker's storage. If not, it
  removes ShakerScan release images that no container uses (running or stopped), never a volume,
  never another product's image, never this install's current or previous release (kept for
  rollback), and, if that is still not enough, stops with the numbers and changes nothing. A start with nothing to download
  never needs this room. Docker Desktop, whose disk the host cannot see, skips the check.
* **The database is saved before an upgrade.** A start that changes the release dumps PostgreSQL to
  `~/.shakerscan/backups/pre-upgrade-<time>-<from>-to-<to>/` first (the newest two are kept;
  `SHAKERSCAN_PRE_UPGRADE_BACKUP=0` turns this off).
* **Redis is repaired, not deleted.** Redis must be healthy before anything that depends on it
  starts. A damaged append-only file (an interrupted write, typically a full disk) is copied to
  `backups/redis-<time>/` and repaired; a Redis that cannot write gets room from old images. Only if
  repair fails does it start empty, which loses queues and job status only: scans, findings and
  evidence are in PostgreSQL. The API and workers are then restarted onto it. A Redis that is still
  loading is waited for; one that fails for a reason the launcher does not recognise is left as it
  is, with its last messages.
* **Old images go after an upgrade.** After a successful start, this install's own ShakerScan
  images of releases older than the previous one are removed. Images it never recorded stay, and so
  do third-party images (PostgreSQL, Redis) and an optional module's images while it is off
  (`SHAKERSCAN_PRUNE_OLD_IMAGES=0` keeps them all). A second, stopped install on the same host that
  still uses one of those old releases pulls its images again when it starts.
* **Warnings ahead of time.** `shakerscan status` and `doctor` warn when Docker has less than 20 GB
  free, or when the database volume is missing on an install that has run before. Nothing in
  ShakerScan suggests `docker system prune --volumes`, which deletes the database.
* **Scans lost from the queue run.** A Scan that was waiting when Redis lost its queue used to stay
  "pending" forever. One that provably never started (no start, no worker, no step begun) is now
  queued again from its saved job, with the routing it was first queued with, within about ten
  minutes, at most three times. A Scan that may have run keeps the "outcome unknown" flag, so
  nothing runs twice.

## Rolling back to 2.9.x

Nothing this release writes is read differently by 2.9.x: the owner and cohort were already
stored in the target's metadata, and 2.9.x ignores the `discovered_secrets`,
`discovered_secret_uses` and `discovered_secret_grants` tables (they stay, unused, until 2.10 runs again). A finding's evidence
that holds a `[secret://...]` reference shows it as text. A 2.9.x launcher ignores `SHAKERSCAN_MODEL_INTAKE_ENABLED` and
starts Model Intake again (pulling its images if they were removed).
