The SBOM was generated and installed onto every server, and nothing read it. "Are we exposed to this advisory" was still a person going and looking, which meant it happened when somebody remembered to. scripts/scan_sbom.py queries OSV.dev by purl - no account, no API key, no agent, no vulnerability database to maintain on a box that cannot reach the internet. The SBOM is already the input OSV wants, so this is a lookup against what shipped rather than a second scan of the tree that could disagree with it. Three decisions worth recording: Shipped and build-only findings are both reported, but only shipped ones can fail a build. Two thirds of the components here are build tooling the SBOM already marks scope 'excluded', and the highest-severity match in the whole tree today is a CRITICAL against the test runner. A gate that fails a release over that trains everybody to bypass it, and then it catches nothing. Severity is taken from the advisory, never derived. Where there is only a CVSS vector the report says UNKNOWN, which is shown and never gates. A guessed score is worse than an honest gap because somebody would act on it. The allowlist is a record rather than a mute button: each entry carries the reason, the accepter, the date, and what would invalidate the reasoning. The first entry is a worked example - a PyJWT algorithm-confusion advisory that needs both mixed algorithm families and a raw JSON JWK as the key, and this application configures neither. The report ends with a per-component upgrade rollup, since that is the part somebody acts on. A target on a different major version is labelled rather than listed beside patch bumps as though the two cost the same. Current state: 8 shipped components have fixes available, none of them exploitable through a path this application opens today.
28 lines
1.5 KiB
JSON
28 lines
1.5 KiB
JSON
{
|
|
"_comment": [
|
|
"Advisories reviewed and accepted, with the reason and the date.",
|
|
"This is a RECORD, not a mute button: an entry says somebody looked at",
|
|
"the advisory and worked out why it does not apply here. Anyone can",
|
|
"re-read the reason and disagree.",
|
|
"",
|
|
"An entry needs an id, a reason that names the precondition that is",
|
|
"absent, who accepted it, and when. 'reviewuntil' is when the reasoning",
|
|
"should be checked again - a reason stops being true when the code that",
|
|
"made it true changes.",
|
|
"",
|
|
"Do NOT add an entry to make a build pass. Upgrading the dependency is",
|
|
"almost always cheaper than defending a decision to skip it."
|
|
],
|
|
"accepted": [
|
|
{
|
|
"id": "GHSA-xgmm-8j9v-c9wx",
|
|
"component": "pyjwt",
|
|
"reason": "Needs a verifier configured with BOTH symmetric and asymmetric algorithms and a raw JSON JWK passed as key=. Neither is present: auth goes through flask-jwt-extended with no JWT_ALGORITHM or JWT_DECODE_ALGORITHMS override anywhere in the tree, so it is HS256-only, and the key is the JWT_SECRET_KEY string. Upstream rates the attack complexity High for exactly this reason. Upgrade to pyjwt 2.13.0 anyway at the next dependency bump; this entry only says it is not urgent.",
|
|
"acceptedby": "DT team, reference site",
|
|
"accepted": "2026-08-29",
|
|
"reviewuntil": "2026-11-29",
|
|
"invalidatedby": "Setting JWT_ALGORITHM or JWT_DECODE_ALGORITHMS to anything asymmetric, or moving to SSO-issued tokens verified against a JWK."
|
|
}
|
|
]
|
|
}
|