…PER-15358)
## The gate never ran on what we ship
docker-scout carried `if: github.event_name == 'pull_request'`. release.yml
calls tests.yml with `uses:`, and inside a called workflow
github.event_name is the CALLER'S event - "release" - so the condition was
false and the job was skipped on every release, and on every push to main.
Not a reading of the YAML. Release v0.9.14, run 30900950607:
success pdp-tests / build-pdp-image
success pdp-tests / pdp-tester
skipped pdp-tests / docker-scout <-- the gate
success build-and-push-pdp # pushed :0.9.14 and :latest
Every image on Docker Hub was built and pushed without passing a CVE gate.
Scanning ran only on PRs, against a :next tag that is never published.
Adding 'workflow_call' to the condition would not have helped - that is not
a value github.event_name takes. `on:` already enumerates exactly the
trigger set we want scanned, so the condition is removed outright.
This makes the gate release-blocking (build-and-push-pdp needs pdp-tests).
That is the intent - PER-15532 was a customer scan of 0.9.14-rc1 finding
CVEs CI never flagged - but it is a real change in failure mode: a HIGH
published between merge and release cut now stops the push and the ECS
redeploy until the dependency is fixed or a VEX statement is added.
Two limits stated plainly rather than oversold: releases run the workflow
AT THE TAG, so a release cut from a commit predating a VEX correction uses
that commit's waiver set; and the scanned image is the single-arch
linux/amd64 CI build, so linux/arm64 is still covered by nothing.
Note docker-scout is NOT currently in the repo's required status checks, so
a new HIGH still reaches main freely and first bites at the release cut.
Adding it there is a follow-up worth doing.
## VEX: unpin from the :next tag
All statements used `pkg:docker/permitio/pdp-v2@next`. That matches today
only because the scanned tag is hardcoded :next - the day anyone
parameterises it, all six waivers silently stop matching and the
now-blocking gate hard-fails on already-triaged CVEs. Dropping the version
qualifier is a strict superset: openvex PurlMatches rejects only when both
versions are non-empty and unequal, so an empty version matches any tag.
Confirmed against go-vex v0.2.7, the version Scout links.
Subcomponent PURLs stay exact-versioned on purpose - pkg:pypi/starlette@0.50.0
must not match 0.51.0. That is how a waiver expires when the pin moves.
## Waiver corrections
- CVE-2026-48710 contained a false claim: "the app registers no
add_middleware/BaseHTTPMiddleware". The PDP adopts OPAL's app
(horizon/pdp.py:280), which registers CORSMiddleware and a 500 handler
(opal_common/middleware.py:77,30). `grep -rn add_middleware horizon/`
returns nothing, which is how the claim got written. The conclusion
survives - CORSMiddleware reads no path - but that is the argument the
waiver needed to make and did not. The rewrite also names the three
layers a "no middleware" claim would miss: FastAPI's framework defaults,
starlette's AuthenticationMiddleware (not installed), and ddtrace's
TraceMiddleware, which DOES read scope['path'] and the Host header
(ddtrace/contrib/internal/asgi/middleware.py:245-248) whenever monitoring
is on - it tags a span rather than making a security decision.
- CVE-2026-54282 (LOW 3.7) was unwaived. Added, with the same carve-outs
rather than a blanket "nothing in the image reads request.url" - which
would have been false, see starlette/authentication.py:66,83.
- CVE-2026-15308 removed entirely: the base image now resolves to CPython
3.13.15 and Scout no longer reports it at any severity, so `not_affected`
would now be the wrong status. This was also the only version-less
subcomponent (pkg:generic/python), which would have suppressed the
finding across every future interpreter. Stale Dockerfile comment
dropped with it.
- CVE-2026-50271: dropped "Independently". horizon/pdp.py:239 applies the
control plane's remote config before the ENABLE_MONITORING check at :262,
so monitoring can be enabled remotely - the ENV is the load-bearing
mitigation, not the flag. Dockerfile comment aligned to match.
- CVE-2026-54283: removed the python-multipart clause. True but irrelevant;
this CVE is the urlencoded path, which starlette parses natively.
- CVE-2026-48817: removed "every route is registered through APIRouter
decorators" - FastAPI registers /docs and /openapi.json via add_route().
## Hardening that the trigger change makes necessary
- write-comment gated on pull_request for both scout steps. The input
exists and defaults to true (docker/scout-action action.yaml:197), and
there is no PR to comment on during a release.
- SARIF upload requires the file to exist, and is now continue-on-error:
the gate is the step above it, and a code-scanning upload problem must
not block a customer release for zero security signal.
- category: docker-scout. codeql-action keys analyses by
`${workflowPath}:${jobName}` resolved from the run's own workflow, so a
release-triggered run would otherwise key separately from PR/push runs.
- Documented that medium/low are report-only by policy.
## Also
requirements.txt's starlette block said "four CVEs / all four are waived";
it is five now that CVE-2026-54282 is tracked. Left as a correct removal
checklist for when OPAL relaxes its starlette<1 cap.
## Merge order
Must land AFTER permitio/permit-opa#<pr> and the golang:1.26 builder PR.
This arms a release-blocking gate; today that gate reports 4 HIGH from the
OPA binary. Merging this first would block the next release.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Superseded by #338, which runs the CVE gate on releases and carries the waiver changes. Closed without merging.
🤖 Generated with Claude Code
https://claude.ai/code/session_01PHJFjyY84UkG7zAfXUHPpP