From aec0d1b4eeab64c2c4261b870f1111ec2af615db Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:39:49 +0000 Subject: [PATCH 01/16] Start squad working record for issue 372 --- specs/issue-372/log.md | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 specs/issue-372/log.md diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md new file mode 100644 index 00000000..2a13999c --- /dev/null +++ b/specs/issue-372/log.md @@ -0,0 +1,8 @@ +# Log: issue #372 + +One line per pipeline step: date and time of day (UTC), step, member, result. Lead decisions and escalations +are quoted in full. + +| Date | Step | Member | Result | +| ---- | ---- | ------ | ------ | +| 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | From 291441e2cf6f7b8502b2bbaf42253692e4ccc59b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:44:53 +0000 Subject: [PATCH 02/16] Add plan for issue 372 --- specs/issue-372/log.md | 1 + specs/issue-372/plan.md | 163 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 164 insertions(+) create mode 100644 specs/issue-372/plan.md diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 2a13999c..bd200635 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -6,3 +6,4 @@ are quoted in full. | Date | Step | Member | Result | | ---- | ---- | ------ | ------ | | 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | +| 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | diff --git a/specs/issue-372/plan.md b/specs/issue-372/plan.md new file mode 100644 index 00000000..4b15ee5e --- /dev/null +++ b/specs/issue-372/plan.md @@ -0,0 +1,163 @@ +# Plan: Bump bundled Trivy from 0.58.1 to the measured v0.76.1 + +Source: Issue #372 +Status: Draft +Tier: standard — the bump replaces the bundled Trivy CLI whose argument parsing, `TRIVY_SERVER` handling and error text the provider relies on, so behavior can change without a code change; no security control is created, changed or removed (the `--` guard of 0015 stays as built, the bump makes the measured binary the bundled one). +Security areas touched: Container image and runtime user (`src/DockerUpdateGuard/Dockerfile`, the Trivy CLI stage); External process execution (the binary `TrivyVulnerabilityProvider` starts and the guard of record 0015 rests on); Docker/build configuration; a dependency (the bundled Trivy CLI, and the Trivy server image of the documented deployment tiers) + +## Problem / root cause + +`src/DockerUpdateGuard/Dockerfile:4` defaults `TRIVY_IMAGE` to `aquasec/trivy:0.58.1`; line 55 copies +`/usr/local/bin/trivy` from it. Record 0015 (Consequences, last item) says the `--` protection was measured on +v0.76.1 only, so the image ships a binary nobody measured. `docs/docker-compose.trivy.yml:28` and +`docs/docker-compose.full.yml:30` run the Trivy server at 0.58.1 as well, so a client bump alone would pair a +v0.76.1 client with a v0.58.1 server in the documented deployment tiers. + +## Verified facts + +| # | Claim | Evidence | Status | +| - | ----- | ------ | ------ | +| 1 | The Dockerfile pins `aquasec/trivy:0.58.1` on line 4 | `src/DockerUpdateGuard/Dockerfile:4`, `ARG TRIVY_IMAGE="aquasec/trivy:0.58.1"` | confirmed | +| 2 | v0.76.1 is the current release | Docker Hub API `GET /v2/repositories/aquasec/trivy/tags?ordering=last_updated`: `0.76.1` (2026-10-21) and `latest` share digest `sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c`; only `canary` is newer | confirmed | +| 3 | That digest is the OCI image index of `0.76.1`, with `linux/amd64` manifest `sha256:2e6c00d711ab4ae41d8a30c2c1fd3bd3b62704c177bb120f95012e939edfb3df` and `linux/arm64` | Docker Hub API `GET /v2/repositories/aquasec/trivy/tags/0.76.1`: `media_type` `application/vnd.oci.image.index.v1+json`, `images[]` | confirmed | +| 4 | The pinned image can be pulled | to verify: registry pull of `aquasec/trivy:0.76.1@sha256:790fd66d…` (manifest, `linux/amd64` manifest, layers), Dev, step 6. Not settled here: no Docker daemon in the session, and `registry-1.docker.io` answered HTTP 429 to an anonymous manifest `HEAD` | unverified | +| 5 | The image holds the binary at `/usr/local/bin/trivy` (the path line 55 copies), reports version 0.76.1 and is statically linked (it runs on the Alpine runtime stage) | to verify: layer listing, `trivy --version`, `file`/`readelf -l` (no `INTERP`), Dev, step 6 | unverified | +| 6 | The #367 cases behave on the bundled binary as record 0015 states for v0.76.1 | to verify: the cases of AC3, Dev, step 6; 0015 measured v0.76.1 from another source (the release archive), not from the image | unverified | +| 7 | v0.76.1 accepts the provider's fixed options (`image --format json --quiet --scanners vuln --pkg-types os,library --timeout s`) and reads `TRIVY_SERVER` (no own database download) | to verify: AC3 cases, with the list `BuildArguments` builds (`TrivyVulnerabilityProvider.cs:246-263`) (no `unknown flag`, no database download in `TRIVY_CACHE_DIR`), Dev, step 6 | unverified | +| 8 | v0.76.1's error output for a missing tag still matches the provider's *unavailable* detection (`MANIFEST_UNKNOWN`, `manifest unknown`, or `manifest` and `not found`) | to verify: AC3 case f against the detection in `docs/areas/vulnerabilities.md#trivy`, Dev, step 6 | unverified | +| 9 | `trivy server --listen` and `--cache-dir` (bound to `TRIVY_CACHE_DIR`, Dockerfile line 56) exist in v0.76.1 | to verify: `trivy server --help` and `trivy image --help` of the extracted binary, Dev, step 6 | unverified | +| 10 | Records 0015 and 0005 are unreleased, so they are edited in place | `git tag --contains` of the commits that added them (`1ea22b3` for 0015, `d822166` for 0005) prints nothing; newest tag `v1.3.7` | confirmed | +| 11 | The release build passes no `TRIVY_IMAGE` build argument, so the Dockerfile default is what ships | `.github/workflows/release.yml:85-88` (`APP_VERSION`, `BASE_RUNTIME`, `BASE_RUNTIME_DIGEST` only) | confirmed | + +## Acceptance criteria + +- [ ] AC1: `src/DockerUpdateGuard/Dockerfile` line 4 reads exactly + `ARG TRIVY_IMAGE="aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c"`, + no other Dockerfile line changes, and that reference can be pulled: the registry serves the index at that digest + and its `linux/amd64` manifest and layers. +- [ ] AC2: the `linux/amd64` image of AC1 contains `usr/local/bin/trivy`; the extracted binary prints version + `0.76.1` and is statically linked. +- [ ] AC3: the extracted binary, run with the provider's fixed options, `TRIVY_SERVER=http://127.0.0.1:9` + (closed port), `TRIVY_CACHE_DIR` set to an empty scratch directory and a scratch working directory: + - a. without `--`, target `--config=x.y/z:latest`: Trivy reads it as its configuration file option (error output + about loading the configuration file), non-zero exit; + - b–d. with `--` before `--config=x.y/z:latest`, `--input=/tmp/x.tar:latest` and `-a/b:latest`: each is treated as an + image name (error output about the image or its reference, not about a configuration file or archive), non-zero + exit, and the file the option would name (`x.y/z:latest` in the working directory, `/tmp/x.tar:latest`) is never + opened; + - e. `-- library/alpine:3.20`: the normal scan path — no option error, no vulnerability database download (no + `db/` under `TRIVY_CACHE_DIR`, no download message), non-zero exit at the unreachable server, or at the image + pull when the registry refuses the session; + - f. `-- library/alpine:0.0.0-does-not-exist`: error output that the provider's *unavailable* detection matches. + No run prints `unknown flag`. +- [ ] AC4: `docs/docker-compose.trivy.yml` and `docs/docker-compose.full.yml` run `aquasec/trivy:0.76.1`, and + `trivy server --help` of the extracted binary lists `--listen`; `trivy image --help` lists `--cache-dir`. +- [ ] AC5: no `0.58.1` remains outside `docs/decisions/` and the measured history in + `docs/areas/vulnerabilities.md` (line 111) — `git grep -n "0\.58\.1" -- ':!docs/decisions' ':!specs'` prints only that line; + records 0015 and 0005 name the bundled v0.76.1 as measured (wording under *Decision records*). + +## Tests per criterion + +None: no production or test code changes; every criterion is verified under *Verification without tests*. + +## Verification without tests + +Steps 4 (*Skeleton*), 5 (*Tests first*) and the *Coverage gate* are not applicable (*Changes without production +or test code*). What can be checked in this session: the Dockerfile and compose edits, hadolint, the *Image build +check*'s restore-order reproduction, and the binary measurements, provided the registry serves the image over HTTPS +(no Docker daemon is running, so `docker pull`/`docker build` are not possible). Not checkable here: a real +`docker build` and a scan against a live Trivy server; the first runs in CI's release build. + +| AC | Where, by whom | +| -- | -------------- | +| AC1 | Dev, step 6: fetch the index by digest from `registry-1.docker.io` (anonymous token from `auth.docker.io`, scope `repository:aquasec/trivy:pull`), check its `Docker-Content-Digest` equals the pinned digest and that it lists the `linux/amd64` manifest of fact 3, then fetch that manifest and its layers. On HTTP 429 retry later or use `mirror.gcr.io/aquasec/trivy` with the same digest (content-addressed, so the bytes are the same). Downloads go into their own new, empty scratch directory outside the repository. Then the *Analyzer gate* with `--lint-only` (hadolint on the whole Dockerfile; the `# hadolint ignore=DL3018` line stays) and the *Image build check* (restore-order reproduction, since Docker does not run). If neither registry serves the image, the Dev stops and reports it; the orchestrator launches the Lead (`decide`) — the pin is not merged unmeasured. | +| AC2 | Dev, step 6: list the layers, extract `usr/local/bin/trivy`, run `trivy --version`, `file` and `readelf -l` on it. | +| AC3 | Dev, step 6: each case run under `strace -f -e trace=openat,open,stat,newfstatat` (installed in the session) with the exact argument list of `BuildArguments` (`TrivyVulnerabilityProvider.cs:246-263`) and `--timeout 30s`; report per case the command, the exit code, the last error line and whether the trace names `x.y/z:latest` or `/tmp/x.tar:latest`. Case a is the control: its trace must show the configuration file being looked up, so an empty trace in b–d is meaningful. A case whose outcome differs from AC3 stops the change and goes to the Lead (`decide`); case f alone may be reported "not measured" when the registry refuses the session, which the Lead then decides. | +| AC4 | Dev, step 6: the edits, and the two `--help` runs of AC2's binary. | +| AC5 | Dev, step 6: the `git grep`; Lead, record edits after the Dev's measurement report (below). Reviewer, step 8: reads both. | + +## Approach + +1. Dockerfile line 4 as AC1: tag and digest, so the build gets exactly the measured binary, even if the tag is moved. +2. Both compose files: `image: aquasec/trivy:0.76.1` (tag only, like the other images there; operators maintain it). +3. The Dev measures AC1–AC4 and reports the raw results; then the orchestrator launches the Lead (`revise`) to apply + the record wording below, adjusted only where a measurement differs within what AC3 allows (case e's stopping + point, case f "not measured"). + +No new user-visible output. + +## Options considered + +1. **Tag and digest in the Dockerfile** — chosen: the measurement binds to the bytes the build copies; a tag is + mutable. +2. **Tag only** (`aquasec/trivy:0.76.1`) — lost: a re-pushed tag would ship a binary nobody measured, which is the + gap this issue closes. Updating needs a digest edit as well; Dependabot covers no Docker ecosystem here + (`.github/dependabot.yml`: NuGet and actions only), so either form is updated by hand. +3. **Keep 0.58.1 and measure it from its image** — lost: the issue asks for a current release, and the old binary + stays a year behind on fixes. +4. **Bump the client only, leave the compose servers at 0.58.1** — lost: a v0.76.1 client against a v0.58.1 server + was not measured, and this plan does not measure it; the documented tiers keep client and server at one version. + +## Affected projects and types + +| Project | Type / file | Change | +| ------- | ----------- | ------ | +| `src/DockerUpdateGuard` | `Dockerfile` | line 4, `TRIVY_IMAGE` default (AC1) | +| docs | `docs/docker-compose.trivy.yml`, `docs/docker-compose.full.yml` | Trivy server image (AC4) | + +## Signatures (for the Dev's skeleton) + +none + +## Test files + +None. Existing test call sites affected: none. Files no unit test can cover: none (no code changes; the Dockerfile +and compose files are not measured for coverage). + +## Areas + +- `docs/areas/hosting.md`: the bundled Trivy version and its pinning. +- `docs/areas/vulnerabilities.md`: none — the argument list and error handling are unchanged; line 111's "measured + with Trivy v0.58.1" is the measured reason for a rule that holds for every version and stays. + +## Documentation updates + +Sweep: `git grep -n "0\.58\|aquasec"` over the repository; `docs/ARCHITECTURE.md` (line 476), `README.md`, +`INSTALL.md`, `.squad/project.md` and `.squad/stack.md` name no Trivy version and stay. + +- `docs/areas/hosting.md` line 138 (Dev): the bundled CLI is taken from `aquasec/trivy:0.76.1`, pinned by digest, by + default; the deployment tiers run the Trivy server of the same version. +- `docs/docker-compose.trivy.yml:28`, `docs/docker-compose.full.yml:30` (Dev): as *Approach* 2. + +## Architecture check + +No guarantee of `.squad/project.md` is touched; the composition, the process boundary and the client-mode design of +`docs/ARCHITECTURE.md` (lines 230, 476) stay. No `docs/ARCHITECTURE.md` update. + +## Security considerations + +- [x] No limits: no parsed input changes. +- [x] None: no exception path changes. +- The guard of record 0015 depends on the binary, so it is measured on exactly the bytes the build copies (AC1–AC3), + and the change fails closed: an unmeasurable or deviating binary stops it before review. + +## Decision records + +No new record (a dependency change without a guarantee or architecture change, README *Rules*). Two unreleased +records are edited in place by the Lead after the Dev's report: + +- `docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md`, *Consequences*, last item: + "The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1, the version the image bundles + (pinned by tag and digest, so the build copies the measured binary): without `--`, `--config=x.y/z:latest` is read + as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image names + and fail without opening the file they name, and a normal target takes the normal scan path. Any later Trivy update + is measured again with the same cases." +- `docs/decisions/0005-urls-are-output-with-user-info-masked.md` line 118: "the bundled Trivy v0.58.1 reads it" → + "the bundled Trivy v0.76.1 reads it" (AC3 case e). Line 24's and 32's v0.58.1 measurements stay: they are the + reason the rules exist. + +## Out of scope / follow-ups + +- Re-measuring the credential repetition in Trivy's error output (record 0005, area line 111) on v0.76.1: the output + is withheld for every version, so the result changes no behavior. +- Pinning the other base images by digest. From df32c52aa2204d467f9e27ad315e2e27876f931f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:47:42 +0000 Subject: [PATCH 03/16] Log plan security review for issue 372 --- specs/issue-372/log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index bd200635..b0afbabd 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -7,3 +7,4 @@ are quoted in full. | ---- | ---- | ------ | ------ | | 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | | 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | +| 2026-10-29 20:47 | 3 Plan security review | Security | CHANGES_REQUIRED: B1 Trivy 0.76.1 phones home (check.trivy.dev) on every scan; needs suppression + pinning criterion; N1 measurement also calls home (opus/medium · 56,081 tokens · 16 tool uses · 137 s) | From 3ebd870c96e42f86a7a0a89eb762e8443a8ed8fe Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:50:05 +0000 Subject: [PATCH 04/16] Revise plan for issue 372 --- ...vy-skips-its-update-check-and-telemetry.md | 46 ++++++++++++ docs/decisions/README.md | 1 + specs/issue-372/log.md | 1 + specs/issue-372/plan.md | 74 +++++++++++++++---- 4 files changed, 109 insertions(+), 13 deletions(-) create mode 100644 docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md diff --git a/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md new file mode 100644 index 00000000..269ba4aa --- /dev/null +++ b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md @@ -0,0 +1,46 @@ +# 0016: The bundled Trivy skips its update check and telemetry + +- **Status:** Proposed +- **Date:** 2026-10-29 +- **Area:** Hosting and configuration +- **Source:** Issue #372 +- **Supersedes:** — + +## Context + +The image bundles the Trivy CLI, which the Trivy provider starts for every scan in client mode against the operator's +Trivy server ([Vulnerabilities](../areas/vulnerabilities.md#trivy)). Trivy v0.76.1, the version issue #372 bundles, +sends on every `trivy image` run a background request to `https://check.trivy.dev/updates` with a stable host +identifier (a SHA-256 of the host name and the first MAC address), the command, the flag names, the OS and the +architecture. `--quiet`, which the provider passes, only hides the printed notice; the request stops only when both +the version check and telemetry are switched off. v0.58.1, the version bundled before, makes no such call. A +deployment with restricted egress would gain an outbound destination it never allowed, and every scan would report +a host fingerprint to a third party, without anything in DockerUpdateGuard asking for it. + +## Options considered + +1. **Set `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true` in the image's environment** — no code + change; every Trivy the application starts inside the image inherits them, a custom `TrivyExecutablePath` + included, and an older Trivy ignores environment variables it does not know. Outside the image the operator + sets them. Chosen. +2. **Add `--skip-version-check --disable-telemetry` to the provider's argument list** — also covers a Trivy run + outside the image, but changes production code, its tests and the argument list of + [0015](0015-the-trivy-scan-target-follows-an-end-of-options-marker.md), and an older custom Trivy without these + flags fails every scan with `unknown flag`. +3. **Leave it to the operator** (document the call, block it by network policy) — every default deployment would + send the fingerprint until someone notices. + +## Decision + +The container image sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so the bundled Trivy +contacts only the Trivy server and the registries; the provider's arguments stay unchanged. The behavior is +described in [Hosting](../areas/hosting.md#container-image). + +## Consequences + +- An operator who sets either variable to `false` in the container gets Trivy's update notice request back; that is + the operator's choice. +- A Trivy started outside the image (the application run without the container) gets the suppression only from the + operator's environment. +- Measured on the bundled v0.76.1 with a control run (issue #372); a later Trivy update is measured again, since a + new release may add another outbound call or rename the variables. \ No newline at end of file diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 226af352..0d918d4b 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -68,4 +68,5 @@ once in the [area document](../areas/README.md) of the record's area, and the re | [0013](0013-docker-instance-keys-are-named-relative-to-the-instance.md) | Validation messages name Docker instance keys relative to the instance | Accepted | 2026-10-27 | | [0014](0014-registry-tag-names-and-digests-are-validated-where-they-are-read.md) | Remote tag names, digests, image references and names are validated or bounded where they are read | Accepted | 2026-10-28 | | [0015](0015-the-trivy-scan-target-follows-an-end-of-options-marker.md) | The Trivy scan target follows an end-of-options marker | Accepted | 2026-10-29 | +| [0016](0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md) | The bundled Trivy skips its update check and telemetry | Proposed | 2026-10-29 | diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index b0afbabd..52c3963b 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -8,3 +8,4 @@ are quoted in full. | 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | | 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | | 2026-10-29 20:47 | 3 Plan security review | Security | CHANGES_REQUIRED: B1 Trivy 0.76.1 phones home (check.trivy.dev) on every scan; needs suppression + pinning criterion; N1 measurement also calls home (opus/medium · 56,081 tokens · 16 tool uses · 137 s) | +| 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · usage pending) | diff --git a/specs/issue-372/plan.md b/specs/issue-372/plan.md index 4b15ee5e..cab661c9 100644 --- a/specs/issue-372/plan.md +++ b/specs/issue-372/plan.md @@ -2,8 +2,8 @@ Source: Issue #372 Status: Draft -Tier: standard — the bump replaces the bundled Trivy CLI whose argument parsing, `TRIVY_SERVER` handling and error text the provider relies on, so behavior can change without a code change; no security control is created, changed or removed (the `--` guard of 0015 stays as built, the bump makes the measured binary the bundled one). -Security areas touched: Container image and runtime user (`src/DockerUpdateGuard/Dockerfile`, the Trivy CLI stage); External process execution (the binary `TrivyVulnerabilityProvider` starts and the guard of record 0015 rests on); Docker/build configuration; a dependency (the bundled Trivy CLI, and the Trivy server image of the documented deployment tiers) +Tier: standard — the bump replaces the bundled Trivy CLI whose argument parsing, `TRIVY_SERVER` handling and error text the provider relies on, and switches off the new binary's update check and telemetry in the image environment; no security control is created, changed or removed (the `--` guard of 0015 stays as built; the two variables only keep a dependency from adding an outbound call, they decide nothing an attacker may reach). +Security areas touched: Container image and runtime user (`src/DockerUpdateGuard/Dockerfile`, the Trivy CLI stage and the runtime environment); External process execution (the binary `TrivyVulnerabilityProvider` starts and the guard of record 0015 rests on); Docker/build configuration; a dependency (the bundled Trivy CLI, and the Trivy server image of the documented deployment tiers) ## Problem / root cause @@ -11,7 +11,10 @@ Security areas touched: Container image and runtime user (`src/DockerUpdateGuard `/usr/local/bin/trivy` from it. Record 0015 (Consequences, last item) says the `--` protection was measured on v0.76.1 only, so the image ships a binary nobody measured. `docs/docker-compose.trivy.yml:28` and `docs/docker-compose.full.yml:30` run the Trivy server at 0.58.1 as well, so a client bump alone would pair a -v0.76.1 client with a v0.58.1 server in the documented deployment tiers. +v0.76.1 client with a v0.58.1 server in the documented deployment tiers. Unlike v0.58.1, v0.76.1 sends an update +check with a stable host identifier to `https://check.trivy.dev/updates` on every `trivy image` run unless both the +version check and telemetry are off (Security, plan review; fact 12), so the bump alone would add an outbound +destination to every deployment. ## Verified facts @@ -28,17 +31,21 @@ v0.76.1 client with a v0.58.1 server in the documented deployment tiers. | 9 | `trivy server --listen` and `--cache-dir` (bound to `TRIVY_CACHE_DIR`, Dockerfile line 56) exist in v0.76.1 | to verify: `trivy server --help` and `trivy image --help` of the extracted binary, Dev, step 6 | unverified | | 10 | Records 0015 and 0005 are unreleased, so they are edited in place | `git tag --contains` of the commits that added them (`1ea22b3` for 0015, `d822166` for 0005) prints nothing; newest tag `v1.3.7` | confirmed | | 11 | The release build passes no `TRIVY_IMAGE` build argument, so the Dockerfile default is what ships | `.github/workflows/release.yml:85-88` (`APP_VERSION`, `BASE_RUNTIME`, `BASE_RUNTIME_DIGEST` only) | confirmed | +| 12 | v0.76.1 sends `GET https://check.trivy.dev/updates` (headers `Trivy-Identifier`, `Trivy-Command`, `Trivy-Flags`, `Trivy-OS`, `Trivy-Arch`) in a background goroutine on every `trivy image` run; only `--skip-version-check` and `--disable-telemetry` together stop it; `--quiet` only hides the notice; v0.58.1 has no such call; the server is not affected | source of v0.76.1, read by Security in the plan review: `pkg/commands/artifact/run.go:143,170`, `pkg/notification/notice.go:39,54-90,57,64-67,110` | confirmed | +| 13 | v0.76.1 reads both switches from `TRIVY_SKIP_VERSION_CHECK` and `TRIVY_DISABLE_TELEMETRY` (Trivy binds each flag to `TRIVY_` plus its name) | to verify: AC6 control and final run, Dev, step 6 | unverified | +| 14 | A Trivy the provider starts inherits the application's environment, so an image `ENV` reaches it, a custom `TrivyExecutablePath` included | `ProcessRunner.cs:23-40`: `UseShellExecute = false`, only the provider's variables are set on `ProcessStartInfo.Environment`, which starts as the process environment; `docs/areas/vulnerabilities.md` line 98 | confirmed | ## Acceptance criteria - [ ] AC1: `src/DockerUpdateGuard/Dockerfile` line 4 reads exactly `ARG TRIVY_IMAGE="aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c"`, - no other Dockerfile line changes, and that reference can be pulled: the registry serves the index at that digest + no Dockerfile line changes besides it and the lines of *Approach* 2, and that reference can be pulled: the registry serves the index at that digest and its `linux/amd64` manifest and layers. - [ ] AC2: the `linux/amd64` image of AC1 contains `usr/local/bin/trivy`; the extracted binary prints version `0.76.1` and is statically linked. - [ ] AC3: the extracted binary, run with the provider's fixed options, `TRIVY_SERVER=http://127.0.0.1:9` - (closed port), `TRIVY_CACHE_DIR` set to an empty scratch directory and a scratch working directory: + (closed port), `TRIVY_SKIP_VERSION_CHECK=true`, `TRIVY_DISABLE_TELEMETRY=true` (the image's environment), + `TRIVY_CACHE_DIR` set to an empty scratch directory and a scratch working directory: - a. without `--`, target `--config=x.y/z:latest`: Trivy reads it as its configuration file option (error output about loading the configuration file), non-zero exit; - b–d. with `--` before `--config=x.y/z:latest`, `--input=/tmp/x.tar:latest` and `-a/b:latest`: each is treated as an @@ -55,6 +62,12 @@ v0.76.1 client with a v0.58.1 server in the documented deployment tiers. - [ ] AC5: no `0.58.1` remains outside `docs/decisions/` and the measured history in `docs/areas/vulnerabilities.md` (line 111) — `git grep -n "0\.58\.1" -- ':!docs/decisions' ':!specs'` prints only that line; records 0015 and 0005 name the bundled v0.76.1 as measured (wording under *Decision records*). +- [ ] AC6: the Dockerfile's runtime stage sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true` + (*Approach* 2), and with them the binary makes no request to `check.trivy.dev`: AC3 case e, run as AC3 says but + with the proxy variables unset and `GODEBUG=netdns=go+2` under `strace -f -e trace=connect`, looks up no + `check.trivy.dev` (no `hostLookupOrder(check.trivy.dev)` line in the error output) and connects only to + `127.0.0.1:9`, the DNS resolver and the registry; the same run without the two variables (control) does look up + `check.trivy.dev`. ## Tests per criterion @@ -75,12 +88,22 @@ check*'s restore-order reproduction, and the binary measurements, provided the r | AC3 | Dev, step 6: each case run under `strace -f -e trace=openat,open,stat,newfstatat` (installed in the session) with the exact argument list of `BuildArguments` (`TrivyVulnerabilityProvider.cs:246-263`) and `--timeout 30s`; report per case the command, the exit code, the last error line and whether the trace names `x.y/z:latest` or `/tmp/x.tar:latest`. Case a is the control: its trace must show the configuration file being looked up, so an empty trace in b–d is meaningful. A case whose outcome differs from AC3 stops the change and goes to the Lead (`decide`); case f alone may be reported "not measured" when the registry refuses the session, which the Lead then decides. | | AC4 | Dev, step 6: the edits, and the two `--help` runs of AC2's binary. | | AC5 | Dev, step 6: the `git grep`; Lead, record edits after the Dev's measurement report (below). Reviewer, step 8: reads both. | +| AC6 | Dev, step 6: the Dockerfile lines, then the control run first and the final run, each reported with the command, the `hostLookupOrder` lines and the `connect` addresses. Only the control may send the request; the Dev records whether it could leave the session (direct egress or not). The control makes the empty final result meaningful: a control without the lookup is repeated up to three times (the request runs in a background goroutine and may lose the race against the exit), then the change stops and goes to the Lead (`decide`), as does a final run with the lookup. | ## Approach 1. Dockerfile line 4 as AC1: tag and digest, so the build gets exactly the measured binary, even if the tag is moved. -2. Both compose files: `image: aquasec/trivy:0.76.1` (tag only, like the other images there; operators maintain it). -3. The Dev measures AC1–AC4 and reports the raw results; then the orchestrator launches the Lead (`revise`) to apply +2. Dockerfile, right after `ENV TRIVY_CACHE_DIR=/tmp/trivy` (line 58), these four lines (AC6, record 0016): + + ```dockerfile + # Trivy's update check and telemetry would send a host identifier to check.trivy.dev on every scan; + # both are off so Trivy contacts only the Trivy server and the registries. + ENV TRIVY_SKIP_VERSION_CHECK=true + ENV TRIVY_DISABLE_TELEMETRY=true + ``` +3. Both compose files: `image: aquasec/trivy:0.76.1` (tag only, like the other images there; operators maintain it). + The server makes no update call (fact 12), so they get no variables. +4. The Dev measures AC1–AC4 and AC6 and reports the raw results; then the orchestrator launches the Lead (`revise`) to apply the record wording below, adjusted only where a measurement differs within what AC3 allows (case e's stopping point, case f "not measured"). @@ -97,12 +120,15 @@ No new user-visible output. stays a year behind on fixes. 4. **Bump the client only, leave the compose servers at 0.58.1** — lost: a v0.76.1 client against a v0.58.1 server was not measured, and this plan does not measure it; the documented tiers keep client and server at one version. +5. **Update check and telemetry: image environment** (chosen), **flags in `BuildArguments`** or **left to the + operator** — weighed in record 0016: the flags change production code, tests and 0015's argument list and break + an older custom Trivy with `unknown flag`; leaving it would send a host fingerprint from every default deployment. ## Affected projects and types | Project | Type / file | Change | | ------- | ----------- | ------ | -| `src/DockerUpdateGuard` | `Dockerfile` | line 4, `TRIVY_IMAGE` default (AC1) | +| `src/DockerUpdateGuard` | `Dockerfile` | line 4, `TRIVY_IMAGE` default (AC1); two `ENV` lines after line 58 (AC6) | | docs | `docs/docker-compose.trivy.yml`, `docs/docker-compose.full.yml` | Trivy server image (AC4) | ## Signatures (for the Dev's skeleton) @@ -117,8 +143,10 @@ and compose files are not measured for coverage). ## Areas - `docs/areas/hosting.md`: the bundled Trivy version and its pinning. -- `docs/areas/vulnerabilities.md`: none — the argument list and error handling are unchanged; line 111's "measured - with Trivy v0.58.1" is the measured reason for a rule that holds for every version and stays. +- `docs/areas/hosting.md`: the image switches off Trivy's update check and telemetry. +- `docs/areas/vulnerabilities.md`: Trivy contacts only the Trivy server and the registries (pointer to Hosting); the + argument list and error handling are unchanged; line 111's "measured with Trivy v0.58.1" is the measured reason + for a rule that holds for every version and stays. ## Documentation updates @@ -126,7 +154,13 @@ Sweep: `git grep -n "0\.58\|aquasec"` over the repository; `docs/ARCHITECTURE.md `INSTALL.md`, `.squad/project.md` and `.squad/stack.md` name no Trivy version and stay. - `docs/areas/hosting.md` line 138 (Dev): the bundled CLI is taken from `aquasec/trivy:0.76.1`, pinned by digest, by - default; the deployment tiers run the Trivy server of the same version. + default; the deployment tiers run the Trivy server of the same version. Same bullet or the next: the image sets + `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so Trivy sends no update check or usage data + to `check.trivy.dev` and contacts only the Trivy server and the registries; setting either to `false` brings the + request back, and a Trivy run outside the image needs both set by the operator. Link record 0016. +- `docs/areas/vulnerabilities.md#trivy`, after the `TRIVY_SERVER` bullet (lines 97-98) (Dev): one sentence that the + Trivy process contacts only the Trivy server and the registries, the update check and telemetry being switched off + by the image ([Hosting](hosting.md#container-image)); add 0016 to the area's record list (line 195 onward). - `docs/docker-compose.trivy.yml:28`, `docs/docker-compose.full.yml:30` (Dev): as *Approach* 2. ## Architecture check @@ -140,11 +174,14 @@ No guarantee of `.squad/project.md` is touched; the composition, the process bou - [x] None: no exception path changes. - The guard of record 0015 depends on the binary, so it is measured on exactly the bytes the build copies (AC1–AC3), and the change fails closed: an unmeasurable or deviating binary stops it before review. +- v0.76.1's update check (fact 12) is switched off in the image (AC6, record 0016); a control run proves the + measurement can see the request, and a missing control or a lookup in the final run stops the change. ## Decision records -No new record (a dependency change without a guarantee or architecture change, README *Rules*). Two unreleased -records are edited in place by the Lead after the Dev's report: +New record `docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md` (Proposed, Area +Hosting and configuration, indexed): why the update check is suppressed in the image environment. The bump itself needs none. Two +unreleased records are edited in place by the Lead after the Dev's report: - `docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md`, *Consequences*, last item: "The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1, the version the image bundles @@ -161,3 +198,14 @@ records are edited in place by the Lead after the Dev's report: - Re-measuring the credential repetition in Trivy's error output (record 0005, area line 111) on v0.76.1: the output is withheld for every version, so the result changes no behavior. - Pinning the other base images by digest. + +## Security review + +- B1 (update check to `check.trivy.dev`): option a taken — `ENV TRIVY_SKIP_VERSION_CHECK=true` and + `ENV TRIVY_DISABLE_TELEMETRY=true` in the runtime stage (*Approach* 2), pinned by AC6 with a control run; why in + record 0016 (option b breaks an older custom Trivy and changes code and 0015; the environment reaches every Trivy + the application starts in the image, fact 14). Documented in `docs/areas/hosting.md` and + `docs/areas/vulnerabilities.md#trivy`. Tier stays `standard`: the variables are a dependency setting, not a guard. +- N1 (the measurements call home): every AC3 run carries both variables; only AC6's control sends the request, on + purpose, and the Dev records whether it could leave the session. +- N2: nothing to change. From 5b2c0a48aabfd08d5b1090b71b2eb94103b1580f Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:50:27 +0000 Subject: [PATCH 05/16] Log plan revision usage for issue 372 --- specs/issue-372/log.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 52c3963b..be47fac1 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -8,4 +8,4 @@ are quoted in full. | 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | | 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | | 2026-10-29 20:47 | 3 Plan security review | Security | CHANGES_REQUIRED: B1 Trivy 0.76.1 phones home (check.trivy.dev) on every scan; needs suppression + pinning criterion; N1 measurement also calls home (opus/medium · 56,081 tokens · 16 tool uses · 137 s) | -| 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · usage pending) | +| 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · 63,020 tokens · 13 tool uses · 153 s) | From 8ee534efe96b433635340a95bfd9870e937feb6b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:52:55 +0000 Subject: [PATCH 06/16] Record plan security approval for issue 372 --- specs/issue-372/log.md | 2 ++ specs/issue-372/plan.md | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index be47fac1..add8d3c0 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -9,3 +9,5 @@ are quoted in full. | 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | | 2026-10-29 20:47 | 3 Plan security review | Security | CHANGES_REQUIRED: B1 Trivy 0.76.1 phones home (check.trivy.dev) on every scan; needs suppression + pinning criterion; N1 measurement also calls home (opus/medium · 56,081 tokens · 16 tool uses · 137 s) | | 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · 63,020 tokens · 13 tool uses · 153 s) | +| 2026-10-29 20:52 | 3 Plan security review | Security | Confirmation of revision: APPROVED; B1/N1 resolved; NB1, NB2 appended as Security notes (opus/medium · 63,050 tokens · 14 tool uses · 146 s) | +| 2026-10-29 20:52 | 4-5 Skeleton, tests first | Orchestrator | skipped: plan declares no production or test code (Verification without tests) | diff --git a/specs/issue-372/plan.md b/specs/issue-372/plan.md index cab661c9..32e7ff34 100644 --- a/specs/issue-372/plan.md +++ b/specs/issue-372/plan.md @@ -209,3 +209,8 @@ unreleased records are edited in place by the Lead after the Dev's report: - N1 (the measurements call home): every AC3 run carries both variables; only AC6's control sends the request, on purpose, and the Dev records whether it could leave the session. - N2: nothing to change. + +## Security notes + +- NB1 (Security, non-blocking): AC6 says the final run "connects only to `127.0.0.1:9`, the DNS resolver and the registry". In the Security measurement strace also showed `connect` calls on port 53 to the resolved registry and CDN addresses (Go UDP connects for source-address selection, no traffic; the control shows the same set). The deciding signal is the `hostLookupOrder(check.trivy.dev)` line; the final run's connect set must equal the control's minus whatever `check.trivy.dev` resolved to. +- NB2 (Security, non-blocking): the sentence "only the Trivy server and the registries" is broad; when an image contains JARs the client still pulls Trivy's Java DB from its OCI repository. Where the Dev writes the area text, use wording such as "the registries, including Trivy's Java DB repository when an image contains Java archives". From e8c50065c235f67eaede5b52c6f7bfdfdb6ade4d Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:57:35 +0000 Subject: [PATCH 07/16] Bump bundled Trivy to 0.76.1 and disable its update check --- docs/areas/hosting.md | 13 +++++++++++-- docs/areas/vulnerabilities.md | 5 +++++ docs/docker-compose.full.yml | 2 +- docs/docker-compose.trivy.yml | 2 +- specs/issue-372/log.md | 1 + src/DockerUpdateGuard/Dockerfile | 6 +++++- 6 files changed, 24 insertions(+), 5 deletions(-) diff --git a/docs/areas/hosting.md b/docs/areas/hosting.md index ed6b5487..3bd0cd58 100644 --- a/docs/areas/hosting.md +++ b/docs/areas/hosting.md @@ -135,8 +135,15 @@ registry never delays or prevents startup. - The process runs as UID `64000` in the root group (GID `0`), never as UID `0`. `/app` is owned by `64000:0` with group permissions equal to owner permissions, so the application also works when started with another UID in group `0`. Membership in group `0` keeps a `root:root` Docker socket readable where the socket grants group access. -- The Trivy CLI is bundled at `/usr/local/bin/trivy` (taken from `aquasec/trivy:0.58.1` by default) with its cache in - `/tmp/trivy`, so Trivy client mode works without extra installation (see [Vulnerabilities](vulnerabilities.md)). +- The Trivy CLI is bundled at `/usr/local/bin/trivy` (taken from `aquasec/trivy:0.76.1`, pinned by digest, by + default) with its cache in `/tmp/trivy`, so Trivy client mode works without extra installation (see + [Vulnerabilities](vulnerabilities.md)). The deployment tiers of the documented compose files run the Trivy server + of the same version. +- The image sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so Trivy sends no update check + or usage data to `check.trivy.dev` and contacts only the Trivy server and the registries, including Trivy's Java DB + repository when an image contains Java archives. Setting either to `false` brings the request back, and a Trivy run + outside the image needs both set by the operator + ([0016](../decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md)). - The image sets `DockerUpdateGuard__DisplayVersion` to the version it was built with. - CA certificates are installed so outbound TLS connections can be verified. - Before the application starts, the entrypoint: @@ -218,6 +225,8 @@ These properties hold today and must be provided for by the deployment: through a private directory appended to `SSL_CERT_DIR`, so no write access to the system trust store is needed. - [0012](../decisions/0012-configured-urls-are-rejected-when-an-at-sign-is-not-their-user-info-separator.md): configured URLs are rejected when an `@` is not their user-info separator. +- [0016](../decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md): the bundled Trivy skips its + update check and telemetry through the image's environment. ## Not here diff --git a/docs/areas/vulnerabilities.md b/docs/areas/vulnerabilities.md index 6f4bb679..074600be 100644 --- a/docs/areas/vulnerabilities.md +++ b/docs/areas/vulnerabilities.md @@ -98,6 +98,9 @@ scan run: process environment, added to the environment inherited from the application (an inherited `TRIVY_SERVER` is replaced), so it does not stand on the command line, where every user who can see the process could read the user info. +- The Trivy process contacts only the Trivy server and the registries, including Trivy's Java DB repository when an + image contains Java archives; the container image switches off Trivy's update check and telemetry + ([Hosting](hosting.md#container-image)). - The target is `:` for `docker.io` images and `/:` otherwise, followed by `@` when the digest is known. - The process gets `RequestTimeoutSeconds` plus 10 seconds. When that passes, the process and its children are killed @@ -196,6 +199,8 @@ The dashboard shows at most one hint about the vulnerability configuration: environment. - [0015](../decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md): the Trivy scan target follows an end-of-options marker (`--`). +- [0016](../decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md): the bundled Trivy skips its + update check and telemetry through the image's environment. ## Not here diff --git a/docs/docker-compose.full.yml b/docs/docker-compose.full.yml index 7f45879d..c2604ab4 100644 --- a/docs/docker-compose.full.yml +++ b/docs/docker-compose.full.yml @@ -27,7 +27,7 @@ services: retries: 5 trivy: - image: aquasec/trivy:0.58.1 + image: aquasec/trivy:0.76.1 container_name: dockerupdateguard-trivy restart: unless-stopped command: ["server", "--listen", "0.0.0.0:4954"] diff --git a/docs/docker-compose.trivy.yml b/docs/docker-compose.trivy.yml index ab0d54f0..fb3e60b3 100644 --- a/docs/docker-compose.trivy.yml +++ b/docs/docker-compose.trivy.yml @@ -25,7 +25,7 @@ services: retries: 5 trivy: - image: aquasec/trivy:0.58.1 + image: aquasec/trivy:0.76.1 container_name: dockerupdateguard-trivy restart: unless-stopped command: ["server", "--listen", "0.0.0.0:4954"] diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index add8d3c0..4a31f823 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -11,3 +11,4 @@ are quoted in full. | 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · 63,020 tokens · 13 tool uses · 153 s) | | 2026-10-29 20:52 | 3 Plan security review | Security | Confirmation of revision: APPROVED; B1/N1 resolved; NB1, NB2 appended as Security notes (opus/medium · 63,050 tokens · 14 tool uses · 146 s) | | 2026-10-29 20:52 | 4-5 Skeleton, tests first | Orchestrator | skipped: plan declares no production or test code (Verification without tests) | +| 2026-10-29 20:57 | 6 Implement | Dev | Dockerfile pin+digest, 2 skip ENVs, compose files, area docs; AC1-AC6 measured and passed (registry via mirror.gcr.io; docker.io 429) (sonnet/medium · 100,556 tokens · 24 tool uses · 253 s) | diff --git a/src/DockerUpdateGuard/Dockerfile b/src/DockerUpdateGuard/Dockerfile index d7f277ba..54dbbb44 100644 --- a/src/DockerUpdateGuard/Dockerfile +++ b/src/DockerUpdateGuard/Dockerfile @@ -1,7 +1,7 @@ ARG BASE_SDK="mcr.microsoft.com/dotnet/sdk:10.0-alpine" ARG BASE_RUNTIME="mcr.microsoft.com/dotnet/aspnet:10.0-alpine" ARG BASE_RUNTIME_DIGEST -ARG TRIVY_IMAGE="aquasec/trivy:0.58.1" +ARG TRIVY_IMAGE="aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c" ARG APP_VERSION=1.0.0 # --------------------------- @@ -56,6 +56,10 @@ RUN apk add --no-cache ca-certificates # The cache directory lives under /tmp so any runtime UID can write it. COPY --from=trivy /usr/local/bin/trivy /usr/local/bin/trivy ENV TRIVY_CACHE_DIR=/tmp/trivy +# Trivy's update check and telemetry would send a host identifier to check.trivy.dev on every scan; +# both are off so Trivy contacts only the Trivy server and the registries. +ENV TRIVY_SKIP_VERSION_CHECK=true +ENV TRIVY_DISABLE_TELEMETRY=true COPY --from=build /app/publish . From deb5f8dca58a45de8b48ac68b44b147252017e88 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:58:17 +0000 Subject: [PATCH 08/16] Update decision records 0015 and 0005 for Trivy 0.76.1 --- .../0005-urls-are-output-with-user-info-masked.md | 2 +- ...ivy-scan-target-follows-an-end-of-options-marker.md | 10 +++++----- specs/issue-372/log.md | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/decisions/0005-urls-are-output-with-user-info-masked.md b/docs/decisions/0005-urls-are-output-with-user-info-masked.md index 9543ccd3..012c23f0 100644 --- a/docs/decisions/0005-urls-are-output-with-user-info-masked.md +++ b/docs/decisions/0005-urls-are-output-with-user-info-masked.md @@ -115,7 +115,7 @@ added to the environment it inherits from the application, and no `--server` arg environment by the application's own user and by root, as the configuration it comes from already is. Trivy still repeats it in its error output, so the withholding above stays. - A Trivy executable that does not read `TRIVY_SERVER` (a custom `TrivyExecutablePath`) would scan without a server, - downloading the vulnerability database itself; the bundled Trivy v0.58.1 reads it. A token setting of our own is + downloading the vulnerability database itself; the bundled Trivy v0.76.1 reads it. A token setting of our own is not added: `TRIVY_TOKEN` in the application's environment reaches Trivy already. - The Trivy process inherits the application's whole environment, other secrets supplied as environment variables included. It is not reduced to an allow-list: the process runs as the application's user, which can read the diff --git a/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md b/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md index bd5b29d8..3af8823a 100644 --- a/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md +++ b/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md @@ -40,8 +40,8 @@ ever read as a Trivy option. The argument list is described in [Vulnerabilities] - A registered reference that begins with `-` is scanned as an image name; when Trivy cannot pull it, its scan ends with a non-zero exit code and is reported like any other image Trivy cannot find. -- The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1: without `--`, `--config=x.y/z:latest` - is read as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image - names and fail, and a normal target scans as before. The bundled v0.58.1 could not be measured (its release archive - is no longer published); moving the bundled version to a measured one is tracked as a follow-up, and any later Trivy - update is measured again with the same cases. \ No newline at end of file +- The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1, the version the image bundles + (pinned by tag and digest, so the build copies the measured binary): without `--`, `--config=x.y/z:latest` is read + as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image names + and fail without opening the file they name, and a normal target takes the normal scan path. Any later Trivy update + is measured again with the same cases. \ No newline at end of file diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 4a31f823..b5515684 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -12,3 +12,4 @@ are quoted in full. | 2026-10-29 20:52 | 3 Plan security review | Security | Confirmation of revision: APPROVED; B1/N1 resolved; NB1, NB2 appended as Security notes (opus/medium · 63,050 tokens · 14 tool uses · 146 s) | | 2026-10-29 20:52 | 4-5 Skeleton, tests first | Orchestrator | skipped: plan declares no production or test code (Verification without tests) | | 2026-10-29 20:57 | 6 Implement | Dev | Dockerfile pin+digest, 2 skip ENVs, compose files, area docs; AC1-AC6 measured and passed (registry via mirror.gcr.io; docker.io 429) (sonnet/medium · 100,556 tokens · 24 tool uses · 253 s) | +| 2026-10-29 20:58 | 6 Records | Lead | Edited records 0015 and 0005 after measurements; 0016 stays Proposed until approval (opus/medium · 40,579 tokens · 6 tool uses · 32 s) | From 395309b2df2717cb24dd9ee116cc8f958177db08 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 20:58:25 +0000 Subject: [PATCH 09/16] Log code check for issue 372 --- specs/issue-372/log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index b5515684..6cf953f1 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -13,3 +13,4 @@ are quoted in full. | 2026-10-29 20:52 | 4-5 Skeleton, tests first | Orchestrator | skipped: plan declares no production or test code (Verification without tests) | | 2026-10-29 20:57 | 6 Implement | Dev | Dockerfile pin+digest, 2 skip ENVs, compose files, area docs; AC1-AC6 measured and passed (registry via mirror.gcr.io; docker.io 429) (sonnet/medium · 100,556 tokens · 24 tool uses · 253 s) | | 2026-10-29 20:58 | 6 Records | Lead | Edited records 0015 and 0005 after measurements; 0016 stays Proposed until approval (opus/medium · 40,579 tokens · 6 tool uses · 32 s) | +| 2026-10-29 20:58 | 7 Code check | Orchestrator | no code changed: hadolint, scope-check, decision-check, plan-check PASS | From 271a71a90b0fcb76fad29f8b722798448444d638 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:00:43 +0000 Subject: [PATCH 10/16] Log Sonar gate for issue 372 --- specs/issue-372/log.md | 1 + 1 file changed, 1 insertion(+) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 6cf953f1..51859e8c 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -14,3 +14,4 @@ are quoted in full. | 2026-10-29 20:57 | 6 Implement | Dev | Dockerfile pin+digest, 2 skip ENVs, compose files, area docs; AC1-AC6 measured and passed (registry via mirror.gcr.io; docker.io 429) (sonnet/medium · 100,556 tokens · 24 tool uses · 253 s) | | 2026-10-29 20:58 | 6 Records | Lead | Edited records 0015 and 0005 after measurements; 0016 stays Proposed until approval (opus/medium · 40,579 tokens · 6 tool uses · 32 s) | | 2026-10-29 20:58 | 7 Code check | Orchestrator | no code changed: hadolint, scope-check, decision-check, plan-check PASS | +| 2026-10-29 21:00 | 7 Sonar gate | Orchestrator | NOT CHECKED: analysis uploaded, results API 403 'not allowed to access data from non main branches'; no code in change; re-check in step 11 via PR | From 5c5cd0cce086920e9037854184f2dfb5fb345adb Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:02:47 +0000 Subject: [PATCH 11/16] Log review round 1 for issue 372 --- ...16-the-bundled-trivy-skips-its-update-check-and-telemetry.md | 2 +- specs/issue-372/log.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md index 269ba4aa..e7ceab5c 100644 --- a/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md +++ b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md @@ -33,7 +33,7 @@ a host fingerprint to a third party, without anything in DockerUpdateGuard askin ## Decision The container image sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so the bundled Trivy -contacts only the Trivy server and the registries; the provider's arguments stay unchanged. The behavior is +sends nothing to `check.trivy.dev`; the provider's arguments stay unchanged. The behavior is described in [Hosting](../areas/hosting.md#container-image). ## Consequences diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 51859e8c..9f0a4d2a 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -15,3 +15,4 @@ are quoted in full. | 2026-10-29 20:58 | 6 Records | Lead | Edited records 0015 and 0005 after measurements; 0016 stays Proposed until approval (opus/medium · 40,579 tokens · 6 tool uses · 32 s) | | 2026-10-29 20:58 | 7 Code check | Orchestrator | no code changed: hadolint, scope-check, decision-check, plan-check PASS | | 2026-10-29 21:00 | 7 Sonar gate | Orchestrator | NOT CHECKED: analysis uploaded, results API 403 'not allowed to access data from non main branches'; no code in change; re-check in step 11 via PR | +| 2026-10-29 21:02 | 8 Review round 1 | Reviewer | BLOCKING 1: 'contacts only the Trivy server and the registries' false (Docker socket is contacted first); verbatim fix in 4 places; non-blocking 0 (opus/medium · 52,026 tokens · 10 tool uses · 94 s) | From e3f9705636d81dc159029ae3c25add10a8526352 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:03:03 +0000 Subject: [PATCH 12/16] Correct Trivy network claim in record 0016 --- docs/areas/hosting.md | 5 +++-- docs/areas/vulnerabilities.md | 5 +++-- src/DockerUpdateGuard/Dockerfile | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/areas/hosting.md b/docs/areas/hosting.md index 3bd0cd58..253d258d 100644 --- a/docs/areas/hosting.md +++ b/docs/areas/hosting.md @@ -140,8 +140,9 @@ registry never delays or prevents startup. [Vulnerabilities](vulnerabilities.md)). The deployment tiers of the documented compose files run the Trivy server of the same version. - The image sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so Trivy sends no update check - or usage data to `check.trivy.dev` and contacts only the Trivy server and the registries, including Trivy's Java DB - repository when an image contains Java archives. Setting either to `false` brings the request back, and a Trivy run + or usage data to `check.trivy.dev` and contacts only the Trivy server, the Docker Engine at `/var/run/docker.sock` + when it is mounted there, and the registries, including Trivy's Java DB repository when an image contains Java + archives. Setting either to `false` brings the request back, and a Trivy run outside the image needs both set by the operator ([0016](../decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md)). - The image sets `DockerUpdateGuard__DisplayVersion` to the version it was built with. diff --git a/docs/areas/vulnerabilities.md b/docs/areas/vulnerabilities.md index 074600be..37b420e8 100644 --- a/docs/areas/vulnerabilities.md +++ b/docs/areas/vulnerabilities.md @@ -98,8 +98,9 @@ scan run: process environment, added to the environment inherited from the application (an inherited `TRIVY_SERVER` is replaced), so it does not stand on the command line, where every user who can see the process could read the user info. -- The Trivy process contacts only the Trivy server and the registries, including Trivy's Java DB repository when an - image contains Java archives; the container image switches off Trivy's update check and telemetry +- Besides the Trivy server, the Trivy process contacts only the Docker Engine at `/var/run/docker.sock` (Trivy reads + the image from it first when the socket is mounted there) and the registries, including Trivy's Java DB repository + when an image contains Java archives; the container image switches off Trivy's update check and telemetry ([Hosting](hosting.md#container-image)). - The target is `:` for `docker.io` images and `/:` otherwise, followed by `@` when the digest is known. diff --git a/src/DockerUpdateGuard/Dockerfile b/src/DockerUpdateGuard/Dockerfile index 54dbbb44..921978ad 100644 --- a/src/DockerUpdateGuard/Dockerfile +++ b/src/DockerUpdateGuard/Dockerfile @@ -57,7 +57,7 @@ RUN apk add --no-cache ca-certificates COPY --from=trivy /usr/local/bin/trivy /usr/local/bin/trivy ENV TRIVY_CACHE_DIR=/tmp/trivy # Trivy's update check and telemetry would send a host identifier to check.trivy.dev on every scan; -# both are off so Trivy contacts only the Trivy server and the registries. +# both are off so Trivy sends nothing to check.trivy.dev. ENV TRIVY_SKIP_VERSION_CHECK=true ENV TRIVY_DISABLE_TELEMETRY=true From 4123274ea1b89a080de8aaee465415fecc4934d4 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:03:09 +0000 Subject: [PATCH 13/16] Correct Trivy network claim in docs and Dockerfile comment --- specs/issue-372/log.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index 9f0a4d2a..f66d9e89 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -16,3 +16,5 @@ are quoted in full. | 2026-10-29 20:58 | 7 Code check | Orchestrator | no code changed: hadolint, scope-check, decision-check, plan-check PASS | | 2026-10-29 21:00 | 7 Sonar gate | Orchestrator | NOT CHECKED: analysis uploaded, results API 403 'not allowed to access data from non main branches'; no code in change; re-check in step 11 via PR | | 2026-10-29 21:02 | 8 Review round 1 | Reviewer | BLOCKING 1: 'contacts only the Trivy server and the registries' false (Docker socket is contacted first); verbatim fix in 4 places; non-blocking 0 (opus/medium · 52,026 tokens · 10 tool uses · 94 s) | +| 2026-10-29 21:03 | 8 Fix round 1 | Dev | Fixed docker-socket claim in vulnerabilities.md, hosting.md, Dockerfile comment (sonnet/medium · 65,557 tokens · 4 tool uses · 19 s) | +| 2026-10-29 21:03 | 8 Fix round 1 | Lead | Fixed claim in record 0016 (opus/medium · 30,001 tokens · 5 tool uses · 16 s) | From dae79b974837ab7585eba53e7556dd5a797e9e90 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:03:38 +0000 Subject: [PATCH 14/16] Accept decision record 0016 --- ...16-the-bundled-trivy-skips-its-update-check-and-telemetry.md | 2 +- docs/decisions/README.md | 2 +- specs/issue-372/log.md | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md index e7ceab5c..0ae31cfe 100644 --- a/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md +++ b/docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md @@ -1,6 +1,6 @@ # 0016: The bundled Trivy skips its update check and telemetry -- **Status:** Proposed +- **Status:** Accepted - **Date:** 2026-10-29 - **Area:** Hosting and configuration - **Source:** Issue #372 diff --git a/docs/decisions/README.md b/docs/decisions/README.md index 0d918d4b..2bc98027 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -68,5 +68,5 @@ once in the [area document](../areas/README.md) of the record's area, and the re | [0013](0013-docker-instance-keys-are-named-relative-to-the-instance.md) | Validation messages name Docker instance keys relative to the instance | Accepted | 2026-10-27 | | [0014](0014-registry-tag-names-and-digests-are-validated-where-they-are-read.md) | Remote tag names, digests, image references and names are validated or bounded where they are read | Accepted | 2026-10-28 | | [0015](0015-the-trivy-scan-target-follows-an-end-of-options-marker.md) | The Trivy scan target follows an end-of-options marker | Accepted | 2026-10-29 | -| [0016](0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md) | The bundled Trivy skips its update check and telemetry | Proposed | 2026-10-29 | +| [0016](0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md) | The bundled Trivy skips its update check and telemetry | Accepted | 2026-10-29 | diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md index f66d9e89..2b9556e0 100644 --- a/specs/issue-372/log.md +++ b/specs/issue-372/log.md @@ -18,3 +18,5 @@ are quoted in full. | 2026-10-29 21:02 | 8 Review round 1 | Reviewer | BLOCKING 1: 'contacts only the Trivy server and the registries' false (Docker socket is contacted first); verbatim fix in 4 places; non-blocking 0 (opus/medium · 52,026 tokens · 10 tool uses · 94 s) | | 2026-10-29 21:03 | 8 Fix round 1 | Dev | Fixed docker-socket claim in vulnerabilities.md, hosting.md, Dockerfile comment (sonnet/medium · 65,557 tokens · 4 tool uses · 19 s) | | 2026-10-29 21:03 | 8 Fix round 1 | Lead | Fixed claim in record 0016 (opus/medium · 30,001 tokens · 5 tool uses · 16 s) | +| 2026-10-29 21:03 | 8 Review round 2 (delta) | Reviewer | APPROVE: blocking finding resolved, no stale claim left (opus/medium · 3,144 tokens · 2 tool uses · 12 s · continued) | +| 2026-10-29 21:03 | 9 PR approval | Orchestrator | approved by checklist: review clean on head, gates pass, main unmoved, area docs in diff; record 0016 set to Accepted | From 0070726336220ae62bb6ddceefd8a8784bba06db Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:04:02 +0000 Subject: [PATCH 15/16] Remove squad working record --- specs/issue-372/log.md | 22 ---- specs/issue-372/plan.md | 216 ---------------------------------------- 2 files changed, 238 deletions(-) delete mode 100644 specs/issue-372/log.md delete mode 100644 specs/issue-372/plan.md diff --git a/specs/issue-372/log.md b/specs/issue-372/log.md deleted file mode 100644 index 2b9556e0..00000000 --- a/specs/issue-372/log.md +++ /dev/null @@ -1,22 +0,0 @@ -# Log: issue #372 - -One line per pipeline step: date and time of day (UTC), step, member, result. Lead decisions and escalations -are quoted in full. - -| Date | Step | Member | Result | -| ---- | ---- | ------ | ------ | -| 2026-10-29 20:39 | 1 Intake | Orchestrator | Issue 372 read: bug/chore, Trivy bump, security area (Docker config); branch fix-issue-372-bump-trivy | -| 2026-10-29 20:44 | 2 Plan | Lead | plan.md written, tier standard, security areas touched; challenge skipped: no options; plan-check PASS (opus/medium · 83,078 tokens · 25 tool uses · 292 s) | -| 2026-10-29 20:47 | 3 Plan security review | Security | CHANGES_REQUIRED: B1 Trivy 0.76.1 phones home (check.trivy.dev) on every scan; needs suppression + pinning criterion; N1 measurement also calls home (opus/medium · 56,081 tokens · 16 tool uses · 137 s) | -| 2026-10-29 20:50 | 3 Plan revise | Lead | Revised for B1: option a (ENV TRIVY_SKIP_VERSION_CHECK/TRIVY_DISABLE_TELEMETRY in Dockerfile), AC6 pin, record 0016 Proposed; plan-check PASS (opus/medium · 63,020 tokens · 13 tool uses · 153 s) | -| 2026-10-29 20:52 | 3 Plan security review | Security | Confirmation of revision: APPROVED; B1/N1 resolved; NB1, NB2 appended as Security notes (opus/medium · 63,050 tokens · 14 tool uses · 146 s) | -| 2026-10-29 20:52 | 4-5 Skeleton, tests first | Orchestrator | skipped: plan declares no production or test code (Verification without tests) | -| 2026-10-29 20:57 | 6 Implement | Dev | Dockerfile pin+digest, 2 skip ENVs, compose files, area docs; AC1-AC6 measured and passed (registry via mirror.gcr.io; docker.io 429) (sonnet/medium · 100,556 tokens · 24 tool uses · 253 s) | -| 2026-10-29 20:58 | 6 Records | Lead | Edited records 0015 and 0005 after measurements; 0016 stays Proposed until approval (opus/medium · 40,579 tokens · 6 tool uses · 32 s) | -| 2026-10-29 20:58 | 7 Code check | Orchestrator | no code changed: hadolint, scope-check, decision-check, plan-check PASS | -| 2026-10-29 21:00 | 7 Sonar gate | Orchestrator | NOT CHECKED: analysis uploaded, results API 403 'not allowed to access data from non main branches'; no code in change; re-check in step 11 via PR | -| 2026-10-29 21:02 | 8 Review round 1 | Reviewer | BLOCKING 1: 'contacts only the Trivy server and the registries' false (Docker socket is contacted first); verbatim fix in 4 places; non-blocking 0 (opus/medium · 52,026 tokens · 10 tool uses · 94 s) | -| 2026-10-29 21:03 | 8 Fix round 1 | Dev | Fixed docker-socket claim in vulnerabilities.md, hosting.md, Dockerfile comment (sonnet/medium · 65,557 tokens · 4 tool uses · 19 s) | -| 2026-10-29 21:03 | 8 Fix round 1 | Lead | Fixed claim in record 0016 (opus/medium · 30,001 tokens · 5 tool uses · 16 s) | -| 2026-10-29 21:03 | 8 Review round 2 (delta) | Reviewer | APPROVE: blocking finding resolved, no stale claim left (opus/medium · 3,144 tokens · 2 tool uses · 12 s · continued) | -| 2026-10-29 21:03 | 9 PR approval | Orchestrator | approved by checklist: review clean on head, gates pass, main unmoved, area docs in diff; record 0016 set to Accepted | diff --git a/specs/issue-372/plan.md b/specs/issue-372/plan.md deleted file mode 100644 index 32e7ff34..00000000 --- a/specs/issue-372/plan.md +++ /dev/null @@ -1,216 +0,0 @@ -# Plan: Bump bundled Trivy from 0.58.1 to the measured v0.76.1 - -Source: Issue #372 -Status: Draft -Tier: standard — the bump replaces the bundled Trivy CLI whose argument parsing, `TRIVY_SERVER` handling and error text the provider relies on, and switches off the new binary's update check and telemetry in the image environment; no security control is created, changed or removed (the `--` guard of 0015 stays as built; the two variables only keep a dependency from adding an outbound call, they decide nothing an attacker may reach). -Security areas touched: Container image and runtime user (`src/DockerUpdateGuard/Dockerfile`, the Trivy CLI stage and the runtime environment); External process execution (the binary `TrivyVulnerabilityProvider` starts and the guard of record 0015 rests on); Docker/build configuration; a dependency (the bundled Trivy CLI, and the Trivy server image of the documented deployment tiers) - -## Problem / root cause - -`src/DockerUpdateGuard/Dockerfile:4` defaults `TRIVY_IMAGE` to `aquasec/trivy:0.58.1`; line 55 copies -`/usr/local/bin/trivy` from it. Record 0015 (Consequences, last item) says the `--` protection was measured on -v0.76.1 only, so the image ships a binary nobody measured. `docs/docker-compose.trivy.yml:28` and -`docs/docker-compose.full.yml:30` run the Trivy server at 0.58.1 as well, so a client bump alone would pair a -v0.76.1 client with a v0.58.1 server in the documented deployment tiers. Unlike v0.58.1, v0.76.1 sends an update -check with a stable host identifier to `https://check.trivy.dev/updates` on every `trivy image` run unless both the -version check and telemetry are off (Security, plan review; fact 12), so the bump alone would add an outbound -destination to every deployment. - -## Verified facts - -| # | Claim | Evidence | Status | -| - | ----- | ------ | ------ | -| 1 | The Dockerfile pins `aquasec/trivy:0.58.1` on line 4 | `src/DockerUpdateGuard/Dockerfile:4`, `ARG TRIVY_IMAGE="aquasec/trivy:0.58.1"` | confirmed | -| 2 | v0.76.1 is the current release | Docker Hub API `GET /v2/repositories/aquasec/trivy/tags?ordering=last_updated`: `0.76.1` (2026-10-21) and `latest` share digest `sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c`; only `canary` is newer | confirmed | -| 3 | That digest is the OCI image index of `0.76.1`, with `linux/amd64` manifest `sha256:2e6c00d711ab4ae41d8a30c2c1fd3bd3b62704c177bb120f95012e939edfb3df` and `linux/arm64` | Docker Hub API `GET /v2/repositories/aquasec/trivy/tags/0.76.1`: `media_type` `application/vnd.oci.image.index.v1+json`, `images[]` | confirmed | -| 4 | The pinned image can be pulled | to verify: registry pull of `aquasec/trivy:0.76.1@sha256:790fd66d…` (manifest, `linux/amd64` manifest, layers), Dev, step 6. Not settled here: no Docker daemon in the session, and `registry-1.docker.io` answered HTTP 429 to an anonymous manifest `HEAD` | unverified | -| 5 | The image holds the binary at `/usr/local/bin/trivy` (the path line 55 copies), reports version 0.76.1 and is statically linked (it runs on the Alpine runtime stage) | to verify: layer listing, `trivy --version`, `file`/`readelf -l` (no `INTERP`), Dev, step 6 | unverified | -| 6 | The #367 cases behave on the bundled binary as record 0015 states for v0.76.1 | to verify: the cases of AC3, Dev, step 6; 0015 measured v0.76.1 from another source (the release archive), not from the image | unverified | -| 7 | v0.76.1 accepts the provider's fixed options (`image --format json --quiet --scanners vuln --pkg-types os,library --timeout s`) and reads `TRIVY_SERVER` (no own database download) | to verify: AC3 cases, with the list `BuildArguments` builds (`TrivyVulnerabilityProvider.cs:246-263`) (no `unknown flag`, no database download in `TRIVY_CACHE_DIR`), Dev, step 6 | unverified | -| 8 | v0.76.1's error output for a missing tag still matches the provider's *unavailable* detection (`MANIFEST_UNKNOWN`, `manifest unknown`, or `manifest` and `not found`) | to verify: AC3 case f against the detection in `docs/areas/vulnerabilities.md#trivy`, Dev, step 6 | unverified | -| 9 | `trivy server --listen` and `--cache-dir` (bound to `TRIVY_CACHE_DIR`, Dockerfile line 56) exist in v0.76.1 | to verify: `trivy server --help` and `trivy image --help` of the extracted binary, Dev, step 6 | unverified | -| 10 | Records 0015 and 0005 are unreleased, so they are edited in place | `git tag --contains` of the commits that added them (`1ea22b3` for 0015, `d822166` for 0005) prints nothing; newest tag `v1.3.7` | confirmed | -| 11 | The release build passes no `TRIVY_IMAGE` build argument, so the Dockerfile default is what ships | `.github/workflows/release.yml:85-88` (`APP_VERSION`, `BASE_RUNTIME`, `BASE_RUNTIME_DIGEST` only) | confirmed | -| 12 | v0.76.1 sends `GET https://check.trivy.dev/updates` (headers `Trivy-Identifier`, `Trivy-Command`, `Trivy-Flags`, `Trivy-OS`, `Trivy-Arch`) in a background goroutine on every `trivy image` run; only `--skip-version-check` and `--disable-telemetry` together stop it; `--quiet` only hides the notice; v0.58.1 has no such call; the server is not affected | source of v0.76.1, read by Security in the plan review: `pkg/commands/artifact/run.go:143,170`, `pkg/notification/notice.go:39,54-90,57,64-67,110` | confirmed | -| 13 | v0.76.1 reads both switches from `TRIVY_SKIP_VERSION_CHECK` and `TRIVY_DISABLE_TELEMETRY` (Trivy binds each flag to `TRIVY_` plus its name) | to verify: AC6 control and final run, Dev, step 6 | unverified | -| 14 | A Trivy the provider starts inherits the application's environment, so an image `ENV` reaches it, a custom `TrivyExecutablePath` included | `ProcessRunner.cs:23-40`: `UseShellExecute = false`, only the provider's variables are set on `ProcessStartInfo.Environment`, which starts as the process environment; `docs/areas/vulnerabilities.md` line 98 | confirmed | - -## Acceptance criteria - -- [ ] AC1: `src/DockerUpdateGuard/Dockerfile` line 4 reads exactly - `ARG TRIVY_IMAGE="aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c"`, - no Dockerfile line changes besides it and the lines of *Approach* 2, and that reference can be pulled: the registry serves the index at that digest - and its `linux/amd64` manifest and layers. -- [ ] AC2: the `linux/amd64` image of AC1 contains `usr/local/bin/trivy`; the extracted binary prints version - `0.76.1` and is statically linked. -- [ ] AC3: the extracted binary, run with the provider's fixed options, `TRIVY_SERVER=http://127.0.0.1:9` - (closed port), `TRIVY_SKIP_VERSION_CHECK=true`, `TRIVY_DISABLE_TELEMETRY=true` (the image's environment), - `TRIVY_CACHE_DIR` set to an empty scratch directory and a scratch working directory: - - a. without `--`, target `--config=x.y/z:latest`: Trivy reads it as its configuration file option (error output - about loading the configuration file), non-zero exit; - - b–d. with `--` before `--config=x.y/z:latest`, `--input=/tmp/x.tar:latest` and `-a/b:latest`: each is treated as an - image name (error output about the image or its reference, not about a configuration file or archive), non-zero - exit, and the file the option would name (`x.y/z:latest` in the working directory, `/tmp/x.tar:latest`) is never - opened; - - e. `-- library/alpine:3.20`: the normal scan path — no option error, no vulnerability database download (no - `db/` under `TRIVY_CACHE_DIR`, no download message), non-zero exit at the unreachable server, or at the image - pull when the registry refuses the session; - - f. `-- library/alpine:0.0.0-does-not-exist`: error output that the provider's *unavailable* detection matches. - No run prints `unknown flag`. -- [ ] AC4: `docs/docker-compose.trivy.yml` and `docs/docker-compose.full.yml` run `aquasec/trivy:0.76.1`, and - `trivy server --help` of the extracted binary lists `--listen`; `trivy image --help` lists `--cache-dir`. -- [ ] AC5: no `0.58.1` remains outside `docs/decisions/` and the measured history in - `docs/areas/vulnerabilities.md` (line 111) — `git grep -n "0\.58\.1" -- ':!docs/decisions' ':!specs'` prints only that line; - records 0015 and 0005 name the bundled v0.76.1 as measured (wording under *Decision records*). -- [ ] AC6: the Dockerfile's runtime stage sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true` - (*Approach* 2), and with them the binary makes no request to `check.trivy.dev`: AC3 case e, run as AC3 says but - with the proxy variables unset and `GODEBUG=netdns=go+2` under `strace -f -e trace=connect`, looks up no - `check.trivy.dev` (no `hostLookupOrder(check.trivy.dev)` line in the error output) and connects only to - `127.0.0.1:9`, the DNS resolver and the registry; the same run without the two variables (control) does look up - `check.trivy.dev`. - -## Tests per criterion - -None: no production or test code changes; every criterion is verified under *Verification without tests*. - -## Verification without tests - -Steps 4 (*Skeleton*), 5 (*Tests first*) and the *Coverage gate* are not applicable (*Changes without production -or test code*). What can be checked in this session: the Dockerfile and compose edits, hadolint, the *Image build -check*'s restore-order reproduction, and the binary measurements, provided the registry serves the image over HTTPS -(no Docker daemon is running, so `docker pull`/`docker build` are not possible). Not checkable here: a real -`docker build` and a scan against a live Trivy server; the first runs in CI's release build. - -| AC | Where, by whom | -| -- | -------------- | -| AC1 | Dev, step 6: fetch the index by digest from `registry-1.docker.io` (anonymous token from `auth.docker.io`, scope `repository:aquasec/trivy:pull`), check its `Docker-Content-Digest` equals the pinned digest and that it lists the `linux/amd64` manifest of fact 3, then fetch that manifest and its layers. On HTTP 429 retry later or use `mirror.gcr.io/aquasec/trivy` with the same digest (content-addressed, so the bytes are the same). Downloads go into their own new, empty scratch directory outside the repository. Then the *Analyzer gate* with `--lint-only` (hadolint on the whole Dockerfile; the `# hadolint ignore=DL3018` line stays) and the *Image build check* (restore-order reproduction, since Docker does not run). If neither registry serves the image, the Dev stops and reports it; the orchestrator launches the Lead (`decide`) — the pin is not merged unmeasured. | -| AC2 | Dev, step 6: list the layers, extract `usr/local/bin/trivy`, run `trivy --version`, `file` and `readelf -l` on it. | -| AC3 | Dev, step 6: each case run under `strace -f -e trace=openat,open,stat,newfstatat` (installed in the session) with the exact argument list of `BuildArguments` (`TrivyVulnerabilityProvider.cs:246-263`) and `--timeout 30s`; report per case the command, the exit code, the last error line and whether the trace names `x.y/z:latest` or `/tmp/x.tar:latest`. Case a is the control: its trace must show the configuration file being looked up, so an empty trace in b–d is meaningful. A case whose outcome differs from AC3 stops the change and goes to the Lead (`decide`); case f alone may be reported "not measured" when the registry refuses the session, which the Lead then decides. | -| AC4 | Dev, step 6: the edits, and the two `--help` runs of AC2's binary. | -| AC5 | Dev, step 6: the `git grep`; Lead, record edits after the Dev's measurement report (below). Reviewer, step 8: reads both. | -| AC6 | Dev, step 6: the Dockerfile lines, then the control run first and the final run, each reported with the command, the `hostLookupOrder` lines and the `connect` addresses. Only the control may send the request; the Dev records whether it could leave the session (direct egress or not). The control makes the empty final result meaningful: a control without the lookup is repeated up to three times (the request runs in a background goroutine and may lose the race against the exit), then the change stops and goes to the Lead (`decide`), as does a final run with the lookup. | - -## Approach - -1. Dockerfile line 4 as AC1: tag and digest, so the build gets exactly the measured binary, even if the tag is moved. -2. Dockerfile, right after `ENV TRIVY_CACHE_DIR=/tmp/trivy` (line 58), these four lines (AC6, record 0016): - - ```dockerfile - # Trivy's update check and telemetry would send a host identifier to check.trivy.dev on every scan; - # both are off so Trivy contacts only the Trivy server and the registries. - ENV TRIVY_SKIP_VERSION_CHECK=true - ENV TRIVY_DISABLE_TELEMETRY=true - ``` -3. Both compose files: `image: aquasec/trivy:0.76.1` (tag only, like the other images there; operators maintain it). - The server makes no update call (fact 12), so they get no variables. -4. The Dev measures AC1–AC4 and AC6 and reports the raw results; then the orchestrator launches the Lead (`revise`) to apply - the record wording below, adjusted only where a measurement differs within what AC3 allows (case e's stopping - point, case f "not measured"). - -No new user-visible output. - -## Options considered - -1. **Tag and digest in the Dockerfile** — chosen: the measurement binds to the bytes the build copies; a tag is - mutable. -2. **Tag only** (`aquasec/trivy:0.76.1`) — lost: a re-pushed tag would ship a binary nobody measured, which is the - gap this issue closes. Updating needs a digest edit as well; Dependabot covers no Docker ecosystem here - (`.github/dependabot.yml`: NuGet and actions only), so either form is updated by hand. -3. **Keep 0.58.1 and measure it from its image** — lost: the issue asks for a current release, and the old binary - stays a year behind on fixes. -4. **Bump the client only, leave the compose servers at 0.58.1** — lost: a v0.76.1 client against a v0.58.1 server - was not measured, and this plan does not measure it; the documented tiers keep client and server at one version. -5. **Update check and telemetry: image environment** (chosen), **flags in `BuildArguments`** or **left to the - operator** — weighed in record 0016: the flags change production code, tests and 0015's argument list and break - an older custom Trivy with `unknown flag`; leaving it would send a host fingerprint from every default deployment. - -## Affected projects and types - -| Project | Type / file | Change | -| ------- | ----------- | ------ | -| `src/DockerUpdateGuard` | `Dockerfile` | line 4, `TRIVY_IMAGE` default (AC1); two `ENV` lines after line 58 (AC6) | -| docs | `docs/docker-compose.trivy.yml`, `docs/docker-compose.full.yml` | Trivy server image (AC4) | - -## Signatures (for the Dev's skeleton) - -none - -## Test files - -None. Existing test call sites affected: none. Files no unit test can cover: none (no code changes; the Dockerfile -and compose files are not measured for coverage). - -## Areas - -- `docs/areas/hosting.md`: the bundled Trivy version and its pinning. -- `docs/areas/hosting.md`: the image switches off Trivy's update check and telemetry. -- `docs/areas/vulnerabilities.md`: Trivy contacts only the Trivy server and the registries (pointer to Hosting); the - argument list and error handling are unchanged; line 111's "measured with Trivy v0.58.1" is the measured reason - for a rule that holds for every version and stays. - -## Documentation updates - -Sweep: `git grep -n "0\.58\|aquasec"` over the repository; `docs/ARCHITECTURE.md` (line 476), `README.md`, -`INSTALL.md`, `.squad/project.md` and `.squad/stack.md` name no Trivy version and stay. - -- `docs/areas/hosting.md` line 138 (Dev): the bundled CLI is taken from `aquasec/trivy:0.76.1`, pinned by digest, by - default; the deployment tiers run the Trivy server of the same version. Same bullet or the next: the image sets - `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so Trivy sends no update check or usage data - to `check.trivy.dev` and contacts only the Trivy server and the registries; setting either to `false` brings the - request back, and a Trivy run outside the image needs both set by the operator. Link record 0016. -- `docs/areas/vulnerabilities.md#trivy`, after the `TRIVY_SERVER` bullet (lines 97-98) (Dev): one sentence that the - Trivy process contacts only the Trivy server and the registries, the update check and telemetry being switched off - by the image ([Hosting](hosting.md#container-image)); add 0016 to the area's record list (line 195 onward). -- `docs/docker-compose.trivy.yml:28`, `docs/docker-compose.full.yml:30` (Dev): as *Approach* 2. - -## Architecture check - -No guarantee of `.squad/project.md` is touched; the composition, the process boundary and the client-mode design of -`docs/ARCHITECTURE.md` (lines 230, 476) stay. No `docs/ARCHITECTURE.md` update. - -## Security considerations - -- [x] No limits: no parsed input changes. -- [x] None: no exception path changes. -- The guard of record 0015 depends on the binary, so it is measured on exactly the bytes the build copies (AC1–AC3), - and the change fails closed: an unmeasurable or deviating binary stops it before review. -- v0.76.1's update check (fact 12) is switched off in the image (AC6, record 0016); a control run proves the - measurement can see the request, and a missing control or a lookup in the final run stops the change. - -## Decision records - -New record `docs/decisions/0016-the-bundled-trivy-skips-its-update-check-and-telemetry.md` (Proposed, Area -Hosting and configuration, indexed): why the update check is suppressed in the image environment. The bump itself needs none. Two -unreleased records are edited in place by the Lead after the Dev's report: - -- `docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md`, *Consequences*, last item: - "The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1, the version the image bundles - (pinned by tag and digest, so the build copies the measured binary): without `--`, `--config=x.y/z:latest` is read - as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image names - and fail without opening the file they name, and a normal target takes the normal scan path. Any later Trivy update - is measured again with the same cases." -- `docs/decisions/0005-urls-are-output-with-user-info-masked.md` line 118: "the bundled Trivy v0.58.1 reads it" → - "the bundled Trivy v0.76.1 reads it" (AC3 case e). Line 24's and 32's v0.58.1 measurements stay: they are the - reason the rules exist. - -## Out of scope / follow-ups - -- Re-measuring the credential repetition in Trivy's error output (record 0005, area line 111) on v0.76.1: the output - is withheld for every version, so the result changes no behavior. -- Pinning the other base images by digest. - -## Security review - -- B1 (update check to `check.trivy.dev`): option a taken — `ENV TRIVY_SKIP_VERSION_CHECK=true` and - `ENV TRIVY_DISABLE_TELEMETRY=true` in the runtime stage (*Approach* 2), pinned by AC6 with a control run; why in - record 0016 (option b breaks an older custom Trivy and changes code and 0015; the environment reaches every Trivy - the application starts in the image, fact 14). Documented in `docs/areas/hosting.md` and - `docs/areas/vulnerabilities.md#trivy`. Tier stays `standard`: the variables are a dependency setting, not a guard. -- N1 (the measurements call home): every AC3 run carries both variables; only AC6's control sends the request, on - purpose, and the Dev records whether it could leave the session. -- N2: nothing to change. - -## Security notes - -- NB1 (Security, non-blocking): AC6 says the final run "connects only to `127.0.0.1:9`, the DNS resolver and the registry". In the Security measurement strace also showed `connect` calls on port 53 to the resolved registry and CDN addresses (Go UDP connects for source-address selection, no traffic; the control shows the same set). The deciding signal is the `hostLookupOrder(check.trivy.dev)` line; the final run's connect set must equal the control's minus whatever `check.trivy.dev` resolved to. -- NB2 (Security, non-blocking): the sentence "only the Trivy server and the registries" is broad; when an image contains JARs the client still pulls Trivy's Java DB from its OCI repository. Where the Dev writes the area text, use wording such as "the registries, including Trivy's Java DB repository when an image contains Java archives". From dbb4f201d9506fff80cdfbbc3b2035254605b9ab Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 21:08:47 +0000 Subject: [PATCH 16/16] Pin the bundled Trivy image by digest only --- docs/areas/hosting.md | 4 ++-- ...-the-trivy-scan-target-follows-an-end-of-options-marker.md | 4 ++-- src/DockerUpdateGuard/Dockerfile | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/areas/hosting.md b/docs/areas/hosting.md index 253d258d..f4042c2d 100644 --- a/docs/areas/hosting.md +++ b/docs/areas/hosting.md @@ -135,8 +135,8 @@ registry never delays or prevents startup. - The process runs as UID `64000` in the root group (GID `0`), never as UID `0`. `/app` is owned by `64000:0` with group permissions equal to owner permissions, so the application also works when started with another UID in group `0`. Membership in group `0` keeps a `root:root` Docker socket readable where the socket grants group access. -- The Trivy CLI is bundled at `/usr/local/bin/trivy` (taken from `aquasec/trivy:0.76.1`, pinned by digest, by - default) with its cache in `/tmp/trivy`, so Trivy client mode works without extra installation (see +- The Trivy CLI is bundled at `/usr/local/bin/trivy` (taken from the digest-pinned `aquasec/trivy` image of v0.76.1, + by default) with its cache in `/tmp/trivy`, so Trivy client mode works without extra installation (see [Vulnerabilities](vulnerabilities.md)). The deployment tiers of the documented compose files run the Trivy server of the same version. - The image sets `TRIVY_SKIP_VERSION_CHECK=true` and `TRIVY_DISABLE_TELEMETRY=true`, so Trivy sends no update check diff --git a/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md b/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md index 3af8823a..e50f45b0 100644 --- a/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md +++ b/docs/decisions/0015-the-trivy-scan-target-follows-an-end-of-options-marker.md @@ -41,7 +41,7 @@ ever read as a Trivy option. The argument list is described in [Vulnerabilities] - A registered reference that begins with `-` is scanned as an image name; when Trivy cannot pull it, its scan ends with a non-zero exit code and is reported like any other image Trivy cannot find. - The protection rests on Trivy honoring `--`. It was measured on Trivy v0.76.1, the version the image bundles - (pinned by tag and digest, so the build copies the measured binary): without `--`, `--config=x.y/z:latest` is read - as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image names + (pinned by digest, the digest of the v0.76.1 image index, so the build copies the measured binary): without + `--`, `--config=x.y/z:latest` is read as the configuration file option; after `--`, `--config=…`, `--input=…` and `-a/b` targets are taken as image names and fail without opening the file they name, and a normal target takes the normal scan path. Any later Trivy update is measured again with the same cases. \ No newline at end of file diff --git a/src/DockerUpdateGuard/Dockerfile b/src/DockerUpdateGuard/Dockerfile index 921978ad..5eb6e5e3 100644 --- a/src/DockerUpdateGuard/Dockerfile +++ b/src/DockerUpdateGuard/Dockerfile @@ -1,7 +1,8 @@ ARG BASE_SDK="mcr.microsoft.com/dotnet/sdk:10.0-alpine" ARG BASE_RUNTIME="mcr.microsoft.com/dotnet/aspnet:10.0-alpine" ARG BASE_RUNTIME_DIGEST -ARG TRIVY_IMAGE="aquasec/trivy:0.76.1@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c" +# Trivy v0.76.1 (digest of the multi-arch image index) +ARG TRIVY_IMAGE="aquasec/trivy@sha256:790fd66d1697395d8693d1acaa1ea702406a20f109b6fc7bac8db623eca6c06c" ARG APP_VERSION=1.0.0 # ---------------------------