Skip to content

Latest commit

 

History

History
95 lines (76 loc) · 5.82 KB

File metadata and controls

95 lines (76 loc) · 5.82 KB
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.

Disclosure — how to report a vulnerability privately — is SECURITY.md; this page is remediation only.

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.

An unfixable HIGH is not an exception and does not need one. It remains recorded, but without a patched version there is no upgrade for the gate to require.

Unfixable findings stay in the scan report and in the highCritical list of the .policy.json attached to the release; the filter is in the evaluator, not a --ignore-unfixed flag.

Where the gate runs

When Subject Effect
Every image build (reusable-docker-build.yml) the linux/amd64 image the run just pushed fails the run
A pull request touching security/release-images.json (ci-security-scan.yml) both platforms of each pinned upstream digest fails the run
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, and both platforms of each pinned upstream digest opens or updates a tracking issue
Weekly (rescan-release-images.yml) every subject in the latest published release fails and comments on the vulnerability response issue

All five evaluate security/vulnerability-policy.json through scripts/check-release-vulnerabilities.ts; the release and the rescans reach it through verify-release-evidence.ts, scan-main-images.ts and scan-upstream-images.ts, and scripts/ci-contract.test.ts asserts there is one policy file. That test also asserts the scans before the release cover exactly the subject set the release gate covers, both derived from security/release-images.json: the upstream images are shipped by digest and never built here, so nothing scanned them until the release gate did, and v0.75.0 failed on a finding that had been in the pinned alpine digest for days. The scheduled rescans do not fail a commit status: a finding published after a clean merge has no commit to revert. A scanner failure is never treated as no findings.

A pinned upstream image cannot be patched by rebuilding anything here. Its remedy is a digest bump in security/release-images.json — which Renovate proposes, and which the pull request proposing it now scans — and until upstream republishes the tag there may be no digest to bump to.

That is also why the upstream scan covers both platforms while the scans of images we build cover linux/amd64 only. An exception matches on image | platform | vulnerability | package | installedVersion, so a single-platform scan can only ever half-check a subject. For an image we build, the other half costs little to leave to the release: a finding on either architecture is fixed by the same rebuild, and the Version PR preflight scans both before the release is cut. For a pinned upstream digest there is no rebuild, the fix is a digest bump nobody can make until upstream publishes one, and the bump arrives in a pull request — so an arm64-only finding has to fail that pull request rather than the release.

Exceptions

An exception lets one fixable finding through for at most 90 days. 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.
  • statusaffected 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.
  • expiresYYYY-MM-DDTHH:MM:SSZ, in the future, at most 90 days out; when it lapses the gate fails again.

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.