-
Notifications
You must be signed in to change notification settings - Fork 2
docs(security): write down the vulnerability remediation policy #1716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,99 @@ | ||
| --- | ||
| id: vulnerability-remediation | ||
| sidebar_position: 5 | ||
| title: Vulnerability remediation | ||
| description: What blocks a build or a release, why unfixable findings do not, and how to write an exception. | ||
| --- | ||
|
|
||
| # Vulnerability remediation | ||
|
|
||
| The policy is executable: `scripts/check-release-vulnerabilities.ts` evaluates a Trivy report against | ||
| `security/vulnerability-policy.json`. This page states it in prose, which is what | ||
| [NIST SSDF](https://csrc.nist.gov/projects/ssdf) RV.1.3 asks for. RV.2.1 accepts "remediation **or | ||
| other risk response**" and prescribes no thresholds — only that ours are written down. | ||
|
|
||
| ## What blocks | ||
|
|
||
| A finding blocks when it is **HIGH or CRITICAL and upstream has published a fix** — Trivy reports a | ||
| non-empty `FixedVersion`. MEDIUM and below never block, at any scan. | ||
|
|
||
| The fixable/unfixable split is the part most easily read wrong, so plainly: **an unfixable HIGH is not | ||
| an exception and does not need one.** Four of the images in `security/release-images.json` are upstream | ||
| — alpine, nats, nginx, and traefik — and cannot be patched here at all, so failing on a finding that | ||
| has no fix removes no risk and leaves nobody an action. The share is not marginal: when the gate was | ||
| first measured against real images, `agent-pi` reported 59 HIGH/CRITICAL findings of which 1 was | ||
| fixable, and `postgres` 99 of which 23 were. | ||
|
|
||
| Gating on fix availability is the ordinary shape of this control. Google Binary Authorization exposes | ||
| `maximumFixableSeverity` and `maximumUnfixableSeverity` as separate settings; Docker Scout's default | ||
| policy is "no **fixable** critical or high vulnerabilities"; Snyk separates `--fail-on=upgradable` from | ||
| `--severity-threshold`; Chainguard's remediation SLA starts when a patch exists rather than when the | ||
| CVE is published. | ||
|
|
||
| Unfixable findings are still counted. The filter lives in the evaluator, not in a `--ignore-unfixed` | ||
| flag on Trivy, so every HIGH and CRITICAL stays in the scan report and in the `highCritical` list of | ||
| the `.policy.json` attached to the release. Suppressing the *failure* is the intent; suppressing the | ||
| *record* is not, and the evidence bundle is signed on the assumption that it is complete. | ||
|
|
||
| ## Where the gate runs | ||
|
|
||
| | When | Subject | Effect | | ||
| | --- | --- | --- | | ||
| | Every image build (`reusable-docker-build.yml`) | the `linux/amd64` image the run just pushed | fails the pull request | | ||
| | Release evidence (`release.yml`) | both platforms of every first-party and upstream image | no release is created | | ||
| | Weekly (`rescan-main-images.yml`) | the `linux/amd64` image behind each `:main` tag | opens or updates a tracking issue | | ||
| | Weekly (`rescan-release-images.yml`) | every subject in the latest published release | reports on [the vulnerability response issue](https://github.com/ls1intum/Hephaestus/issues/1369) | | ||
|
|
||
| All four call the same script with the same policy file; `scripts/ci-contract.test.ts` asserts there is | ||
| no second copy of either. The two scheduled rescans deliberately do not block: a finding published | ||
| after a clean merge is nobody's commit and no revert removes it, so a red status would name no | ||
| culprit and demand no action — [Release management](./release-management.mdx#supply-chain-evidence) | ||
| has how the drift is routed instead. A scanner failure is never treated as no findings. | ||
|
|
||
| ## Exceptions | ||
|
|
||
| An exception lets one fixable finding through for a bounded time. It is an entry in | ||
| `security/vulnerability-policy.json`, and `security/` has no CODEOWNERS rule of its own, so the default | ||
| one applies: a member of the @ls1intum/hephaestus-maintainers team has to approve it. | ||
|
|
||
| - `image`, `platform`, `vulnerability`, `package`, `installedVersion` — the match key. All five bind | ||
| exactly, and `installedVersion` is what makes an exception die when the package moves. | ||
| - `digest` — recorded so the exception names the artefact it was reviewed against, but deliberately | ||
| **not** matched on. A release digest does not exist until the images are tagged, so a digest-keyed | ||
| exception could only ever be written after the gate had already failed a release. | ||
| - `owner` — who is accountable for retiring it. | ||
| - `status` — `affected` or `not_affected`, plus a `justification` in prose. | ||
| - `evidence` — an HTTPS URL: the upstream advisory, the tracking issue, or the analysis the claim rests | ||
| on. A link a reviewer can follow, not a sentence. | ||
| - `expires` — RFC 3339 UTC, in the future, and **at most 90 days out**. The cap is not a renewal-free | ||
| window; at 90 days the gate fails again and the finding is argued again with whatever upstream has | ||
| published since. | ||
|
|
||
| ### `not_affected` justifications | ||
|
|
||
| A `not_affected` exception also carries a `justificationCategory`, one of the five in CISA's *Minimum | ||
| Requirements for Vulnerability Exploitability eXchange (VEX)*. Naming one forces the claim to say which | ||
| way the finding does not apply, so prose like "not reachable" cannot stand in for an analysis nobody | ||
| did. | ||
|
|
||
| | Value | Means | | ||
| | --- | --- | | ||
| | `component_not_present` | the vulnerable component is not in the image | | ||
| | `vulnerable_code_not_present` | the component ships, the affected code does not | | ||
| | `vulnerable_code_not_in_execute_path` | the affected code ships but is never called | | ||
| | `vulnerable_code_cannot_be_controlled_by_adversary` | it is called, but never with attacker-controlled input | | ||
| | `inline_mitigations_already_exist` | it is reachable and controllable, but a compensating control blocks exploitation | | ||
|
|
||
| A value outside those five is a malformed policy and throws. An `affected` exception carries no | ||
| category — it defers a risk we concede applies, and claiming non-exploitability at the same time is a | ||
| contradiction. | ||
|
|
||
| ## VEX is not adopted | ||
|
|
||
| The vocabulary above is all we take. [OpenVEX](https://github.com/openvex/spec) has been frozen at | ||
| v0.2.0 since 2023-08-22, and Trivy's `--vex` still carries an EXPERIMENTAL banner and matches on | ||
| digest-based PURLs that a locally built image does not have. `aquasecurity/vexhub`, Trivy's default VEX | ||
| repository, stopped updating on 2026-03-23 and has no OS-distro coverage at all — no Alpine, no Debian, | ||
| no nginx — and Alpine publishes no VEX itself, which is where most findings here originate. Adopting it | ||
| would mean hand-authoring documents that duplicate `security/vulnerability-policy.json`, minus the | ||
| expiry field OpenVEX does not define. Revisit if the distros start publishing. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.