PER-16045: move the OPA builder to golang:1.26-bookworm (lands before permit-opa go 1.26) - #342
Conversation
Lands ahead of permit-opa raising its go directive to 1.26, which golang.org/x/crypto >= 0.56.0 forces (its own go.mod declares go 1.26.0). That x/crypto version clears CVE-2026-78662 / CVE-2026-56855, today waived in .docker/scout/pdp-v2.vex.json. Ordering only works one way: tests.yml and release.yml build permit-opa from an unpinned `ref: main` checkout, and the official golang images set GOTOOLCHAIN=local, so a 1.25 builder facing a go 1.26 module hard-fails instead of fetching a toolchain - breaking build-pdp-image everywhere the moment the permit-opa change merges. A 1.26 builder on today's go 1.25.0 module is forward-compatible. Same change as the closed PDP#334 (PER-15358), re-proposed under PER-16045. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
🔍 Vulnerabilities of
|
| digest | sha256:91da910734a9dc9dfb7e1c72eeeba85e97390a8b1d1e2c2f4ee43cec26f1f022 |
| vulnerabilities | |
| platform | linux/amd64 |
| size | 136 MB |
| packages | 247 |
📦 Base Image python:026d26881d2e1ebad06e4f309b0fc5f03c0471c5230d325d7b571be802586ee6
| also known as |
|
| digest | sha256:f282f385cfce21b0a644094330930704424a48d59afe8f08052e0f8e0b7a35c6 |
| vulnerabilities |
Description
Description
Description
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
Description
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Description
|
zeevmoney
left a comment
There was a problem hiding this comment.
Approved — no CRITICAL or HIGH issues found. 7 MEDIUM, 5 LOW.
Non-blocking:
- MEDIUM
Dockerfile:70— Comment omits the go.mod directive/GODEBUG dependency it actually relies on - MEDIUM
Dockerfile:74— Major toolchain bump validated only by "it builds"; comment claims only glibc - MEDIUM
Dockerfile:75— Merge falsifies two CVE waivers whose removal gate names the closed PDP#334 - MEDIUM
Dockerfile:76— opa_build base stays tag-only; the 1.26.6 floor and toolchain are unenforced - LOW
Dockerfile:57— Stated merge order omits permit-opa#51, which gates #52 - LOW
Dockerfile:78— GOTOOLCHAIN=local is load-bearing but inherited, never asserted
Findings that could not be anchored to a line inside this PR's diff:
- MEDIUM
.github/workflows/tests.yml:52— permit-opa is compiled into released images from an unpinned ref: main (file not present in this PR's diff)
This PR's comment (Dockerfile:62-64) makes it load-bearing that "tests.yml and release.yml check out permitio/permit-opa atref: main, unpinned, so a permit-opa merge reaches the very next PDP build". That property is the defect it is being used to justify: tests.yml:52 and release.yml:43 pin nothing, so every publishedpermitio/pdp-v2image compiles whatever was on another repo's default branch at release time, with no record of which commit produced/app/bin/opaand no review gate between a permit-opa merge and a customer artifact. It is also what turns this routine bump into a cross-repo merge-order hazard managed by hand in PR bodies. (release.yml:40 is additionally stillactions/checkout@v3.)
Suggestion: Pin the permit-opa checkout to a commit SHA or release tag in both workflows and bump it deliberately, so the shipped OPA source is reproducible and the ordering constraint becomes a version bump rather than a race. - MEDIUM
.github/workflows/tests.yml:77— arm64 leg of the new builder is first exercised at release, and never scanned (file not present in this PR's diff)
build-pdp-imagebuildsplatforms: linux/amd64only (tests.yml:77), while release.yml:62 and :114 buildlinux/amd64,linux/arm64. Neither build passestarget:, so both resolveapplication->main-permit->main, which COPYs from opa_build (Dockerfile:233). Sogolang:1.26-bookworm/arm64 compiles the shipped OPA for the first time during a published release, under QEMU, and the Docker Scout gate (tests.yml:233-239) scanslocal://permitio/pdp-v2:next, the amd64 artifact - the arm64 binary's new go1.26 stdlib is never scanned before it reaches Docker Hub. The only arm64 evidence is a local--target opa_buildrun recorded in the PR body, and this stage has shipped an arm64-only defect before (Dockerfile:82, issue #289).
Suggestion: Addlinux/arm64to the PR build, or add a cheap--target opa_build --platform linux/arm64job, so a builder change fails on the PR rather than mid-release. - MEDIUM
Dockerfile:95— OPA_BUILD arg is disconnected from the custom-vs-vanilla branch (line 95 is not inside a diff hunk (nearest diff line: 79))
Two independent switches decide the same thing.if [ -f /custom/custom_opa.tar.gz ](line 95) picks the custom vs vanilla OPA binary, whileARG OPA_BUILD=permit(line 1) separately selectsPDP_OPA_PLUGINS='{"permit_graph":{}}'(line 327) viaFROM main-${OPA_BUILD} AS application(line 329). Nothing ties them, and no workflow or Makefile passes--build-arg OPA_BUILD(grep over.github/workflowsandMakefile: zero hits), so it is alwayspermit. A clean vanilla build therefore ships a vanilla OPA in an image declaring a plugin the Dockerfile itself says at line 319-320 "we MUST not add".build_opal_bundle.sh:19-29compounds it:PDP_VANILLA=trueskips the tarball but never clearscustom/, so a stale tarball silently yields a custom build.
Suggestion: DeclareARG OPA_BUILDinside opa_build, take the download branch only whenOPA_BUILD=vanilla, and hard-fail whenOPA_BUILD=permitand/custom/custom_opa.tar.gzis absent. Makebuild_opal_bundle.shclearcustom/on both branches. - LOW
Dockerfile:102—-anegates the go-build cache mount and opa_build is not cross-compiled (line 102 is not inside a diff hunk (nearest diff line: 79))
Lines 92-94 mount/go/pkg/modand/root/.cache/go-build"for MUCH faster incremental builds", but line 102 passes-a, which forces a rebuild of every package including the stdlib, so the go-build cache mount never serves a compile (the module cache mount still helps). WithCGO_ENABLED=0the-abuys nothing - the build cache already keys on CGO_ENABLED and build tags. Compounding it, opa_build has no--platform=$BUILDPLATFORM(unlike rust_chef at line 12), so release.yml'slinux/amd64,linux/arm64runs that full stdlib+OPA rebuild under QEMU for the arm64 leg; release.yml:93-94 already notes "opa_build and main are each built twice with the second leg under QEMU".
Suggestion: Drop-a, and build once on the build platform using native Go cross-compilation, which removes QEMU from this stage. - LOW
Dockerfile:106— Vanilla OPA fallback downloads 'latest' with no checksum and no curl --fail (line 106 is not inside a diff hunk (nearest diff line: 79))
In the stage this PR re-bases, the non-custom branch runscurl -L -o /opa https://openpolicyagent.org/downloads/latest/opa_linux_amd64_static(and the aarch64 twin on line 107). Three gaps:latestmeans the build has no record of which OPA version shipped; there is no checksum or signature check on a binary that lands at/app/bin/opa(line 233) and evaluates every authorization decision; and without--fail, curl writes an HTTP error body to/opaand exits 0, so a CDN 404/5xx yields an image whose OPA binary is an HTML page while the build stays green. Reachability is narrow - CI always suppliescustom_opa.tar.gz- but this is the shipped Dockerfile.
Suggestion: Pin the OPA version, add--fail --show-error, and verify the published SHA256 before accepting the binary. - LOW
test_offline_mode/Dockerfile:1— test_offline_mode base has no version at all, let alone a digest (file not present in this PR's diff)
Auditing the repo's otherFROMlines while this one moves:test_offline_mode/Dockerfile:1isFROM python:alpine- no major.minor, no Alpine suite, no digest. Every rebuild can land on a different CPython major and a different Alpine, so the offline-mode check can start failing (or passing) for reasons unrelated to the PDP, with no record of what it ran on. Not shipped to customers, hence LOW, but it is the last wholly unversioned base in the tree;Dockerfile:12(rust:1.94-alpine) andDockerfile:169(python:3.13-alpine3.23) are at least tag-versioned.
Suggestion: Give it an explicit, digest-pinned tag matching the main image's Python, e.g.python:3.13-alpine3.23@sha256:<digest>.
Details are in the inline comments on each line.
…/crypto waiver gates (PER-16045) Review follow-up (Zeev): - opa_build sets ENV GOTOOLCHAIN=local instead of relying on the base image, and fails the build if the builder is below go1.26.6 (GO-2026-6090) - the same shape as the CPython floor check in `main`. It also catches a merge that restores golang:1.25 (checked: a 1.25 builder fails on it). - The comment names what the 1.26 toolchain changes in /app/bin/opa (Green Tea GC) versus what follows permit-opa's go.mod (GODEBUG defaults), and the merge order now includes permit-opa#51. Drops the closed PDP#334. - pdp-v2.vex.json (version 11) and the tests.yml scout comment no longer claim the builder is golang:1.25 or gate removal on the closed PDP#334; the remaining gate is permit-opa#52. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@zeevmoney, here is what happened to the review findings that had no inline thread. None of them change in this PR; all are about code this diff doesn't touch.
|
zeevmoney
left a comment
There was a problem hiding this comment.
Re-review of the fixes. 3 MEDIUM, 9 LOW.
Fix verification — 12 findings from the 2026-09-17 review: 4 fixed, 2 partly fixed, 6 not fixed
Non-blocking:
- LOW
.docker/scout/pdp-v2.vex.json:23— Waiver removal gate disagrees with permit-opa#52's own step 4 - LOW
.github/workflows/tests.yml:251— PR description still says the waivers name the closed PDP#334 as the gate
Findings that could not be anchored to a line inside this PR's diff:
- MEDIUM
.github/workflows/tests.yml:52— permit-opa is still compiled into released images from an unpinned ref: main (line 52 is not inside a diff hunk (nearest diff line: 248))
Unchanged since the first review, and this PR now depends on it in three places. tests.yml:52 and release.yml:43 pin nothing, so every published permitio/pdp-v2 image compiles whatever was on another repo's default branch at release time, with no record of which commit produced /app/bin/opa and no review gate between a permit-opa merge and a customer artifact. Dockerfile:63-65 states that as the ordering mechanism, and both rewritten vex impact_statements make it the CVE removal gate. The concrete consequence is written into the same comment at Dockerfile:76-78: deleting onegodebug default=go1.25line in permit-opa (its go.mod:161 on #52) changes the shipped OPA binary on the next PDP build, with no PDP-side diff. release.yml:40 is additionally stillactions/checkout@v3.
Suggestion: Pin the permit-opa checkout to a commit SHA or release tag in both workflows and bump it deliberately, so the shipped OPA source is reproducible and the ordering constraint becomes a version bump rather than a race. Bump release.yml:40 to a SHA-pinned actions/checkout at the same time. - MEDIUM
.github/workflows/tests.yml:77— arm64 leg of the new Go 1.26 builder is still first compiled at release (line 77 is not inside a diff hunk (nearest diff line: 248))
The previous round did not close this - no code changed and the PR body demotes it to a follow-up. tests.yml:77 buildsplatforms: linux/amd64; release.yml:62 and :114 buildlinux/amd64,linux/arm64. Neither passestarget:, so both resolveapplication(Dockerfile:341) ->main-permit->main, which COPYs from opa_build at Dockerfile:245. Sogolang:1.26-bookworm/arm64, and the new floor RUN at Dockerfile:86-88, first execute under QEMU while publishing to Docker Hub. The scout gate ispull_request-only (tests.yml:199) and scanslocal://permitio/pdp-v2:next(tests.yml:228, :237), the amd64 artifact, so the arm64 binary's new go1.26 stdlib is never scanned. This stage has shipped an arm64-only defect before (Dockerfile:94-97, issue #289).
Suggestion: Add a cheap--target opa_build --platform linux/arm64job to tests.yml (it stops before the Rust and Python stages), or add linux/arm64 to build-pdp-image, so a builder change fails on the PR rather than mid-release. - MEDIUM
Dockerfile:107— OPA_BUILD arg still decides the plugin, not which OPA binary ships (line 107 is not inside a diff hunk (nearest diff line: 91))
Unchanged and unanswered. Two independent switches decide the same thing:if [ -f /custom/custom_opa.tar.gz ](line 107) picks the custom vs vanilla OPA binary, whileARG OPA_BUILD=permit(line 1) separately selectsPDP_OPA_PLUGINS='{"permit_graph":{}}'(line 339) throughFROM main-${OPA_BUILD} AS application(line 341). A repo-wide grep for OPA_BUILD matches only lines 1 and 341 - no workflow, Makefile or compose file passes--build-arg OPA_BUILD- so it is alwayspermitandmain-vanilla(line 333) is unbuildable. A vanilla build therefore ships a vanilla OPA declaring a plugin the file itself says at lines 331-332 we MUST not add. build_opal_bundle.sh:19-22 compounds it:PDP_VANILLA=trueskips the tarball but never clearscustom/, so a stale tarball silently yields a custom build.
Suggestion: DeclareARG OPA_BUILDinside opa_build, take the download branch only whenOPA_BUILD=vanilla, and hard-fail whenOPA_BUILD=permitand /custom/custom_opa.tar.gz is absent. Moverm -rf custom; mkdir customout of the conditional in build_opal_bundle.sh. - LOW
.github/workflows/release.yml:77— The stated reason for excluding opa_build from no-cache-filters is now weaker (file not present in this PR's diff)
Lines 77-79 justify leaving opa_build out ofno-cache-filterswith "COPY custom* /custompulls in custom_opa.tar.gz, which the Pre-build step regenerates every run, so that stage already re-executes unconditionally." This PR insertsENV GOTOOLCHAIN=localand the go1.26.6 floor RUN (Dockerfile:85-88) ahead of that COPY at Dockerfile:90. Those two layers are keyed only on the resolved base image digest, so they can be served fromcache-from: type=ghawhenevergolang:1.26-bookwormhas not moved - the stage no longer re-executes end to end. The behaviour stays correct (a moved digest changes the key and re-runs the check), but the prose overstates it, and Dockerfile:82-83 reads as though the floor check runs on every build.
Suggestion: Add a clause to release.yml:77-81 saying the stage's pre-COPY preamble (GOTOOLCHAIN plus the floor check) caches on the base image digest, which is why it still does not need the filter. - LOW
Dockerfile:114—-astill defeats the go-build cache mount; opa_build still not cross-compiled (line 114 is not inside a diff hunk (nearest diff line: 91))
Unchanged and unanswered, and the bump makes it more expensive. Lines 105-106 mount /go/pkg/mod and /root/.cache/go-build "for MUCH faster incremental builds", but line 114 passes-a, forcing a rebuild of every package including the whole Go 1.26 stdlib, so the build-cache mount never serves a compile. With CGO_ENABLED=0 the-abuys nothing - the cache already keys on CGO_ENABLED and build tags - and its self-justification at line 99 is cargo-culted. Line 84 also has no--platform=$BUILDPLATFORM(unlike line 12), so release.yml's arm64 leg runs that full rebuild under QEMU; release.yml:93-94 already notes it. The sibling permit-opa#52 went the other way and cross-compiles from $BUILDPLATFORM.
Suggestion: Drop-aand build once on the build platform with--platform=$BUILDPLATFORMplus GOARCH from $TARGETARCH. The fix is only complete if the vanilla branch'scase $(uname -m)(lines 117-121) moves to $TARGETARCH at the same time - on a BUILDPLATFORM-pinned stageuname -mreports the builder's arch and would download the wrong binary. - LOW
Dockerfile:118— New floor guards the stage, not the binary the vanilla branch actually ships (line 118 is not inside a diff hunk (nearest diff line: 91))
Unchanged since the first review, and the new floor check does not reach it. When /custom/custom_opa.tar.gz is absent (line 107) the stage compiles nothing: lines 118-119 runcurl -L -o /opa https://openpolicyagent.org/downloads/latest/opa_linux_{amd64,arm64}_staticand that prebuilt binary is what line 245 copies to /app/bin/opa.latestrecords no version; there is no checksum or signature on a binary that evaluates every authorization decision; and without--fail, curl writes an HTTP error body to /opa and exits 0, so a CDN 404/5xx yields a green build whose OPA binary is an HTML page. The go1.26.6 / GO-2026-6090 guarantee stated at lines 81-83 does not describe this branch's binary at all.
Suggestion: Pin the OPA version, add--fail --show-error, verify the published SHA256 before accepting the binary, and say at lines 81-83 that the floor binds only the custom-tarball branch. - LOW
Dockerfile:276— ensurepip path hardcodes python3.13 while the floor check branches for 3.14 (line 276 is not inside a diff hunk (nearest diff line: 91))
Pre-existing and outside this PR's subject, raised from a full read of the file. The CPython floor check at line 241 branches per minor ({13: (3, 13, 15), 14: (3, 14, 7)}) and the comment at lines 142-145 explains that the base may resolve to the 3.14 line. Line 276 then runsrm -r /usr/local/lib/python3.13/ensurepip, a hardcoded minor, andrm -rwithout-fexits non-zero on a missing path. Exactly one of the two is right: withFROM python:3.13-alpine3.23(line 181) the 3.14 branch of the floor check is unreachable, and the day anyone moves that tag, line 276 fails the build. A reader has to resolve the contradiction to know which.
Suggestion: Derive the path instead of hardcoding it (e.g. from sysconfig's stdlib path), or drop the 3.14 branch from the floor check so both places agree the base is 3.13-only. - LOW
Dockerfile:284— Duplicate USER permit instruction is dead (line 284 is not inside a diff hunk (nearest diff line: 91))
Pre-existing, noticed while reading the whole file for this PR's multi-stage audit. Line 279 isUSER permitand line 284 repeats it verbatim, with onlyCOPY ./horizon /app/horizon(line 282) between them - a COPY does not change the user, so line 284 is a no-op. It is a metadata-only history entry rather than a filesystem layer, so the cost is confusion rather than image size: a reader looking for where the image drops privileges finds two answers.
Suggestion: Delete line 284. - LOW
build_opal_bundle.sh:3— build_opal_bundle.sh uses set -e instead of set -euo pipefail (file not present in this PR's diff)
Pre-existing, and it is the script that produces the exact input this PR's builder compiles. Line 3 isset -eonly. Line 25 is a pipeline -find * \( -name '*go*' -o -name 'LICENSE.md' \) -print0 | xargs -0 tar -czf .../custom_opa.tar.gz --exclude '.*'- whose exit status withoutpipefailis tar's alone, so a failingfindproduces a green run and a tarball that may be short. The Dockerfile then decides custom-vs-vanilla purely on[ -f /custom/custom_opa.tar.gz ](Dockerfile:107), so whatever this script leaves behind fully determines which OPA binary ships.
Suggestion: Change line 3 toset -euo pipefailand re-run shellcheck. Pair it with${PDP_VANILLA:-}on lines 6 and 19: both read the variable bare today, so adding-uon its own would abort the script with "unbound variable" whenever PDP_VANILLA is not exported, which is the normal case. - LOW
test_offline_mode/Dockerfile:1— test_offline_mode base still has no version at all, let alone a digest (file not present in this PR's diff)
Unchanged and unanswered.FROM python:alpinehas no major.minor, no Alpine suite and no digest, while this PR's own subject is which base a builder resolves to. Every rebuild can land on a different CPython major and a different Alpine, so the offline-mode check can start failing (or passing) for reasons unrelated to the PDP, with no record of what it ran on; test_offline_mode/docker-compose.yaml builds it for both the online and offline testers. Not shipped to customers, hence LOW, but it is the last wholly unversioned base in the tree: Dockerfile:12 (rust:1.94-alpine) and Dockerfile:181 (python:3.13-alpine3.23) are at least tag-versioned, and PDP#338 digest-pins all three of those.
Suggestion: Give it an explicit, digest-pinned tag matching the main image's Python, e.g.python:3.13-alpine3.23@sha256:<digest>.
Already raised in an existing thread (not re-posted):
- MEDIUM
Dockerfile:84— Base is still a floating tag; nothing records which 1.26.x built the binary - LOW
Dockerfile:86— The go1.26.6 floor added here is absent from permit-opa's own builder
Details are in the inline comments on each line.
zeevmoney
left a comment
There was a problem hiding this comment.
2 further finding(s) from the re-review of this PR.
1 of them restate a finding from the 2026-09-17 review whose fix did not close it. They were left out of the re-review comment in error: they matched the original thread, which is now resolved, and my de-duplication treated that as already-raised. A resolved thread is a closed conversation, so they belong on fresh threads. The verdict already submitted on this PR is unchanged.
…s (PER-16045) Review follow-up (Zeev, round 2): - Dockerfile: the patch version floats on purpose (Go security releases arrive without a bump), and the exact toolchain is recorded in the binary's build info, which is what scanners read. The floor check caches on the base digest, and it binds only the branch that compiles permit-opa, not the prebuilt-OPA fallback. - release.yml: opa_build re-executes from `COPY custom*` on; the two layers before it cache on the base image digest. - Dockerfile: drop the second `USER permit` (a no-op after a COPY). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Findings with no inline thread (5d84477). I re-checked the six you marked not fixed. All are real, and all predate this PR, which changes only the builder version. None was answered last round. Here is each one:
All the not-changed items are listed as follow-ups in the PR body. |
zeevmoney
left a comment
There was a problem hiding this comment.
Third-round review of the fixes. 3 MEDIUM, 10 LOW.
Fix verification: 14 findings from the 2026-09-18 re-review. 5 fixed, 1 partly fixed, 7 not fixed, 1 accepted wontfix
Non-blocking:
- LOW
Dockerfile:81— Float rationale omits PDP#338's digest pin and permit-opa#51's exact pin - LOW
Dockerfile:83— "which is what image scanners read" overstates the audit trail the float gets - LOW
Dockerfile:89— This FROM line is parsed by permit-opa CI, with nothing here saying so - LOW
Dockerfile:91— The floor check reads the toolchain version and then discards it - LOW
Dockerfile:286— ensurepip path hardcodes python3.13 while the floor check branches for 3.14
1 of the above restate a finding whose fix did not close it.
Findings that could not be anchored to a line inside this PR's diff:
- MEDIUM
.github/workflows/tests.yml:52— permit-opa is still compiled into released images from an unpinned ref: main (line 52 is not inside a diff hunk (nearest diff line: 248))
Raised in rounds 1 and 2 and unchanged; this PR now leans on it in three places. tests.yml:52 and release.yml:43 areref: main, so every published permitio/pdp-v2 image compiles whatever was on another repo's default branch at release time, with no record of which commit produced /app/bin/opa and no review gate between a permit-opa merge and a customer artifact. Dockerfile:63-64 states the unpinned ref as the ordering mechanism and .docker/scout/pdp-v2.vex.json:23 and :40 make it the CVE removal gate. The consequence is written at Dockerfile:76-78: deleting onegodebug default=go1.25line in permit-opa (live at its go.mod:162) changes the shipped OPA binary on the next PDP build with no PDP-side diff. release.yml:40 is also stillactions/checkout@v3, on the step that carriesCLONE_REPO_TOKEN(:45).
Suggestion: Pin the permit-opa checkout to a commit SHA or release tag in both workflows and bump it deliberately, so the shipped OPA source is reproducible and the ordering constraint becomes a version bump rather than a race. Bump release.yml:40 to a SHA-pinned actions/checkout at the same time. - MEDIUM
.github/workflows/tests.yml:77— No CI gate builds opa_build for arm64; only the release does (line 77 is not inside a diff hunk (nearest diff line: 248))
Raised in rounds 1 and 2; no workflow changed, and the PR body moves it to a follow-up with no ticket. This PR's own arm64 validation was run by hand, so the gap is the absent CI gate for the next change, not this one. tests.yml:77 buildsplatforms: linux/amd64; release.yml:62 and :117 buildlinux/amd64,linux/arm64. Neither passestarget:, so both resolveapplication(Dockerfile:344) and COPY from opa_build (Dockerfile:250). A later edit to the stage, or a movedgolang:1.26-bookwormdigest, first executes on arm64 under QEMU while publishing to Docker Hub. The scout gate is pull_request-only (tests.yml:199) and scans the amd64 artifact (tests.yml:228, :237). This stage shipped an arm64-only defect before (Dockerfile:99-102, issue #289).
Suggestion: Add a--target opa_build --platform linux/arm64job to tests.yml, which stops before the Rust and Python stages and so is cheap, or add linux/arm64 to build-pdp-image, so a builder or base-digest change fails on the PR rather than mid-release. - MEDIUM
Dockerfile:112— OPA_BUILD selects the plugin, not which OPA binary ships (line 112 is not inside a diff hunk (nearest diff line: 96))
Raised in rounds 1 and 2 and unchanged; the PR body now lists it as a follow-up. Two independent switches decide the same thing:if [ -f /custom/custom_opa.tar.gz ](line 112) picks the custom versus vanilla OPA binary, whileARG OPA_BUILD=permit(line 1) separately selectsPDP_OPA_PLUGINS='{"permit_graph":{}}'(line 342) viaFROM main-${OPA_BUILD} AS application(line 344). Grep returns OPA_BUILD only at lines 1 and 344, and nothing in the repo (workflows, Makefile:22-32, compose) ever passes--build-arg OPA_BUILDorPDP_VANILLA, so the two never move together. A build that passesOPA_BUILD=vanillawhile a stalecustom/tarball exists ships a custom OPA with the plugin disabled; the reverse ships a vanilla OPA declaring a plugin lines 334-335 say we MUST not add. build_opal_bundle.sh:19-22 compounds it:PDP_VANILLA=trueskips the tarball but never clearscustom/.
Suggestion: DeclareARG OPA_BUILDinside opa_build, take the download branch only whenOPA_BUILD=vanilla, and hard-fail whenOPA_BUILD=permitand /custom/custom_opa.tar.gz is absent. Moverm -rf custom; mkdir customout of the conditional in build_opal_bundle.sh so a vanilla run cannot inherit a stale tarball. - LOW
Dockerfile:119—-adefeats the go-build cache mount; opa_build still builds under QEMU (line 119 is not inside a diff hunk (nearest diff line: 96))
Raised in rounds 1 and 2 and unchanged; the bump makes it more expensive. Lines 110-111 mount /go/pkg/mod and /root/.cache/go-build "for MUCH faster incremental builds", but line 119 passes-a, forcing a rebuild of every package including the whole Go 1.26 stdlib, so the go-build cache mount never serves a compile (the module mount still serves downloads). With CGO_ENABLED=0 the-abuys nothing, and-installsuffix netgoplus-extldflags=-staticon the same line are likewise inert. Line 89 also has no--platform=$BUILDPLATFORM(contrast line 12 for rust_chef), so release.yml's arm64 leg runs that full rebuild emulated. permit-opa#52 went the other way: its Dockerfile:19 pins $BUILDPLATFORM and :38-39 cross-compile via GOOS/GOARCH.
Suggestion: Drop-aand build once on the build platform with--platform=$BUILDPLATFORMplus GOARCH from $TARGETARCH, matching permit-opa#52. The fix is only complete if the vanilla branch'scase $(uname -m)(lines 122-126) moves to $TARGETARCH at the same time, because on a BUILDPLATFORM-pinned stageuname -mreports the builder's arch and would download the wrong binary. - LOW
Dockerfile:123— Vanilla fallback downloadslatestOPA with no --fail and no checksum (line 123 is not inside a diff hunk (nearest diff line: 96))
Partly fixed since round 2: the doc half landed at lines 87-88, which scope the floor to the custom-tarball branch, but the branch itself is untouched. When /custom/custom_opa.tar.gz is absent (line 112), lines 123-124 runcurl -L -o /opa https://openpolicyagent.org/downloads/latest/opa_linux_{amd64,arm64}_static, and line 250 copies that binary to /app/bin/opa.latestrecords no version, there is no checksum or signature, and without--failcurl writes an HTTP error body to /opa and exits 0, so a CDN 404 or 5xx yields a green build whose OPA binary is an HTML page. Scope: tests.yml:62-69 and release.yml:47-54 regenerate the tarball unconditionally, so published images take the compile branch; this bites local and manual builds. The new sentence at line 87 also does not say this branch is unpinned.
Suggestion: Pin the OPA version in the URL, add--fail --show-error, and verify the published SHA256 before accepting the binary. In the same pass, say in the line 87-88 sentence that the fallback is unpinned and unverified, so the deferral survives the merge of the PR body that currently holds it. - LOW
Dockerfile:266— Second deadUSER permitleft behind by this round's fix (line 266 is not inside a diff hunk (nearest diff line: 286))
This round deleted one no-opUSER permit(the one afterCOPY ./horizon) but left the identical pattern at line 266. Between line 266 andUSER rootat line 271 there is onlyCOPY kong_routes.json /config/kong_routes.json(line 269), and a COPY without--chownwrites UID/GID 0 regardless of the current USER; /config is already permit-owned from line 260. No RUN executes under line 266, so it changes nothing. The round-2 finding named a defect that exists in two places and the fix removed one, leaving a reader the same two answers to "where does this image drop privileges". Line 284 is the one that matters: it sets the final runtime user inherited by main-vanilla, main-permit and application.
Suggestion: Delete line 266, or give line 269--chown=permit:permitif permit ownership of /config/kong_routes.json was the intent. - LOW
build_opal_bundle.sh:3— build_opal_bundle.sh uses set -e; a failing find in the tar pipeline is masked (file not present in this PR's diff)
Raised in round 2 and unchanged; the PR body lists it under follow-ups. It is the script that produces the exact input this PR's builder compiles. Line 3 isset -eonly. Line 25 is a pipeline,find * \( -name '*go*' -o -name 'LICENSE.md' \) -print0 | xargs -0 tar -czf .../custom_opa.tar.gz --exclude '.*'; without pipefail a failingfindis masked, so a partial or empty file list produces a green run and a tarball that may be short. Dockerfile:112 then decides custom versus vanilla purely on[ -f /custom/custom_opa.tar.gz ], so whatever this script leaves behind fully determines which OPA binary ships. The same pipeline is inlined at tests.yml:69 and release.yml:54, inrun:blocks that set no shell options.
Suggestion: Change line 3 toset -euo pipefailand re-run shellcheck, pairing it with${PDP_VANILLA:-}on lines 6 and 19, which read the variable bare today, so-ualone would abort with "unbound variable" whenever PDP_VANILLA is not exported. Addset -euo pipefailto the two workflowrun:blocks as well. - LOW
test_offline_mode/Dockerfile:1— test_offline_mode base has no version at all, let alone a digest (file not present in this PR's diff)
Raised in rounds 1 and 2 and unchanged; the PR body lists it under follow-ups.FROM python:alpinehas no major.minor, no Alpine suite and no digest, while this PR's own subject is which base a builder resolves to. Every rebuild can land on a different CPython major and a different Alpine, so the offline-mode check can start failing or passing for reasons unrelated to the PDP, with no record of what it ran on; test_offline_mode/docker-compose.yaml:41-59 builds it for both the online and offline testers. Nothing under .github/workflows/ or the Makefile references it, so it is a manual harness, hence LOW, but it is the last wholly unversioned base in the tree: Dockerfile:12 is rust:1.94-alpine, :89 golang:1.26-bookworm, :186 python:3.13-alpine3.23.
Suggestion: Give it an explicit tag matching the main image's Python, for examplepython:3.13-alpine3.23, and a digest alongside it if PDP#338's digest convention lands.
Details are in the inline comments on each line.
…h (PER-16045) Review round 3 on #342. * The float rationale said the build info "is what image scanners read". Nothing here scans a published tag: the docker-scout job is `pull_request`-only and points at a local tag, which tests.yml already calls a gap (PER-15358). The paragraph now says what actually holds - the toolchain is in the build info, survives `-s -w`, and is readable with `go version` on an extracted copy, because the runtime base ships no Go - and calls it an after-the-fact audit rather than a gate. * The same paragraph now names PDP#338, which digest-pins this line and adds a daily Dependabot digest bump, and says what to reword once it lands; and it names permit-opa's release assets as the build that pins the toolchain exactly, so both trees describe the same three-way policy. * The floor RUN read `go env GOVERSION` and printed it only when it failed. It now echoes the accepted version too, on the one run where the base image digest moved - so the layer log answers which 1.26.x compiled /app/bin/opa. Verified in /bin/sh: go1.26.8 and go1.26.6 exit 0 and print, go1.26.5 and go1.25.14 still exit 1 with the floor message. * The comment block above the FROM now says that permit-opa's `pdp-builder` check parses that line for a literal `golang:<major>.<minor>`, so an ARG, a line split or a stage rename breaks another repo's CI. Verified by running permit-opa#52's check-pdp-builder.sh against this file: still parses. * The ensurepip removal hardcoded python3.13 while the CPython floor check 34 lines above branches for 3.13 and 3.14. Exactly one could be right, and the day the base tag moved to 3.14 the `rm -r` (no -f) would have failed the build. The path now comes from sysconfig's stdlib, so both places agree and neither has to be edited when the base moves. On a stock CPython under /usr/local it resolves to the same path as the literal; verified the exact quoting through /bin/sh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Round 3 on #342 (head On the four findings that could not be anchored, all pre-existing and none touched by this PR. This PR is approved and is a builder-version bump plus comments; I am not growing it into any of these, but here is where each stands:
|
zeevmoney
left a comment
There was a problem hiding this comment.
Fourth-round review of the fixes. 4 MEDIUM, 10 LOW.
Fix verification: 13 findings from the 2026-09-20 review. 5 fixed, 8 not fixed
Non-blocking:
- MEDIUM
Dockerfile:71— "Until this lands" is wrong: releases cut from older commits stay broken - LOW
.docker/scout/pdp-v2.vex.json:23— Rewritten x/crypto waivers still treat the merged permit-opa#49 as pending - LOW
Dockerfile:85— Three-builds sentence states permit-opa#51 and #52 outcomes as current - LOW
Dockerfile:89— PR body still says build info is "what scanners read", which f867aad retracted - LOW
Dockerfile:93— PDP#338 falsifies the auditability paragraph too; the reword note misses it - LOW
Dockerfile:100— Floor echo is usually CACHED in a release log; it is not the release's answer
Findings that could not be anchored to a line inside this PR's diff:
- MEDIUM
.github/workflows/tests.yml:52— permit-opa is still compiled into released images from an unpinned ref: main (line 52 is not inside a diff hunk (nearest diff line: 248))
Raised in rounds 1 to 3 and unchanged; the only answer is a PR-body follow-up with no ticket. tests.yml:52 and release.yml:43 check out permitio/permit-opa atref: main, so every published pdp-v2 image compiles whatever sat on another repo's default branch, with no record of which commit produced /app/bin/opa. This PR builds on that: Dockerfile:63-64 names the unpinned ref as the ordering mechanism, and the rewritten VEX removal gates (.docker/scout/pdp-v2.vex.json:23, :40) say 'the next PDP build resolves x/crypto v0.57.0'. Per Dockerfile:76-78, deleting onegodebugline in permit-opa changes the shipped binary with no PDP diff. release.yml:40 is stillactions/checkout@v3on the step holding CLONE_REPO_TOKEN.
Suggestion: Pin the permit-opa checkout to a commit SHA or release tag in both workflows and bump it deliberately, so the shipped OPA source is reproducible and the ordering constraint becomes a version bump. Move release.yml:40 to the same checkout version as line 26 in the same change. - MEDIUM
.github/workflows/tests.yml:77— No CI gate builds opa_build for arm64; only the release does (line 77 is not inside a diff hunk (nearest diff line: 248))
Raised in rounds 1 to 3; f867aad changed only the Dockerfile and the PR body defers this with no ticket. tests.yml:77 buildsplatforms: linux/amd64; release.yml:62 and :117 build amd64 and arm64 with notarget:, resolvingapplication(Dockerfile:367), which copies /app/bin/opa from opa_build (Dockerfile:273). The stage this PR rewrote (new base, GOTOOLCHAIN, floor RUN) and its floating tag therefore first execute on arm64, under QEMU, inside the job that pushes to Docker Hub. This stage has shipped an arm64-only defect before (Dockerfile:122-124, #289), and this PR's own arm64 validation was manual.
Suggestion: Add a cheap--target opa_build --platform linux/arm64build to tests.yml (it stops before the Rust and Python stages), or add linux/arm64 to build-pdp-image, so a builder change or a moved base digest fails on the PR rather than mid-release. - MEDIUM
Dockerfile:135— OPA_BUILD selects the plugin, not which OPA binary ships (line 135 is not inside a diff hunk (nearest diff line: 119))
Raised in rounds 1 to 3 and unchanged; deferred in the PR body only. Two independent switches decide one thing:if [ -f /custom/custom_opa.tar.gz ](line 135) picks compiled versus downloaded OPA, whileARG OPA_BUILD=permit(line 1) separately picksPDP_OPA_PLUGINS='{"permit_graph":{}}'(line 365) viaFROM main-${OPA_BUILD}(line 367). Nothing in the repo passes--build-arg OPA_BUILD, so they never move together.OPA_BUILD=vanillawith a stale custom/ tarball ships permit-opa with the plugin disabled; the reverse ships vanilla OPA declaring a plugin that lines 357-358 say MUST not be added. build_opal_bundle.sh:19-22 compounds it:PDP_VANILLA=trueskips the tarball but never clears custom/.
Suggestion: DeclareARG OPA_BUILDinside opa_build, take the download branch only whenOPA_BUILD=vanilla, and fail the build whenOPA_BUILD=permitand /custom/custom_opa.tar.gz is absent. Moverm -rf custom; mkdir customout of the conditional in build_opal_bundle.sh. - LOW
Dockerfile:142—-adefeats the go-build cache mount; opa_build still builds under QEMU (line 142 is not inside a diff hunk (nearest diff line: 119))
Raised in rounds 1 to 3 and unchanged. Line 142 passes-a, which rebuilds every package including the Go 1.26 stdlib, so the go-build cache mount at line 134 (sold at line 132 as 'for MUCH faster incremental builds') never serves a compile. With CGO_ENABLED=0 the-abuys nothing, although line 127 documents it as needed for a static build. Line 111 has no--platform=$BUILDPLATFORM, so release.yml's arm64 leg runs that full rebuild emulated. permit-opa#52's Dockerfile:19 pins $BUILDPLATFORM and :39 cross-compiles with GOOS/GOARCH.
Suggestion: Drop-a, pin the stage to--platform=$BUILDPLATFORMand cross-compile with GOARCH=$TARGETARCH. Move the vanilla branch'scase $(uname -m)(line 145) to $TARGETARCH in the same change, or a BUILDPLATFORM-pinned stage downloads the builder's arch. - LOW
Dockerfile:146— Vanilla fallback downloadslatestOPA with no --fail and no checksum (line 146 is not inside a diff hunk (nearest diff line: 119))
Raised in rounds 1 to 3; neither the code nor the prose half changed. Lines 146-147 runcurl -L -o /opa https://openpolicyagent.org/downloads/latest/opa_linux_{amd64,arm64}_staticwhen no tarball exists, and line 273 ships that file as /app/bin/opa.latestrecords no version, nothing verifies a checksum, and without--failcurl writes an HTTP error body to /opa and exits 0, so a CDN 404 or 5xx yields a green build whose OPA is an HTML page. Lines 102-103 still say only that the fallback 'downloads a prebuilt OPA'. Published images take the compile branch (tests.yml:62-69, release.yml:47-54); any build without the tarball takes this one.
Suggestion: Pin the OPA version in the URL, add--fail --show-error, and verify the published SHA256. At minimum, say at lines 102-103 that the fallback is unpinned and unverified, so the deferral survives the PR body. - LOW
Dockerfile:289— Second deadUSER permitleft behind by the earlier fix (line 289 is not inside a diff hunk (nearest diff line: 301))
Raised in round 3 with no reply and no change, and absent from the PR-body follow-up list, which still describes the earlier fix as 'dropped the secondUSER permit, a no-op after aCOPY'. Line 289 is the same shape:USER permitfollowed only byCOPY kong_routes.json /config/kong_routes.json(line 292) andUSER root(line 294). A COPY without--chownwrites UID/GID 0 whatever USER is set, and no RUN executes under line 289, so it changes nothing. Line 307 is the one that sets the runtime user, so a reader gets two answers to where the image drops privileges.
Suggestion: Delete line 289, or give line 292--chown=permit:permitif permit ownership of /config/kong_routes.json was the intent. - LOW
build_opal_bundle.sh:3— build_opal_bundle.sh uses set -e; a failing find in the tar pipeline is masked (file not present in this PR's diff)
Raised in rounds 2 and 3 and unchanged; deferred in the PR body only. Line 3 isset -eonly, and line 25 isfind * ... -print0 | xargs -0 tar -czf .../custom_opa.tar.gz, so a failingfindis masked and a short file list still yields a green run and a tarball. Dockerfile:135 chooses compiled versus downloaded OPA purely on[ -f /custom/custom_opa.tar.gz ], so what this script leaves behind decides which binary ships. The same pipeline is inlined at tests.yml:69 and release.yml:54 inrun:blocks with noshell:override, which GitHub runs asbash -ewithout pipefail.
Suggestion: Useset -euo pipefail, paired with${PDP_VANILLA:-}at lines 6 and 19 (read bare today, so-uwould abort), and addset -o pipefailto the two workflowrun:blocks. - LOW
test_offline_mode/Dockerfile:1— test_offline_mode base has no version at all, let alone a digest (file not present in this PR's diff)
Raised in rounds 1 to 3 and unchanged; deferred in the PR body only.FROM python:alpinehas no major.minor, no Alpine suite and no digest, so every rebuild of the offline-mode harness (test_offline_mode/docker-compose.yaml builds it for both testers) can land on a different CPython and Alpine with no record of which it ran on. PDP#338 adds a Dependabot docker entry for /test_offline_mode, but a tag with no version gives it nothing to bump. It is a manual harness, and the last unversioned base in the tree (Dockerfile:12, :111, :209 all carry versions).
Suggestion: Give it an explicit tag matching the main image,python:3.13-alpine3.23, and a digest once PDP#338's digest convention lands.
Details are in the inline comments on each line.
… cannot cache (PER-16045) Zeev's 2026-09-22 review: six threads, all accuracy claims about other repos, other PRs, and what a build log proves. - "until this lands" scoped the permit-opa#52 breakage to main. release.yml checks this repo out with no `ref:`, so a release builds the Dockerfile of the commit it was cut from, while permit-opa still comes from `ref: main`; tests.yml also builds on `v*` pushes. v0.9.15 is d3da8b9, still on the 1.25 builder. State the lasting rule instead: cut every release and `v*` build from a commit containing this FROM line. - The x/crypto waivers still spoke of permit-opa#49 as pending. It MERGED 2026-09-16 and permit-opa main carries x/crypto v0.55.0 + grpc v1.83.2. Both statements are past tense now, and say why the superseded 0.53.0 subcomponent PURL stays in the list. VEX doc re-issued as version 12. - The three-builds sentence stated permit-opa#51/#52 outcomes as current; both are open and being edited. Replaced with wording that stays true whatever they land as: permit-opa's toolchain policy lives in permit-opa's own files, not in this comment. - The PDP#338 reword note covered only the float paragraph. #338 also drops docker-scout's `pull_request` gate and adds image-scan-published.yml (daily), so it falsifies the auditability paragraph too - and closes that gap rather than recording it. Its Dependabot entry carries `cooldown: default-days: 7`. - The floor check's echo is a gate, not a record: it caches on the base image digest and reads CACHED in a typical release log. The compile RUN cannot cache (`COPY custom* /custom` sees a tarball the workflow regenerates every run), so echo the toolchain there instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…k is unpinned (PER-16045) `USER permit` before `COPY kong_routes.json` and the `USER root` after it changed nothing: no RUN executes between them, and a COPY without --chown writes root:root whatever USER is set. Same shape as the one this PR already removed. The file stays root-owned, as it was. The floor comment now says the no-tarball fallback downloads OPA `latest` with no --fail and no checksum, so that deferral lives in the file and not only in the PR body. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
@zeevmoney on the eight unanchored findings from your last review. This PR's scope is the OPA builder's move to golang:1.26-bookworm. The deferrals are pre-existing behaviour that this PR doesn't change, and each one is listed under the PR body's follow-ups.
|
zeevmoney
left a comment
There was a problem hiding this comment.
Fifth-round check of this PR after it merged. 3 MEDIUM, 6 LOW. These were not fixed before the merge, or were introduced by commits added after the round-4 approval, so they need a follow-up change. No verdict is submitted on a merged PR.
Fix verification: 14 findings from the 2026-09-22 review. 5 fixed, 3 partly fixed, 6 not fixed
Non-blocking:
- MEDIUM
Dockerfile:166: OPA_BUILD selects the plugin, not which OPA binary ships - LOW
Dockerfile:97: PDP#338 reword note describes a superseded #338 head - LOW
Dockerfile:109: "Both are open" was false on main 16 minutes after merge - LOW
Dockerfile:175:-adefeats the go-build cache mount; opa_build still builds under QEMU - LOW
Dockerfile:175: Vanilla fallback downloadslatestOPA with no --fail and no checksum
5 of the above restate a finding whose fix did not close it.
Findings that could not be anchored to a line inside this PR's diff:
- MEDIUM
.github/workflows/tests.yml:52: permit-opa is still compiled into released images from an unpinned ref: main (line 52 is not inside a diff hunk (nearest diff line: 248))
Not fixed: the workflows are unchanged, and the only response is Dockerfile prose (:79-85) naming this checkout as the cause of the release hazard. tests.yml:52 and release.yml:43 still check out permitio/permit-opa atref: main, so every published pdp-v2 image compiles whatever sits on another repo's default branch, with no record of which commit produced /app/bin/opa. This PR's ordering (Dockerfile:63-64) and the VEX removal gate (.docker/scout/pdp-v2.vex.json:23, :40: 'the next PDP build resolves x/crypto v0.57.0') both rest on that float, so permit-opa#52 merging will change the shipped binary with no PDP diff. release.yml:40 is stillactions/checkout@v3on the step holding CLONE_REPO_TOKEN.
Suggestion: Pin the permit-opa checkout in both workflows to a commit SHA or release tag and bump it deliberately; that also turns the backport rule at Dockerfile:79-82 into an ordinary version bump. Move release.yml:40 to actions/checkout@v4 to match line 26. - MEDIUM
.github/workflows/tests.yml:77: No CI gate builds opa_build for arm64; only the release does (line 77 is not inside a diff hunk (nearest diff line: 248))
Not fixed: no workflow changed, and this PR's arm64 evidence is a manualdocker buildx build --platform linux/arm64 --target opa_build(PR body). tests.yml:77 buildsplatforms: linux/amd64only. release.yml:62 and :117 build amd64 and arm64 and resolveapplication, which copies /app/bin/opa from opa_build (Dockerfile:307). So the stage this PR rewrote (new base tag, GOTOOLCHAIN, floor RUN, new echo) and its floating tag first execute on arm64 under QEMU, inside the job that pushes to Docker Hub. Dockerfile:152-154 records an earlier arm64-only defect in this same stage (#289).
Suggestion: Add a cheap--target opa_build --platform linux/arm64build to tests.yml (it stops before the Rust and Python stages), or add linux/arm64 to build-pdp-image, so a builder change or a moved base digest fails on the PR rather than mid-release. - LOW
build_opal_bundle.sh:3: build_opal_bundle.sh uses set -e; a failing find in the tar pipeline is masked (file not present in this PR's diff)
Not fixed: the script and both workflows are unchanged; deferred in the PR body with no ticket. build_opal_bundle.sh:3 isset -eonly and :25 isfind * ... -print0 | xargs -0 tar -czf .../custom_opa.tar.gz, so a failingfindis masked and a short file list still yields a tarball and a green run. Dockerfile:165 picks compiled versus downloaded OPA purely on that tarball's presence, so this pipeline decides which binary ships. The same pipeline at tests.yml:69 and release.yml:54 runs inrun:blocks with noshell:override, which GitHub runs asbash -ewithout pipefail.
Suggestion: Useset -euo pipefail, reading${PDP_VANILLA:-}at lines 6 and 19 so-udoes not abort, and addshell: bash(which runsbash -eo pipefail) or an explicitset -o pipefailto the two workflowrun:blocks. - LOW
test_offline_mode/Dockerfile:1: test_offline_mode base has no version at all, let alone a digest (file not present in this PR's diff)
Not fixed: the file is unchanged; the PR body defers it with no ticket.FROM python:alpinehas no major.minor, no Alpine suite and no digest, so each rebuild of the offline-mode harness (test_offline_mode/docker-compose.yaml builds it) can land on a different CPython and Alpine with no record of which one ran. PDP#338 (open) adds a weekly Dependabot docker entry for /test_offline_mode, but a tag with no version gives it nothing to bump. It is the last unversioned base in the tree; Dockerfile:12, :141 and :243 all carry versions.
Suggestion: Use an explicit tag matching the main image,python:3.13-alpine3.23, and add a digest once PDP#338's digest convention lands.
Details are in the inline comments on each line.
|
@zeevmoney the 4 findings from your review body that had no line to anchor to are all fixed in #345 (374c0cc):
|
Summary
The Dockerfile's
opa_buildstage moves fromgolang:1.25-bookwormtogolang:1.26-bookworm, setsGOTOOLCHAIN=localitself, and fails the build below the go1.26.6 floor. The x/crypto waiver texts are updated to match. This has to land before permit-opa raises itsgodirective to 1.26 (permitio/permit-opa#52), because that change hard-fails the current builder.Why
PER-16045 (Decision 3: fix the expected x/crypto/ssh waiver rather than keep it). The same change was first proposed as PDP#334 under PER-15358, which was closed unmerged on 2026-09-16. No open PR does it, so it is re-proposed here. Before this PR,
.docker/scout/pdp-v2.vex.jsonand thetests.ymlcomment named PDP#334 as the removal gate.golang.org/x/crypto≥ 0.56.0 clears CVE-2026-78662 and CVE-2026-56855 (x/crypto/ssh), which are currently waived asnot_affectedin.docker/scout/pdp-v2.vex.json. Its go.mod declaresgo 1.26.0, so permit-opa's directive has to move with it.tests.ymlandrelease.ymlbuild permit-opa from an unpinnedref: maincheckout. The official golang images setGOTOOLCHAIN=local, so this builder does not fetch a newer toolchain. It fails:build-pdp-imageon every PR, every push to main and every release, from the moment the permit-opa change merges.go 1.25.0module is forward-compatible, so this PR is safe on its own.What changed
Dockerfile:FROM golang:1.25-bookworm AS opa_build→FROM golang:1.26-bookworm AS opa_build, with a comment recording the ordering constraint.ENV GOTOOLCHAIN=localin the stage, so the hard-fail does not depend on the base image setting it.RUNthat fails the build ifgo env GOVERSIONis below go1.26.6, the first 1.26 release with the crypto/tls fix for GO-2026-6090 (checked on vuln.go.dev). It has the same shape as the CPython floor check inmain. The tag floats its patch version (go1.26.8 today), so this catches a stale local image, or a merge conflict with ci: gate releases on CVEs and unify Dependabot, Trivy and Docker Scout (PER-15358) #338 that restores 1.25./app/bin/opa. Changes that come with the 1.26 toolchain land with this builder (e.g. the Green Tea GC is on by default). GODEBUG defaults follow permit-opa's go.mod instead. The OPA build isCGO_ENABLED=0, so the builder's glibc never reaches the image.-s -win the binary's build info and is readable withgo versionon an extracted copy, but that is an after-the-fact audit, not a gate: the only scanner in this repo is thepull_request-onlydocker-scoutjob pointed at a local tag, so no published pdp-v2 tag is re-scanned today. PDP#338 is what closes that gap.RUNnow echoesgo env GOVERSION. The floorRUNis the gate, and it caches on the base image digest - in a release log it typically readsCACHED, so it is not the record of what compiled that image.COPY custom* /customsees a tarball the workflow regenerates every run, so the compileRUNnever caches and its echo is always in the log of the build that produced the binary.release.yml: theno-cache-filterscomment no longer saysopa_build"re-executes unconditionally". It re-executes fromCOPY custom*on; the two layers before it cache on the base image digest.Dockerfile: dropped two deadUSERswitches: theUSER permitafterCOPY ./horizon, and theUSER permit/USER rootpair aroundCOPY kong_routes.json(no RUN between them; aCOPYwithout--chownwrites root:root regardless)..docker/scout/pdp-v2.vex.json(version 12) and thetests.ymlscout comment: the two x/crypto waivers no longer say the builder is 1.25 or name the closed PDP#334 as their removal gate. The remaining gate is permitio/permit-opa#52. permitio/permit-opa#49 is stated in the past tense (merged 2026-09-16; permit-opamaincarries x/crypto v0.55.0 and grpc v1.83.2), and both statements now say why the superseded@0.53.0subcomponent PURL stays in the list instead of being swapped out.How validated
All three runs build only the
opa_buildstage (docker buildx build --platform linux/arm64 --target opa_build), withcustom/custom_opa.tar.gzpackaged exactly astests.ymldoes (find * \( -name '*go*' -o -name 'LICENSE.md' \) … tar -czf):golang:1.26-bookworm)main(go 1.25.0)/opa version→ OPA 1.14.1, go1.26.8golang:1.26-bookworm)go 1.26.0, x/crypto v0.57.0)/opa version→ go1.26.8. Trivy (CRITICAL/HIGH/MEDIUM) on the binary: 0 Critical/High, 1 Medium (containerd CVE-2026-53495; permit-opa#52 has since bumped containerd/v2 to v2.2.8, and the binary built in round 4 carries it)origin/mainDockerfile (golang:1.25-bookworm)go: go.mod requires go >= 1.26.0 (running go 1.25.14; GOTOOLCHAIN=local). This is the failure this PR prevents.After the review follow-up (139a4c1), with permit-opa#52's source:
opa_buildbuilds;/opais go1.26.8, x/crypto v0.57.0,CGO_ENABLED=0. Mutation check: with the builder set back togolang:1.25-bookworm, the new floor check fails the build (go1.25.14 is below the go1.26.6 floor). The floor expression passes 1.26.6, 1.26.8, 1.26.10 and 1.27.1, and fails 1.25.14, 1.26.0 and 1.26.5. pre-commit is clean on the changed files; actionlint ontests.ymlreports the same 3 pre-existing findings as before.Round 3 (f867aad) is comments plus two one-liners, checked without a build: permit-opa#52's
check-pdp-builder.shstill parses theFROM … AS opa_buildline (that parse is now called out in the comment above it, because an ARG or a line split would turn permit-opa's CI red); the floorRUNchain run through/bin/shprints on go1.26.8 and go1.26.6 and still exits 1 on go1.26.5 and go1.25.14; and the derived ensurepip path evaluates correctly through/bin/shwith the exact quoting used in the file. No Docker daemon was available on this machine, so the image itself was not rebuilt this round.After the second review follow-up (5d84477), with permit-opa#52 at 31d5dda (x/crypto v0.57.0, containerd v2.2.8) packaged the same way:
opa_build(linux/arm64) builds and the floor check passes./opais go1.26.8, x/crypto v0.57.0, containerd/v2 v2.2.8,CGO_ENABLED=0, with no interpreter orNEEDEDentry.go version -mreads all of that from the stripped (-s -w) binary.docker buildx build --checkreports the same single pre-existing warning (SecretsUsedInArgOrEnv) before and after. actionlint onrelease.ymlreports the same 2 pre-existing findings before and after. pre-commit is clean.Round 4 (273ca8f) is Zeev's six accuracy threads: five comment/PR-body corrections plus one one-line
echo. A Docker daemon WAS available this round.docker buildx build --platform linux/arm64 --target opa_buildagainst permit-opa#52 at 8d44183 (go 1.26.0, x/crypto v0.57.0) builds; the floor check printsopa_build: building with go1.26.8and the new compile-RUN echo printsopa_build: compiling permit-opa with go1.26.8. Cache split reproduced: re-running with a regenerated tarball gives#6 CACHEDfor the floor layer (no version printed) while the compileRUNre-executes and prints the toolchain - which is the claim the reworded comment makes.go version -mon the extracted binary:go1.26.8,golang.org/x/crypto v0.57.0,github.com/containerd/containerd/v2 v2.2.8,CGO_ENABLED=0,GOARCH=arm64.docker buildx build --check --target opa_buildreports no warnings.jq -e .on the VEX doc is valid (version 12, 7 statements). pre-commit is clean on both changed files.Not run locally: the full multi-arch image or the scout gate. CI's
build-pdp-image,docker-scoutandpdp-testercover them.Merge order / dependencies
mainafter it merges.go 1.26+ x/crypto v0.57.0), which says DO NOT MERGE before this one. Itspdp-builderCI check reads this repo'sDockerfileonmainand fails until this PR has merged. PDP builds permit-opa fromref: main, so the next PDP build after [Snyk] Security upgrade aiohttp from 3.7.2 to 3.8.0 #52 merges has x/crypto v0.57.0. No permit-opa release is needed for pdp-v2. The release and the cloud-pdp.permit-opa-versionbump in [Snyk] Security upgrade aiohttp from 3.7.2 to 3.8.0 #52's step 4 are for the Nexus/edge image..docker/scout/pdp-v2.vex.jsonand the matchingtests.ymlcomment.Merging this fixes
main, not the tags.release.ymlchecks THIS repo out with noref:(:25-26) while still taking permit-opa fromref: main(:40-43), andtests.ymlalso builds on any push to av*branch (:6). The newest tag, v0.9.15, is d3da8b9 - this PR's base - whoseopa_buildis stillgolang:1.25-bookworm. So the lasting rule, for as long as the permit-opa checkout is unpinned: once permit-opa#52 merges, every pdp-v2 release and everyv*branch build must come from a commit containing thisFROMline. Backport it before cutting a hotfix, or re-running a release, from an older tag. permit-opa#52'scheck-pdp-builder.shhands the releaser exactly this rule and says it cannot enforce it, because it only ever reads this repo'sDockerfileonmain. Pinning the permit-opa checkout to a tag removes the class.Conflict note: #338 (open) pins this same
FROMline by digest (golang:1.25-bookworm@sha256:…). Whichever PR lands second should keep 1.26 and take #338's digest convention:docker buildx imagetools inspect golang:1.26-bookwormgives the manifest-list digest.Related, same ticket: permitio/permit-opa#51 (pinned release toolchain), permitio/cloud-pdp#161, permitio/cloud-pdp#162.
Risks / follow-ups
pdp-testerpasses on the built image; watch memory on the first release.ref: main) intests.yml/release.yml, andrelease.yml'sactions/checkout@v3;opa_buildPR job (this change was validated on linux/arm64 locally; CI builds amd64);latest, no--fail, no checksum), andOPA_BUILDnot deciding which OPA ships;-adefeating the Go build cache;pipefailfor the tarball pipeline inbuild_opal_bundle.sh,tests.ymlandrelease.yml;test_offline_mode's unversionedpython:alpinebase.python3.13ensurepip path. It contradicted the CPython floor check 34 lines above, which branches for 3.13 and 3.14; withrm -r(no-f) the build would have failed the day the base tag moved. The path now comes fromsysconfig.get_paths()["stdlib"], which resolves to the same location on a stock CPython under/usr/localand tracks the base.GOTOOLCHAIN=localbreaks the same way. permit-opa's own Dockerfile is bumped in that PR.🤖 Generated with Claude Code