Skip to content

ci: exclude the pdp-tester policy.* cases from this job - #336

Merged
omer9564 merged 1 commit into
mainfrom
omer/per-15775-exclude-policy-cases
Sep 8, 2026
Merged

omer9564 merged 1 commit into
mainfrom
omer/per-15775-exclude-policy-cases

Conversation

@omer9564

@omer9564 omer9564 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

One line: excludes the new pdp-tester policy.* cases from this repo's pdp-tester job. Companion to permitio/pdp-tester#152 (PER-15775).

Why this repo needs a change at all

.github/workflows/tests.yml checks out permitio/pdp-tester with no pinned ref:, so it tracks that repo's main, and then runs:

python -m pdp_tester --docker --local --tag next --skip-generate

with no include or exclude list — which means it runs every test case it discovers. So a new case added in pdp-tester lands here automatically, on the next run, without anything in this repo changing.

What is landing there

pdp-tester#152 adds five policy.* cases. Unlike every existing case, they create policy schema during the test — roles, resource types, and relations on the stock File/Folder types — and only then the facts that depend on it. That is the point: facts ride the WAL while schema and rego ride POLICY_DATA/POLICY_FILES, so a PDP with a stale policy plane answers the existing suite correctly and stays green.

For this job it would mean schema writes into the shared staging Permit environment on every run, each triggering a rego rebuild pushed to every PDP in that environment — on top of roughly +50s of runtime from the propagation waits those cases need. Nothing here needs that coverage: the cases are meant for the pdp-tester and Cloud PDP releases, which name them explicitly.

Merge order

No constraint. This is safe to merge before or after pdp-tester#152 — EXCLUDE_TESTS is a pattern, so it costs nothing while the cases do not exist, and is already in place if they land first.

🤖 Generated with Claude Code

This job checks out permitio/pdp-tester with no pinned ref, so it tracks that
repo's main, and runs 'python -m pdp_tester --docker --local --tag next
--skip-generate' with no include or exclude list -- meaning it runs every test
case it discovers.

pdp-tester#152 adds five policy.* cases that create policy SCHEMA during the
test (roles, resource types, relations on the stock File/Folder types) rather
than only facts, and each such write triggers a rego rebuild pushed to every
PDP in the environment. Without this line, this job would start doing that on
the shared staging environment the moment those cases merged, having never
opted in.

Those cases are meant to run on the pdp-tester and Cloud PDP releases, which
name them explicitly. Nothing here needs them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 8, 2026 11:22
@linear-code

linear-code Bot commented Sep 8, 2026

Copy link
Copy Markdown

PER-15775

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The change is narrowly scoped to CI configuration, is consistent with the PR description, and introduces no apparent behavioral risk beyond the intended test exclusion.

Pull request overview

Updates the CI pdp-tester GitHub Actions job to exclude newly added policy.* test cases from running in this repository’s workflow, preventing unintended policy-schema mutations and extra runtime in the shared staging environment as permitio/pdp-tester main evolves.

Changes:

  • Adds EXCLUDE_TESTS: '["policy.*"]' to the pdp-tester job step environment.
  • Documents in-line why the exclusion is required and links the upstream context (permitio/pdp-tester#152).
File summaries
File Description
.github/workflows/tests.yml Excludes policy.* pdp-tester cases via env var to avoid schema churn in shared staging during CI runs.
Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🔍 Vulnerabilities of permitio/pdp-v2:next

📦 Image Reference permitio/pdp-v2:next
digestsha256:93f806e7f6d631dd28e0f7af164183d1da7d7b1e0cad34662fc4229a162332c6
vulnerabilitiescritical: 0 high: 7 medium: 3 low: 1 unspecified: 1
platformlinux/amd64
size139 MB
packages247
📦 Base Image python:3.13-alpine3.23
also known as
  • 3.13.15-alpine3.23
  • b3fdbd3f0fb569f6c053a23e74b6e99a856bf9efe5823bca837a80a875ee7a9f
digestsha256:c694844958d415b76979bb3e0927273b013294ab36e715ca1fc4962713a8ee69
vulnerabilitiescritical: 0 high: 6 medium: 2 low: 0 unspecified: 3
critical: 0 high: 3 medium: 0 low: 0 unspecified: 1golang.org/x/crypto 0.53.0 (golang)

pkg:golang/golang.org/x/crypto@0.53.0

high : CVE--2026--78662

Affected range<0.56.0
Fixed version0.56.0
EPSS Score0.315%
EPSS Percentile24th percentile
Description

Previously, a channel registered in the mux's chanList is not usable until it is established. A malicious peer was able flood the channel's incomingRequests, deadlocking the entire connection.

Now, we add an atomic established state, set when a channel becomes usable. Until such a time, handlePacket drops every packet other than the open confirmation/failure, without blocking and without tearing down the connection.

high : CVE--2026--56855

Affected range<0.56.0
Fixed version0.56.0
EPSS Score0.378%
EPSS Percentile31st percentile
Description

Previously, after a channel has been established, a malicious peer could send crafted messages that would deadlock the entire connection.

Now, we handle all RFC 4254 channel messages; global requests are handled explicitly. Then, treat all other messages as a protocol error and tear the connection down instead of buffering and blocking.

high : CVE--2026--56854

Affected range<0.55.0
Fixed version0.55.0
EPSS Score0.325%
EPSS Percentile25th percentile
Description

The source-address critical option in the Permissions returned by an authentication callback was only enforced for the PublicKeyCallback and VerifiedPublicKeyCallback paths, extending the fix for CVE-2026-46595. Permissions returned by the PasswordCallback, KeyboardInteractiveCallback, NoClientAuthCallback, and GSSAPIWithMICConfig.AllowLogin callbacks were not validated against the client's remote address, so a source-address restriction set by those callbacks was silently ignored. The check is now applied to the Permissions returned by any authentication callback.

unspecified : GO--2026--5932

Affected range>=0
Fixed versionNot Fixed
Description

The golang.org/x/crypto/openpgp package is unsafe by design, has numerous known security issues, is not maintained, and should not be used.

If you are required to interoperate with OpenPGP systems and need a maintained package, consider github.com/ProtonMail/go-crypto/openpgp which is a maintained fork that aims to be a drop-in replacement for this package.

critical: 0 high: 2 medium: 2 low: 1 starlette 0.50.0 (pypi)

pkg:pypi/starlette@0.50.0

high 7.5: CVE--2026--54283 Allocation of Resources Without Limits or Throttling

Affected range>=0.4.1
<1.3.1
Fixed version1.3.1
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Score0.397%
EPSS Percentile33rd percentile
Description

Summary

request.form() accepts max_fields and max_part_size to bound resource consumption while parsing form data. These limits are enforced for multipart/form-data, but silently ignored for application/x-www-form-urlencoded. An unauthenticated attacker can therefore send a urlencoded body with an arbitrarily large number of fields or an arbitrarily large field, even when the application configured limits it believed would apply.

Details

request.form() dispatches to a different parser depending on the Content-Type. For multipart/form-data the max_files, max_fields, and max_part_size limits are forwarded to the parser, but for application/x-www-form-urlencoded the parser is constructed without them. It has no max_fields or max_part_size parameter to receive them, and it appends every field with no count check and accumulates each field's name and value with no size check. The configured limits are therefore both unreachable and unenforced for url-encoded bodies.

Because the url-encoded parser does its work synchronously between stream reads, the two attack shapes have different effects:

  • Field count drives CPU and event-loop blocking. A body of ~1,000,000 fields (a sub-10MB payload such as f0=v&f1=v&...) blocks the worker's event loop for several seconds while parsing, during which the worker serves no other request.
  • Field size drives memory. A single large field value (e.g. a 50MB value) is buffered in full to build the FormData, forcing memory allocation proportional to the request body.

The equivalent multipart/form-data request is correctly rejected with 400 Too many fields / 400 Field exceeded maximum size.

Impact

This Denial of service (DoS) vulnerability affects all applications built with Starlette (or FastAPI) that call request.form() on application/x-www-form-urlencoded requests. A single request with a very large number of fields blocks the event loop for several seconds, and a single request with a very large field forces unbounded memory allocation; in either case, parallel requests can render the service unusable. A reverse proxy that enforces a request body size limit reduces but does not eliminate the exposure, since a sub-10MB body is already enough to block the event loop.

Mitigation

Upgrade to a patched version, which forwards max_fields and max_part_size to the url-encoded parser and enforces them while parsing, raising before the oversized field or excess fields are accumulated. The defaults match multipart/form-data (max_fields=1000, max_part_size=1MB) and can be customized via request.form(max_fields=..., max_part_size=...).

high 7.5: CVE--2026--48818 Server-Side Request Forgery (SSRF)

Affected range<1.1.0
Fixed version1.1.0
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
EPSS Score0.368%
EPSS Percentile30th percentile
Description

Summary

When serving static files on Windows, StaticFiles resolves the requested path with os.path.realpath. If a UNC path (such as \\attacker.com\share) reaches the resolver, realpath causes the process to open a connection to the remote host over SMB (port 445). This is a server-side request forgery (SSRF) that leaks the service account's NTLMv2 credentials to the attacker-controlled host, which can then be cracked offline or relayed to other hosts.

Details

StaticFiles.lookup_path() joins the requested path onto the served directory and calls os.path.realpath on the result before checking containment with os.path.commonpath. On Windows, a UNC path is absolute, so os.path.join discards the served directory and realpath resolves the bare UNC path, triggering the outbound SMB connection and NTLM authentication before the containment check rejects the path. The HTTP response is a benign 404, but the credential disclosure has already happened. POSIX systems are not affected.

This only affects the default configuration (follow_symlink=False), which uses os.path.realpath. The follow_symlink=True branch uses os.path.abspath, which performs no I/O.

Impact

Applications running on Windows that serve files with StaticFiles (directly, or via a framework built on Starlette such as FastAPI) in the default configuration are affected. StaticFiles is typically unauthenticated, so any client can trigger the SMB connection and leak the service account's NTLMv2 hash. A secondary impact is discovering internal hosts reachable over SMB by timing responses for valid versus invalid addresses.

Mitigation

Applications not running on Windows are not affected. On Windows, serving static files through a dedicated web server (such as nginx or IIS) instead of StaticFiles avoids the issue. Blocking outbound SMB (port 445) from the application host prevents the credential disclosure even if a UNC path is resolved.

medium 6.5: CVE--2026--48710 Improper Validation of Unsafe Equivalence in Input

Affected range<=1.0.0
Fixed version1.0.1
CVSS Score6.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
EPSS Score36.257%
EPSS Percentile98th percentile
Description

Summary

In affected versions, the HTTP Host request header was not validated before being used to reconstruct request.url. Because the routing algorithm relies on the raw HTTP path while request.url is rebuilt from the Host header, a malformed header could make request.url.path differ from the path that was actually requested. Middleware and endpoints that apply security restrictions based on request.url (rather than the raw scope path) could therefore be bypassed.

Details

When a client requests http://example.com/foo, it sends:

GET /foo HTTP/1.1
Host: example.com

Affected versions reconstructed the URL by concatenating http://{host}{path} and re-parsing the result. The Host value is only valid as a uri-host [ ":" port ] per RFC 9112 §3.2, where uri-host follows the restricted host grammar of RFC 3986 §3.2.2. When it contains characters outside that grammar - notably /, ?, or # - those characters move the path/query/fragment boundaries during re-parsing, so the parsed request.url.path no longer matches the path the server actually received. For example:

GET /foo HTTP/1.1
Host: example.com/abc?bar=

reconstructs to http://example.com/abc?bar=/foo, whose parsed path is /abc - even though routing used the real path /foo. The router still dispatches to /foo and the endpoint executes, but any middleware or code that reads request.url.path sees /abc, so path-based authorization checks can be bypassed.

Impact

Any application running an affected version that relies on request.url (or request.url.path) for security-sensitive decisions is affected. The most common case is middleware that gates access to certain path prefixes based on request.url.path. Deployments fronted by a proxy or load balancer are mitigated only if that proxy rejects or normalizes the malformed Host header before forwarding and the application does not trust attacker-controlled host headers (e.g. X-Forwarded-Host) elsewhere.

Mitigation

Upgrade to a patched version, which validates the Host header against the grammar of RFC 9112 §3.2 / RFC 3986 §3.2.2 when constructing request.url and falls back to scope["server"] for malformed values.

medium 5.3: CVE--2026--48817 Use of Externally-Controlled Input to Select Classes or Code ('Unsafe Reflection')

Affected range<1.1.0
Fixed version1.1.0
CVSS Score5.3
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Score0.213%
EPSS Percentile12th percentile
Description

Summary

When dispatching a request, HTTPEndpoint selects the handler by lowercasing the HTTP method and looking it up as an attribute with getattr, without restricting the lookup to a known set of HTTP verbs.

When an HTTPEndpoint subclass is registered through Route(...) without an explicit methods= argument, the route does not constrain the method and every method reaches the endpoint. If a non-standard HTTP method whose lowercased name matches an attribute on the endpoint subclass reaches the endpoint, that attribute is invoked as if it were a request handler. An attacker can use this to reach methods that were never meant to be HTTP handlers, such as internal helpers, without the authorization checks applied by the intended public handler.

Details

HTTPEndpoint uses the client-supplied method name to resolve an instance attribute, without validating it against the set of HTTP verbs the endpoint supports. A method such as _DO_DELETE therefore resolves an attribute like _do_delete and invokes it. Non-standard methods are valid RFC 9110 token methods, so an endpoint must not treat the method name as a trusted attribute selector.

Impact

An application is affected when all of the following hold:

  • It defines an HTTPEndpoint subclass and registers it via Route(...) without an explicit methods= argument.
  • The subclass defines additional methods whose names match a non-standard HTTP-method token shape and that accept a single request argument and return a response.

This also affects frameworks built on Starlette, like FastAPI.

Mitigation

Register HTTPEndpoint subclasses with an explicit methods= argument on the Route, listing only the HTTP verbs the endpoint supports. The route then rejects any other method with 405 Method Not Allowed before it reaches the endpoint, so non-standard methods cannot resolve an attribute.

low 3.7: CVE--2026--54282 Improper Input Validation

Affected range<1.3.0
Fixed version1.3.0
CVSS Score3.7
CVSS VectorCVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:L/A:N
EPSS Score0.187%
EPSS Percentile8th percentile
Description

Summary

In affected versions, the HTTP request path is not validated before being used to reconstruct request.url. Because request.url is rebuilt by concatenating {scheme}://{host}{path} and re-parsing the result, a path that does not begin with / (for example @<!-- -->google.com) moves the authority boundary during re-parsing, so request.url.hostname and request.url.netloc become attacker-controlled. Code that reads request.url.hostname (rather than the Host header or scope) can therefore be misled into trusting an attacker-supplied host.

Details

When a client requests a path that does not start with /:

GET @<!-- -->google.com HTTP/1.1
Host: localhost

affected versions reconstruct the URL as http://localhost@<!-- -->google.com. Per RFC 3986 §3.2.1, the substring before @ in the authority is userinfo, so re-parsing yields username = "localhost" and hostname = "google.com", with an empty path:

request.url          == "http://localhost@<!-- -->google.com"
request.url.hostname == "google.com"
request.url.path     == ""

The root cause is that the path is concatenated directly after the host without a separating /, and without validating that it begins with one. Only the Host header was validated when constructing request.url; the path was not.

This requires an ASGI server that forwards a request-target lacking a leading / into scope["path"].

Impact

Any application running an affected version that uses request.url, request.url.netloc, or request.url.hostname for a security-sensitive decision (host-based authorization, redirect/callback base, SSRF target, cache key, audit log) may be affected, when no fronting proxy or load balancer rejects the malformed request-target first.

Note that this is less exploitable than GHSA-86qp-5c8j-p5mr: there, the poison is carried in the Host header, so the real path still routes to a valid endpoint while request.url.path lies. Here, the poison must be carried in the path itself, and that path (@<!-- -->google.com) does not match any registered route, so routing returns 404 and no endpoint handler runs. The exposure is limited to code that reads request.url before routing - notably middleware - or in 404/exception handlers.

Mitigation

Upgrade to a patched version, which prevents the request path from crossing into the URL authority. The request above instead yields http://localhost/@<!-- -->google.com with request.url.hostname == "localhost".

critical: 0 high: 1 medium: 0 low: 0 ddtrace 3.19.8 (pypi)

pkg:pypi/ddtrace@3.19.8

high 7.5: CVE--2026--50271 Uncontrolled Resource Consumption

Affected range<4.8.2
Fixed version4.8.2
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
EPSS Score0.793%
EPSS Percentile54th percentile
Description

Impact

Datadog tracing libraries that implement W3C baggage propagation parse incoming baggage HTTP headers without enforcing item-count or byte-size limits on the extract path. The DD_TRACE_BAGGAGE_MAX_ITEMS (default 64) and DD_TRACE_BAGGAGE_MAX_BYTES (default 8192) limits were applied only to baggage injection, not extraction. A remote, unauthenticated attacker can send a request whose baggage header contains an arbitrarily large number of comma-separated key-value pairs (or a single very large value). The tracer allocates a hash-map entry for each pair on every request, causing unbounded CPU and memory consumption and enabling a remote Denial of Service against any HTTP service that has the baggage propagation style enabled.
The baggage propagation style is enabled by default in most affected tracers, so any internet-facing service that has been instrumented with an affected tracer version is exposed unless the propagation style has been explicitly narrowed.

Patches

This is resolved in version 4.8.2 and later of the dd-trace-py library

Workarounds

If users cannot upgrade immediately:

  1. Disable baggage extraction by removing baggage from DD_TRACE_PROPAGATION_STYLE (or DD_TRACE_PROPAGATION_STYLE_EXTRACT if set independently).
  2. Cap the maximum HTTP request header size at an upstream proxy or web server (for example, Apache LimitRequestFieldSize, Nginx large_client_header_buffers, Envoy max_request_headers_kb).

Resources

Related upstream advisories:
opentelemetry-go GHSA-mh2q-q3fh-2475
opentelemetry-dotnet GHSA-g94r-2vxg-569j

critical: 0 high: 1 medium: 0 low: 0 google.golang.org/grpc 1.82.1 (golang)

pkg:golang/google.golang.org/grpc@1.82.1

high 8.7: CVE--2026--84304 Uncontrolled Resource Consumption

Affected range<=1.83.0
Fixed version1.83.1
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.415%
EPSS Percentile35th percentile
Description

Impact

An unauthenticated remote attacker can initiate a gRPC stream and purposefully fragment their payload into millions of tiny (e.g., 1-byte) HTTP/2 DATA frames. Even if the total payload volume falls within the configured connection and stream flow-control windows, each independent fragment incurs memory overhead due to internal tracking structures and queue allocation.

Repeated fragmentation massively inflates the heap space consumed by the stream. An attacker multiplexing multiple concurrent streams can exhaust the memory bounds of the runtime, forcing a runtime panic or OutOfMemory condition and leading to a remote Denial of Service (DoS).

Patches

The change to fix this issue is merged in master and a patch release, 1.83.1, has been published that contains this fix.

Workarounds

This vulnerability is mitigated by implementing receive buffer compaction. Consecutive small data buffers are automatically coalesced into larger buffers from a shared pool once the overhead is perceived to be excessive relative to actual payload data, drastically minimizing per-frame memory overheads.

This behavior is enabled by default. A temporary escape hatch is provided via the environment variable GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION=false to disable the feature if unforeseen issues arise, but it will be removed in a future release.

critical: 0 high: 0 medium: 1 low: 0 busybox 1.37.0-r30 (apk)

pkg:apk/alpine/busybox@1.37.0-r30?os_name=alpine&os_version=3.23

medium : CVE--2025--60876

Affected range<=1.37.0-r30
Fixed versionNot Fixed
EPSS Score0.291%
EPSS Percentile21st percentile
Description

@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown

🔍 Vulnerabilities of permitio/pdp-v2:next

📦 Image Reference permitio/pdp-v2:next
digestsha256:93f806e7f6d631dd28e0f7af164183d1da7d7b1e0cad34662fc4229a162332c6
vulnerabilitiescritical: 0 high: 4 medium: 0 low: 0
platformlinux/amd64
size139 MB
packages247
📦 Base Image python:3.13-alpine3.23
also known as
  • 3.13.15-alpine3.23
  • b3fdbd3f0fb569f6c053a23e74b6e99a856bf9efe5823bca837a80a875ee7a9f
digestsha256:c694844958d415b76979bb3e0927273b013294ab36e715ca1fc4962713a8ee69
vulnerabilitiescritical: 0 high: 6 medium: 2 low: 0 unspecified: 3
critical: 0 high: 3 medium: 0 low: 0 golang.org/x/crypto 0.53.0 (golang)

pkg:golang/golang.org/x/crypto@0.53.0

high : CVE--2026--78662

Affected range<0.56.0
Fixed version0.56.0
EPSS Score0.315%
EPSS Percentile24th percentile
Description

Previously, a channel registered in the mux's chanList is not usable until it is established. A malicious peer was able flood the channel's incomingRequests, deadlocking the entire connection.

Now, we add an atomic established state, set when a channel becomes usable. Until such a time, handlePacket drops every packet other than the open confirmation/failure, without blocking and without tearing down the connection.

high : CVE--2026--56855

Affected range<0.56.0
Fixed version0.56.0
EPSS Score0.378%
EPSS Percentile31st percentile
Description

Previously, after a channel has been established, a malicious peer could send crafted messages that would deadlock the entire connection.

Now, we handle all RFC 4254 channel messages; global requests are handled explicitly. Then, treat all other messages as a protocol error and tear the connection down instead of buffering and blocking.

high : CVE--2026--56854

Affected range<0.55.0
Fixed version0.55.0
EPSS Score0.325%
EPSS Percentile25th percentile
Description

The source-address critical option in the Permissions returned by an authentication callback was only enforced for the PublicKeyCallback and VerifiedPublicKeyCallback paths, extending the fix for CVE-2026-46595. Permissions returned by the PasswordCallback, KeyboardInteractiveCallback, NoClientAuthCallback, and GSSAPIWithMICConfig.AllowLogin callbacks were not validated against the client's remote address, so a source-address restriction set by those callbacks was silently ignored. The check is now applied to the Permissions returned by any authentication callback.

critical: 0 high: 1 medium: 0 low: 0 google.golang.org/grpc 1.82.1 (golang)

pkg:golang/google.golang.org/grpc@1.82.1

high 8.7: CVE--2026--84304 Uncontrolled Resource Consumption

Affected range<=1.83.0
Fixed version1.83.1
CVSS Score8.7
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
EPSS Score0.415%
EPSS Percentile35th percentile
Description

Impact

An unauthenticated remote attacker can initiate a gRPC stream and purposefully fragment their payload into millions of tiny (e.g., 1-byte) HTTP/2 DATA frames. Even if the total payload volume falls within the configured connection and stream flow-control windows, each independent fragment incurs memory overhead due to internal tracking structures and queue allocation.

Repeated fragmentation massively inflates the heap space consumed by the stream. An attacker multiplexing multiple concurrent streams can exhaust the memory bounds of the runtime, forcing a runtime panic or OutOfMemory condition and leading to a remote Denial of Service (DoS).

Patches

The change to fix this issue is merged in master and a patch release, 1.83.1, has been published that contains this fix.

Workarounds

This vulnerability is mitigated by implementing receive buffer compaction. Consecutive small data buffers are automatically coalesced into larger buffers from a shared pool once the overhead is perceived to be excessive relative to actual payload data, drastically minimizing per-frame memory overheads.

This behavior is enabled by default. A temporary escape hatch is provided via the environment variable GRPC_GO_EXPERIMENTAL_ENABLE_RECEIVE_BUFFER_COMPACTION=false to disable the feature if unforeseen issues arise, but it will be removed in a future release.

@omer9564
omer9564 merged commit 9a1f75e into main Sep 8, 2026
8 of 10 checks passed
@omer9564
omer9564 deleted the omer/per-15775-exclude-policy-cases branch September 8, 2026 13:05
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
…5358)

The docker-scout gate on this PR fails on 5 HIGH that do NOT come from this
repo: /app/bin/opa is compiled from permitio/permit-opa, so its Go module CVEs
land in our scans. The gate has been red on them since 2026-09-06 - #333 and
#336 both went red and were merged anyway - so this is pre-existing drift, not
a regression from this PR. It matters now because the companion PR makes the
gate block releases.

Split by what is actually fixable:

  permitio/permit-opa#49 bumps grpc 1.80.0 -> 1.83.2 and x/crypto 0.53.0 ->
  0.55.0, clearing 3 of the 5 (CVE-2026-84304, CVE-2026-84445, CVE-2026-56854).
  Note 1.83.1 is NOT enough for CVE-2026-84445 - its affected range covers
  >=1.83.0 <1.83.2 - which only showed up by scanning the rebuilt binary.

  The remaining two, CVE-2026-78662 and CVE-2026-56855, are fixed only in
  x/crypto >= 0.56.0, which declares `go >= 1.26.0`. Go 1.26 is not released
  (there is no golang:1.26 image) and this image builds on golang:1.25-bookworm,
  so that upgrade does not exist yet. Waived here as not_affected /
  vulnerable_code_not_in_execute_path, on evidence rather than assertion: both
  live in golang.org/x/crypto/ssh, and `go list -deps ./cmd/opa` in permit-opa
  shows x/crypto/ssh is NOT among the linked x/crypto packages (cryptobyte,
  chacha20(+poly1305), pbkdf2, curve25519, blake2b, salsa20, nacl/box,
  nacl/secretbox, hkdf, sha3). The SSH transport is never linked into the
  binary, and the PDP speaks no SSH of its own. Each statement names the
  removal gate: drop it when a Go 1.26 toolchain lands.

vex.json v5 -> v6. Trivy does not currently flag these two at module level, so
the .trivyignore.yaml counterparts are added in the companion PR to keep the
two waiver files saying the same thing.

NOTE ON CI: this PR cannot go green until permitio/permit-opa#49 merges, because
tests.yml clones permit-opa@main at build time to produce custom_opa.tar.gz.
Once #49 is in, the two grpc CVEs disappear from the build and these two waivers
cover the rest. Verified locally: trivy on the rebuilt OPA binary with grpc
1.83.2 + x/crypto 0.55.0 reports 0 CRITICAL/HIGH, down from 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EliMoshkovich added a commit that referenced this pull request Sep 11, 2026
While checking CI on this series, security/snyk (permit) reported ERROR - not a
clean result, a failure to run - on #330, #333, #336 and both PRs here. It has
been broken long enough to read as normal.

That makes three vulnerability scanners attached to this repo, none of which
caught the 0.9.14 drift: Scout could not see past the build date, Dependabot's
6 open alerts went unread, and Snyk never ran at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
zeevmoney added a commit that referenced this pull request Sep 16, 2026
…(PER-15358) (#337)

* fix: clear all 12 CVEs from customer scan of published 0.9.14 (PER-15358)

A customer CPE scan of permitio/pdp-v2:0.9.14 on 2026-09-09 reported 21 findings
/ 12 unique CVEs, all OS packages, flagged 2 CRITICAL + 10 HIGH.

Verified: the published tag really does contain them, but NOT because of a source
defect. 0.9.14 was built 2026-08-04 and froze libcrypto3/libssl3 3.5.7-r0 and
libuuid 2.41.4-r0. Alpine 3.23 has since published openssl 3.5.8-r0 and util-linux
2.41.6-r1. A rebuild of the UNCHANGED Dockerfile resolves both and scans clean, so
this is pure base-image drift on a tag that was never rebuilt.

All nine OpenSSL CVEs (CVE-2026-14456, -14457, -18798, -54874, -63072, -63073,
-63075, -63076, -75803) are fixed in OpenSSL 3.5.8; the three util-linux ones in
2.41.6. Worth noting for the customer reply: upstream OpenSSL rates six of these
Low and three Moderate - NONE is Critical. Both CVEs the scanner called CRITICAL
(CVE-2026-63073 CMP sender-DN format string, CVE-2026-75803 AEAD tag bypass on
empty ciphertext via EVP_Cipher) are OpenSSL-severity Low, and neither code path
is one the PDP drives - it runs no CMP client and calls no raw EVP_Cipher.

While verifying, found something the customer's OS-only scan did NOT report and
which a rebuild would only have fixed by luck:

  GitPython 3.1.57 carries CVE-2026-78676 - a genuine CRITICAL (CVSS 3.1 9.8)
  argument-injection RCE via git-config re-serialization into core.hooksPath.

GitPython is transitive via opal-common (`gitpython<4,>=3.1.32`) and nothing else
bounded it, so the unpinned build took whatever was current. Now floored at
>=3.1.59,<4, which clears that CRITICAL plus six HIGHs.

Changes:
- requirements.txt: floor GitPython >=3.1.59,<4 (1 CRITICAL + 6 HIGH).
- release.yml: `no-cache-filter: main,opa_build` on both build steps. The GHA
  cache key is instruction text + parent layer, so a release cut weeks later
  could replay the old `apk upgrade`/`pip install` layers and re-ship the exact
  packages a customer just flagged. Rust stages still cache normally.
- vex.json v4 -> v5: drop the CVE-2026-15308 waiver. CPython backported the
  html.parser fix into 3.13.15, which the floating base tag now resolves - the
  waiver itself said to remove it at this point.
- Dockerfile: record why a correct Dockerfile still shipped stale packages.

Verified by building the exact base + apk + pip layers with no cache:
  libcrypto3/libssl3 3.5.8-r0, libuuid 2.41.6-r1, Python 3.13.15,
  GitPython 3.1.62, cryptography 50.0.1
  trivy os+library CRITICAL/HIGH: 12 customer CVEs -> 0 remaining.
Only 3 HIGHs remain, all pre-existing and already VEX-waived as unreachable
behind OPAL's caps (starlette CVE-2026-48818/-54283, ddtrace CVE-2026-50271).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: waive the two x/crypto/ssh CVEs that Go 1.25 cannot clear (PER-15358)

The docker-scout gate on this PR fails on 5 HIGH that do NOT come from this
repo: /app/bin/opa is compiled from permitio/permit-opa, so its Go module CVEs
land in our scans. The gate has been red on them since 2026-09-06 - #333 and
#336 both went red and were merged anyway - so this is pre-existing drift, not
a regression from this PR. It matters now because the companion PR makes the
gate block releases.

Split by what is actually fixable:

  permitio/permit-opa#49 bumps grpc 1.80.0 -> 1.83.2 and x/crypto 0.53.0 ->
  0.55.0, clearing 3 of the 5 (CVE-2026-84304, CVE-2026-84445, CVE-2026-56854).
  Note 1.83.1 is NOT enough for CVE-2026-84445 - its affected range covers
  >=1.83.0 <1.83.2 - which only showed up by scanning the rebuilt binary.

  The remaining two, CVE-2026-78662 and CVE-2026-56855, are fixed only in
  x/crypto >= 0.56.0, which declares `go >= 1.26.0`. Go 1.26 is not released
  (there is no golang:1.26 image) and this image builds on golang:1.25-bookworm,
  so that upgrade does not exist yet. Waived here as not_affected /
  vulnerable_code_not_in_execute_path, on evidence rather than assertion: both
  live in golang.org/x/crypto/ssh, and `go list -deps ./cmd/opa` in permit-opa
  shows x/crypto/ssh is NOT among the linked x/crypto packages (cryptobyte,
  chacha20(+poly1305), pbkdf2, curve25519, blake2b, salsa20, nacl/box,
  nacl/secretbox, hkdf, sha3). The SSH transport is never linked into the
  binary, and the PDP speaks no SSH of its own. Each statement names the
  removal gate: drop it when a Go 1.26 toolchain lands.

vex.json v5 -> v6. Trivy does not currently flag these two at module level, so
the .trivyignore.yaml counterparts are added in the companion PR to keep the
two waiver files saying the same thing.

NOTE ON CI: this PR cannot go green until permitio/permit-opa#49 merges, because
tests.yml clones permit-opa@main at build time to produce custom_opa.tar.gz.
Once #49 is in, the two grpc CVEs disappear from the build and these two waivers
cover the rest. Verified locally: trivy on the rebuilt OPA binary with grpc
1.83.2 + x/crypto 0.55.0 reports 0 CRITICAL/HIGH, down from 5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: bind the x/crypto waivers to the PURLs scout actually emits (PER-15358)

The two waivers added in the previous commit suppressed nothing. The gate run
on 81056d6 still reported CVE-2026-78662 and CVE-2026-56855, and the reason is
the subcomponent PURL: docker scout matches VEX statements on the FULL PURL
string, and the one I wrote matched neither the version in the image nor scout's
spelling of it.

Scout's own output names the component

  pkg:golang/golang.org/x/crypto@0.53.0

while the waiver said `@v0.55.0`. Two independent mismatches:

  1. NO `v` PREFIX. Go module versions conventionally carry one and the purl
     spec permits it, but scout emits Go PURLs without it. Confirmed against the
     waivers that DO work in the same run - pkg:pypi/starlette@0.50.0 and
     pkg:pypi/ddtrace@3.19.8 are suppressed because they match byte-for-byte.
     `@v0.55.0` bound to nothing at all, and would have kept binding to nothing
     even after permit-opa#49 merged - a silent tripwire that would have looked
     like the waiver "not working" weeks later.
  2. Wrong version. The build resolves x/crypto 0.53.0 until permit-opa#49
     merges and 0.55.0 after, so a statement naming one version stops
     suppressing the moment the other is in play.

Both PURLs are therefore listed as subcomponents, which is the sanctioned
pattern for one CVE spanning several package versions. Deliberately NOT solved
by dropping `subcomponents` altogether: per Docker's VEX guide that broadens the
statement to the entire image, which would suppress these CVEs wherever they
appeared rather than only in the OPA binary. Scope is the point of the waiver.

Both impact statements now record the `v`-prefix trap and why two versions are
listed, so the next person editing this file does not rediscover it.

vex.json v6 -> v7. Verified by scanning the gate log rather than assuming:
docker scout needs a login this environment does not have, so the PURL format
was taken from scout's own printed output and cross-checked against the pypi
waivers that already suppress correctly in the same run.

This does not turn the gate green on its own - the 2 grpc CVEs and
CVE-2026-56854 still need permit-opa#49. It makes these two waivers actually
bind once it lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: drop CVE-2026-15308 from the scout gate's waiver list

The OpenVEX waiver for CVE-2026-15308 was removed in this branch (v4 -> v5)
because CPython backported the html.parser fix into 3.13.15, which the floating
python:3.13-alpine3.23 base now resolves. The docker-scout gate's comment still
described that waiver as active and as unfixable before 3.15.0b4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KY2DZ9RpvA157tQzGAggtt

* docs: list the x/crypto waivers in the scout gate's inventory too

Follows 7fef179. That commit was right to strip CVE-2026-15308 from this
comment - the waiver went away in this branch - but the same branch then ADDED
two waivers in vex.json v6/v7 (x/crypto CVE-2026-78662 and CVE-2026-56855) and
the inventory did not mention them. So the comment was accurate about what is no
longer waived and silent about what now is.

Keeping the list in step with vex.json matters more here than it looks: this
comment is the only place a reader sees WHY the gate tolerates a finding without
opening the OpenVEX doc, and the two x/crypto entries are the ones most likely
to be mistaken for an oversight - they are HIGH, they sit in a Go module nobody
in this repo edits, and they are waived rather than fixed only because
x/crypto >= 0.56.0 requires an unreleased Go 1.26.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: address Zeev's review — the cache filter was inert, plus honest comments

All four code-affecting findings verified independently before changing anything.

HIGH release.yml:73/87 - `no-cache-filter` is not an input. CONFIRMED against
docker/build-push-action action.yml at v5.4.0 and v6.9.0: the input is
`no-cache-filters` (plural); the singular form is the buildx CLI flag. An
undeclared input is warned about and dropped, so the one change this PR called
load-bearing was doing nothing at all and the stale-layer replay was still live.
Both steps now use the plural spelling, with a note so the next reader does not
"tidy" it back.

HIGH release.yml:87 - he is right that fixing the spelling alone would make the
published image stop being the image pdp-tester validated, because tests.yml
builds with no filter. Fixed together: tests.yml's build now passes the same
`no-cache-filters: main`. That also makes the gate honest - it was scanning a
possibly-cached package set rather than the one a release would ship, which can
fail in both directions (passing on cached packages while the release ships
newer ones, or failing on packages the release would not contain).

Dropped `opa_build` from the filter value. CONFIRMED: `COPY custom* /custom`
pulls in custom_opa.tar.gz, `custom/` is absent from .dockerignore, and the
Pre-build step regenerates that tarball every run - so the stage already
re-executes unconditionally and listing it implied this filter governs the OPA
binary's Go versions. It does not; those come from permit-opa's go.sum
(permitio/permit-opa#49).

MEDIUM release.yml:72 - "the expensive Rust stages still cache normally" was
inaccurate and is gone. rust_planner's `COPY . .` sees the same regenerated
tarball, so it and the final cargo zigbuild re-run every build; what actually
still caches is `cargo chef cook`, keyed on recipe.json. The comment now says
that instead. (His Cargo.lock point is real and separate: it is untracked,
.dockerignore'd, and built without --locked. Not touched here - that is a
reproducibility change with its own blast radius.)

HIGH requirements.txt:94 - correct, and the most useful finding. The floor
changes no resolution on any current path: opal-common's gitpython<4,>=3.1.32 is
the only other bound, pip takes the newest, and a fresh resolve lands on 3.1.62
with or without the line - my own verification build showed exactly that. What
cleared the CVE was the rebuild. The comment claimed the line made the fix
"resolver-enforced rather than incidental", which overclaimed. Rewritten to say
what it is: a tripwire for the case where something later pushes the resolver
below the fix (a narrower opal bound, a new transitive cap, a lockfile pinning
older), where the alternative is silently reintroducing a CVSS 9.8 RCE. Also
records why a floor and not an exact pin - websockets/ddtrace/starlette are
pinned to keep waiver PURLs bound or to keep majors off the OPAL pub/sub path,
neither of which applies to a transitive dep with no waiver - and why `<4`
stays: it is our own bound, so it holds if opal ever allows a 4.x.

MEDIUM Dockerfile:111 - the 3.13.15 floor is not enforced by the comment, but it
IS enforced, and by this PR: while CVE-2026-15308 was waived a base below 3.13.15
passed the gate anyway; with the waiver gone the same regression is reported with
nothing to suppress it and FAILS. Said explicitly, since "the waiver that used to
absorb a regression is gone" is the mechanism, not a gap.

MEDIUM Dockerfile:153 - correct, present tense described a scheduled workflow
this branch does not contain. Reworded as a requirement and says plainly that
nothing here has a `schedule:` trigger yet.

MEDIUM vex.json:6 - no gap today, and now documented rather than left implicit.
Every product binds `@next` because this gate is the ONLY consumer of the
OpenVEX doc; the scheduled published-tag re-scan uses Trivy, which reads
.trivyignore.yaml and keys on CVE id, so tag names never enter into it. Recorded
next to vex-author, including what breaks if Scout is ever aimed at a published
tag.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* docs: drop a vex.json version number that already rotted

The comment said the CVE-2026-15308 waiver was removed '(v4 -> v5)'. True at the
time, but the doc is at v7 now - two later commits added and then re-bound the
x/crypto waivers - so a reader checking the file finds a number that does not
match and has to work out which is wrong.

Version numbers in prose rot by construction. The removal is the durable fact;
the revision it happened in is recoverable from git. Dropped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix: correct the review-round-2 claims, and make the 3.13.15 floor a real control

Zeev's second round found six places where the new prose asserted a security
property that does not hold. Each was verified before changing anything.

HIGH - "Go 1.26 is still unreleased" is false, and it was the stated removal
gate for two waivers. Go 1.26.0 shipped 2026-02-10, 1.26.8 is current and
1.27.0 is out; golang:1.26-bookworm exists and PDP#334 already pins it. The
blocker is entirely local: Dockerfile:57 is still golang:1.25-bookworm and
permit-opa's go.mod is go 1.25.0, and the official golang images set
GOTOOLCHAIN=local so the toolchain cannot move implicitly. Corrected in
tests.yml and in both x/crypto impact statements, whose removal gates now name
conditions someone can actually close. vex.json v7 -> v8.

HIGH - "removing the waiver IS the enforcement" does not hold, for three
independent reasons: the gate is pull_request-only so a release never scans;
scout reads packages and never reports the interpreter (run 34620527942 lists
15 findings across 6 packages, none CPython); and the removed waiver bound
pkg:generic/python, which is not a PURL scout emits, so it was almost certainly
suppressing nothing. Rather than only describing the gap, the apk layer now
asserts sys.version_info >= (3,13,15) - which runs on every build path,
releases included, and costs no extra layer.

MED - the @next scope argument rested on a scheduled Trivy re-scan, a
.trivyignore.yaml and a schedule: trigger, none of which exist in this repo.
Since the phantom WAS the argument, removing it inverts the conclusion: the
published tags are matched by no waiver mechanism at all. Now stated as a gap.
Same fix for the "docker-scout/Trivy gate" reference at tests.yml:82.

MED - docs/image-vulnerability-management.md does not exist; there is no docs/
directory. Cite the tracker, which does.

MED - the lockfile scenario ran backwards. A lock is what gets installed, so
the floor is never consulted; its real value is upstream, where pip-compile/uv
read this file as input and cannot generate a lock below 3.1.59. Also: the
resolve fails loudly only when the capping package is exact-pinned, and
transitivity does not distinguish GitPython from the three pins cited against
it - all four are transitive. Cost of drift does. Named the verification as a
local --no-cache build rather than implying a CI run.

MED - "the scanned image matches the published one" overstates what the filter
delivers: two independent fresh resolutions, amd64-only on the test side, and
no scan on the release path at all. The guarantee is that neither is built on a
stale package set.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: make the interpreter floor a real check, and correct five more claims

Round-3 review (five specialist agents plus an independent pass) found eight
defects in the prose and controls this branch added. Each was verified before
being changed; the two that alter behaviour are first.

The 3.13.15 check could not detect what I said it detected. I told Zeev in a
review thread that placing it after the .python-rundeps surgery made it "proof
the interpreter survived that." It was not: `sys` is a builtin, so `import sys`
loads no extension modules, and a version-only check passes on an interpreter
whose entire lib-dynload is unresolvable - exactly what that surgery produces if
the `so:`-derived list ever comes back short. It now imports
ssl/hashlib/zlib/lzma/bz2/ctypes/pyexpat/decimal first, so it fails on a broken
interpreter as well as a stale one, and uses sys.exit rather than `assert`,
which -O / PYTHONOPTIMIZE would strip. Verified in both directions locally.

The x/crypto waivers used the wrong OpenVEX justification.
`vulnerable_code_not_in_execute_path` asserts the component ships and merely is
not called; both impact statements argue the stronger and provable claim, that
the ssh package is excluded at link time. That is `vulnerable_code_not_present`.
The four starlette waivers keep the old label, which is correct for them -
starlette is installed and imported. vex.json v8 -> v9, and its timestamp now
says when it was actually issued rather than 2026-09-11.

Corrections to statements that were simply wrong:

- release.yml claimed "what does still cache is `cargo chef cook`". Run
  34858998837 says otherwise: exactly three vertices cached, all in `rust_chef`,
  while cargo chef cook was the longest step in the build at 404.6s. The stage
  that does cache runs `apk add musl-dev openssl-dev zig ...` - a
  package-resolving layer outside the filter - and went unmentioned. Both halves
  corrected; the likely cause (no tracked Cargo.lock, so recipe.json drifts) is
  recorded.
- `<4` does not cap GitPython drift to a patch inside 3.1.x; it admits 3.2.0 and
  caps at a major. Verified with packaging's SpecifierSet. The conclusion - a
  floor, not an exact pin - is unchanged, but it now rests on a true premise.
- The GitPython paragraph enumerates eight advisories and said seven.
- `Dockerfile:57` was cited from three places across two files. PR #334 moves
  that stage and `git merge-tree` shows it merges clean, so all three assertions
  would go false with no conflict and no check. They name the stage now.
- tests.yml said an undeclared action input is "silently dropped"; release.yml
  correctly says it is warned about. Actions emits an `Unexpected input(s)`
  annotation, so the tests.yml wording was wrong in the direction that matters.

Not addressed here, because they change security posture or other PRs: the
three x/crypto CVEs all live in golang.org/x/crypto/ssh, so the evidence that
waives two of them covers CVE-2026-56854 as well; the scan gate has never run on
a release; and the waivers bind by PURL against an unpinned permit-opa@main.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix: make the interpreter check detect what it claims, and fix four counts

Zeev approved round 3 with seven non-blocking findings, two of which he
established by executing rather than reading. Both of those land on the control
I added last round. Every finding was re-verified before changing anything.

The check could be defeated by its own import list. decimal.py and hashlib.py
both fall back silently to pure Python when their C extension is missing -
`try: from _decimal import *` / `except ImportError: import _pydecimal` - so
importing them detected nothing. Verified by blocking `_decimal`/`_hashlib`
behind a meta_path finder: the verbatim old line exited 0 with `_pydecimal
used: True` and `_hashlib loaded: False`. The other six do propagate, `ssl`
included, so the line still caught a wholesale lib-dynload wipe - this is a
narrowing, not a breakage. It now imports the underscore modules directly, which
removes the asymmetry.

Not taking the suggested import list verbatim: it included `_gdbm`, which is
absent from this machine's CPython 3.13 (`ModuleNotFoundError`), and
readline/_curses/_gdbm guard libs the PDP never uses. Requiring them risks
failing a build for no security reason, so the list stops at the nine that cover
libssl, libcrypto, libz, liblzma, libbz2, libffi, libuuid and lib-dynload
itself.

The version floor was a single global tuple compare, so 3.14.0-3.14.6 passed -
exactly the versions the comment four lines above says lack the CVE-2026-15308
backport, which landed in 3.13.15 and 3.14.7. Now branch-aware via
`{13: (3,13,15), 14: (3,14,7)}.get(v[1], (3,15,0))`, tested across 3.12.11 /
3.13.14 / 3.13.15 / 3.14.0 / 3.14.6 / 3.14.7 / 3.15.0 / 4.0.0, and free of
shell metacharacters.

`pkg:generic/python` IS a PURL scout emits - `docker scout sbom` reports it for
this base image, and the deleted waiver was bound to a component scout indexes.
My claim came from the scan FINDINGS list, which only shows packages that have
findings and says nothing about what the SBOM indexes; the inference was invalid
regardless of the answer. Reason (a) is narrowed to match what tests.yml already
says correctly, and the simple true reason - 3.13.15 carries the fix - is stated.

The CVE-2026-48710 waiver claimed the app "registers no
add_middleware/BaseHTTPMiddleware". False: horizon/pdp.py adopts OPAL's app and
opal_client/client.py reaches opal_common/middleware.py:77, which registers
CORSMiddleware. The conclusion survives - starlette/middleware/cors.py reads no
`path`, no `url` and never builds a Request, dispatching only on Origin and
scope['method'] - but the premise was wrong and is now stated accurately.
vex.json v9 -> v10, which also clears both open siblings for a union merge.

Counts, all regenerated from source rather than memory:
- GHSA-jm78-9fvv-mhgr IS CVE-2026-76221, so listing it by GHSA id made the
  76218-76222 run read as if it had a gap. Now listed by CVE id.
- The 3.1.57 -> 3.1.59 move clears eleven distinct CVEs, eight of them
  HIGH-or-above; "eight advisories" read as a total. Both numbers now stated.
- PyPI says nine GitPython releases in the seven weeks to 3.1.62, not eight.
- The cache measurement came from a `pull_request` run of tests.yml built
  `--platform linux/amd64` and was quoted on a two-platform release step, where
  opa_build and main are each built twice with one leg under QEMU and a tag ref
  restores different GHA caches. The 404.6s figure carries over; the "longest
  step" ranking does not. `rust_chef` cached three of four layers, not all -
  #18 `rustup target add` re-ran.
- `openssl-dev` and `ENV OPENSSL_DIR=/usr` are dead config, since pdp-server
  vendors OpenSSL. Named as dead and tracked rather than removed here; pulling
  build config out of the Rust stage is its own change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Zeev Manilovich <zeevmanilovich@gmail.com>
Co-authored-by: David Shoen <dbshoen@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants