Continuous scanning

Keep a target under watch so a new advisory reaches you without a rescan.

A scan is a photograph. Most vulnerabilities arrive after the shutter closes — the code did not change, the advisory did. This tutorial sets up a watch so that matters.

You should have completed the first tutorial.

Start a watch#

The scan pipeline
Ingest, resolve, match, attest — the four stages a watch run repeats.
ztvs watch ./my-project --interval 6h

ZTVS resolves the target once, then re-evaluates it against the advisory database on each interval. It does not re-resolve the filesystem unless it changed — re-matching is cheap, re-resolving is not.

  watching ./my-project · interval 6h
  [04:00] advisories refreshed · no change
  [10:00] advisories refreshed · 1 new finding
          HIGH  ZTVS-2026-0052  serde_json 1.0.114 → 1.0.121

Get told about it#

Polling a terminal is not a notification. Point it at a webhook:

ztvs watch ./my-project \
  --interval 6h \
  --on-finding webhook \
  --webhook-url https://hooks.example.com/ztvs

Only new findings fire the hook. A finding you have already seen is not news.

Run it as a service#

# /etc/systemd/system/ztvs-watch.service
[Service]
ExecStart=/usr/local/bin/ztvs watch /srv/api --interval 6h --on-finding webhook
Restart=on-failure
User=ztvs

Run the watcher as an unprivileged user with read-only access to the target. It needs to read the artefact and reach the advisory database. Nothing else.

How it decides something is new#

noyesyesnoyesnoadvisory refreshmatches target?no actionseen before?suppressed by policy?record, no alertalertnoyesyesnoyesnoadvisory refreshmatches target?no actionseen before?suppressed by policy?record, no alertalert

Suppressed findings are still recorded. The difference between suppressed and hidden is the difference between a decision and a lie.