Skip to content

fix(ci): scan release builds, unpin VEX from :next, correct waivers (PER-15358) - #335

Closed
dshoen619 wants to merge 2 commits into
mainfrom
david/per-15358-scout-gate-releases-vex
Closed

dshoen619 wants to merge 2 commits into
mainfrom
david/per-15358-scout-gate-releases-vex

Conversation

@dshoen619

@dshoen619 dshoen619 commented Sep 6, 2026 •

Copy link
Copy Markdown
Contributor

Superseded by #338, which runs the CVE gate on releases and carries the waiver changes. Closed without merging.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PHJFjyY84UkG7zAfXUHPpP

…PER-15358)

## The gate never ran on what we ship

docker-scout carried `if: github.event_name == 'pull_request'`. release.yml
calls tests.yml with `uses:`, and inside a called workflow
github.event_name is the CALLER'S event - "release" - so the condition was
false and the job was skipped on every release, and on every push to main.

Not a reading of the YAML. Release v0.9.14, run 30900950607:

    success   pdp-tests / build-pdp-image
    success   pdp-tests / pdp-tester
    skipped   pdp-tests / docker-scout      <-- the gate
    success   build-and-push-pdp            # pushed :0.9.14 and :latest

Every image on Docker Hub was built and pushed without passing a CVE gate.
Scanning ran only on PRs, against a :next tag that is never published.

Adding 'workflow_call' to the condition would not have helped - that is not
a value github.event_name takes. `on:` already enumerates exactly the
trigger set we want scanned, so the condition is removed outright.

This makes the gate release-blocking (build-and-push-pdp needs pdp-tests).
That is the intent - PER-15532 was a customer scan of 0.9.14-rc1 finding
CVEs CI never flagged - but it is a real change in failure mode: a HIGH
published between merge and release cut now stops the push and the ECS
redeploy until the dependency is fixed or a VEX statement is added.

Two limits stated plainly rather than oversold: releases run the workflow
AT THE TAG, so a release cut from a commit predating a VEX correction uses
that commit's waiver set; and the scanned image is the single-arch
linux/amd64 CI build, so linux/arm64 is still covered by nothing.

Note docker-scout is NOT currently in the repo's required status checks, so
a new HIGH still reaches main freely and first bites at the release cut.
Adding it there is a follow-up worth doing.

## VEX: unpin from the :next tag

All statements used `pkg:docker/permitio/pdp-v2@next`. That matches today
only because the scanned tag is hardcoded :next - the day anyone
parameterises it, all six waivers silently stop matching and the
now-blocking gate hard-fails on already-triaged CVEs. Dropping the version
qualifier is a strict superset: openvex PurlMatches rejects only when both
versions are non-empty and unequal, so an empty version matches any tag.
Confirmed against go-vex v0.2.7, the version Scout links.

Subcomponent PURLs stay exact-versioned on purpose - pkg:pypi/starlette@0.50.0
must not match 0.51.0. That is how a waiver expires when the pin moves.

## Waiver corrections

- CVE-2026-48710 contained a false claim: "the app registers no
  add_middleware/BaseHTTPMiddleware". The PDP adopts OPAL's app
  (horizon/pdp.py:280), which registers CORSMiddleware and a 500 handler
  (opal_common/middleware.py:77,30). `grep -rn add_middleware horizon/`
  returns nothing, which is how the claim got written. The conclusion
  survives - CORSMiddleware reads no path - but that is the argument the
  waiver needed to make and did not. The rewrite also names the three
  layers a "no middleware" claim would miss: FastAPI's framework defaults,
  starlette's AuthenticationMiddleware (not installed), and ddtrace's
  TraceMiddleware, which DOES read scope['path'] and the Host header
  (ddtrace/contrib/internal/asgi/middleware.py:245-248) whenever monitoring
  is on - it tags a span rather than making a security decision.
- CVE-2026-54282 (LOW 3.7) was unwaived. Added, with the same carve-outs
  rather than a blanket "nothing in the image reads request.url" - which
  would have been false, see starlette/authentication.py:66,83.
- CVE-2026-15308 removed entirely: the base image now resolves to CPython
  3.13.15 and Scout no longer reports it at any severity, so `not_affected`
  would now be the wrong status. This was also the only version-less
  subcomponent (pkg:generic/python), which would have suppressed the
  finding across every future interpreter. Stale Dockerfile comment
  dropped with it.
- CVE-2026-50271: dropped "Independently". horizon/pdp.py:239 applies the
  control plane's remote config before the ENABLE_MONITORING check at :262,
  so monitoring can be enabled remotely - the ENV is the load-bearing
  mitigation, not the flag. Dockerfile comment aligned to match.
- CVE-2026-54283: removed the python-multipart clause. True but irrelevant;
  this CVE is the urlencoded path, which starlette parses natively.
- CVE-2026-48817: removed "every route is registered through APIRouter
  decorators" - FastAPI registers /docs and /openapi.json via add_route().

## Hardening that the trigger change makes necessary

- write-comment gated on pull_request for both scout steps. The input
  exists and defaults to true (docker/scout-action action.yaml:197), and
  there is no PR to comment on during a release.
- SARIF upload requires the file to exist, and is now continue-on-error:
  the gate is the step above it, and a code-scanning upload problem must
  not block a customer release for zero security signal.
- category: docker-scout. codeql-action keys analyses by
  `${workflowPath}:${jobName}` resolved from the run's own workflow, so a
  release-triggered run would otherwise key separately from PR/push runs.
- Documented that medium/low are report-only by policy.

## Also

requirements.txt's starlette block said "four CVEs / all four are waived";
it is five now that CVE-2026-54282 is tracked. Left as a correct removal
checklist for when OPAL relaxes its starlette<1 cap.

## Merge order

Must land AFTER permitio/permit-opa#<pr> and the golang:1.26 builder PR.
This arms a release-blocking gate; today that gate reports 4 HIGH from the
OPA binary. Merging this first would block the next release.

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

linear-code Bot commented Sep 6, 2026

Copy link
Copy Markdown

PER-15358

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.

🔵 Needs a closer look

It changes release-blocking CI behavior and security gating/waiver matching, so it warrants final human review to validate the operational failure modes and release impact.

Pull request overview

This PR tightens the PDP container security gate by ensuring Docker Scout CVE scanning runs for the same build paths that produce shipped artifacts (including release-triggered runs), and updates the OpenVEX waiver set so existing triaged findings continue to match once image tagging is parameterized.

Changes:

  • Removes the docker-scout job’s event conditional so the CVE gate runs on release and mainline builds, and hardens SARIF/comment behavior for non-PR events.
  • Updates .docker/scout/pdp-v2.vex.json to use a versionless product PURL and corrects/refreshes waiver statements (including adding CVE-2026-54282 and removing CVE-2026-15308).
  • Updates documentation/comments in requirements.txt and Dockerfile to reflect the current waiver count and mitigation rationale.
File summaries
File Description
requirements.txt Updates the Starlette CVE waiver checklist/comment to reflect five tracked CVEs and the current waiver rationale.
Dockerfile Updates commentary around Python base-image CVE waiver removal and clarifies ddtrace mitigation reliance.
.github/workflows/tests.yml Makes Docker Scout scanning/gating run for release/push paths and hardens PR-comment + SARIF upload behavior.
.docker/scout/pdp-v2.vex.json Unpins the product PURL from @next, increments VEX version/timestamp, and corrects waiver statements.
Review details
  • Files reviewed: 3/4 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.

@dshoen619 dshoen619 self-assigned this Sep 6, 2026
@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

🔍 Vulnerabilities of permitio/pdp-v2:next

📦 Image Reference permitio/pdp-v2:next
digestsha256:33e004123aa53dd23649b90c1c7ea1a1d38df7436d41afd6f93f5e00843b9302
vulnerabilitiescritical: 0 high: 8 medium: 5 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: 2 medium: 1 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.

high : CVE--2026--84445 Improper Validation of Array Index

Affected range<1.82.2
Fixed version1.82.2
Description

A vulnerability exists in gRPC-Go servers configured with xds.NewGRPCServer() where a crafted request missing both :authority and Host headers can cause a server panic, resulting in a Denial of Service (DoS).

Servers built with xds.NewGRPCServer install an xDS routing interceptor on every RPC. This interceptor looks up the request’s :authority header to pick a virtual host. The HTTP/2 server transport previously accepted requests that had neither :authority nor Host. When this happened, the xDS routing interceptor attempted to access the first element of an empty slice of authorities, leading to an index out of bounds panic. Since the per-RPC goroutine does not recover from panics, the entire server process would terminate.

This panic occurs in the interceptor pipeline, meaning the transport credentials handshake (TLS, mTLS, or ALTS) and HTTP/2 connection establishment must complete successfully before the crafted request can reach this logic.

  • Insecure/Standard TLS: If the server permits insecure (plaintext) connections or standard credentials (where client certs are not checked), any unauthenticated remote attacker can trigger the crash.
  • mTLS / ALTS: If strict transport-level authentication is enforced at the network edge or transport layer (e.g., requiring a valid client certificate), the attacker must possess valid transport credentials to initiate the stream and trigger the panic.

Impact

An attacker can cause a complete outage of the gRPC server by sending a request missing both :authority and Host headers, provided they can successfully establish a transport connection.

Patches

The issue has been addressed in master (and backported to 1.83.2 and 1.82.2). The fix updates the HTTP/2 transport layer to reject requests missing both :authority and Host headers early, maintaining consistency with and other gRPC language implementations.

medium 6.3: CVE--2026--84303 Improper Handling of Case Sensitivity

Affected range<=1.83.0
Fixed version1.83.1
CVSS Score6.3
CVSS VectorCVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:L/VI:L/VA:N/SC:N/SI:N/SA:N
EPSS Score0.311%
EPSS Percentile24th percentile
Description

Summary

A vulnerability in the xDS RBAC HTTP filter implementation in grpc-go allows remote attackers to bypass authorization policies (specifically DENY rules) by using mixed-case or canonical-case header matchers (e.g., X-Role instead of x-role). Additionally, the safety guards introduced by gRFC A41 to block grpc- prefixed headers can be evaded via variations in casing (e.g., Grpc-Status).

Impact

When an operator defines an RBAC policy referencing headers containing uppercase letters (e.g. X-Role), grpc-go fails to match incoming metadata keys because they are unconditionally lowercased. Because of this case-sensitivity mismatch, a policy designed to block requests containing specific header values fails open: the rule is evaluated as a non-match, and traffic that should have been rejected is served.

Furthermore, gRFC A41 requires rejecting configuration schemas specifying header matchers starting with grpc-. Because this check is executed case-sensitively in grpc-go, attackers can bypass the validation by specifying titles like Grpc-Status.

Patches

The problem is fixed in master and in the 1.83.1 release.

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: 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 Percentile22nd percentile
Description
critical: 0 high: 0 medium: 1 low: 0 github.com/containerd/containerd/v2 2.2.5 (golang)

pkg:golang/github.com/containerd/containerd/v2@2.2.5

medium 6.8: CVE--2026--53495 Uncontrolled Resource Consumption

Affected range>=2.2.0
<2.2.8
Fixed version2.2.8
CVSS Score6.8
CVSS VectorCVSS:4.0/AV:L/AC:L/AT:N/PR:L/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N
Description

Impact

A bug in containerd's CRI ExecSync implementation allows exec probes and lifecycle hooks with background child processes to keep containerd's stdio-drain goroutines indefinitely blocked. Because the I/O drain phase lacks a default timeout or context cancellation handling, repeated ExecSync invocations (like probes) that include long-lived background processes against a container can cause containerd to leak goroutines and host memory. Over time, this resource exhaustion can cause the containerd daemon to be terminated by the OOM killer, rendering containerd unavailable until it is restarted. This issue affects containerd on Linux systems running with the CRI plugin enabled. Users not using containerd's CRI implementation or not running containers on Linux are not affected.

Patches

This bug has been fixed in containerd 2.3.5, 2.2.8, 2.0.12, and 1.7.35. Users should update to these versions to resolve the issue.

Workarounds

Ensure exec probes and lifecycle hooks do not launch long-lived background child processes.

Credits

The containerd project would like to thank XlabAI Team of Tencent Xuanwu Lab (xlabai@tencent.com), including Guannan Wang, Zhanpeng Liu, Jiashuo Liang, and Guancheng Li, and @IamwhatIamSY who independently discovered and responsibly disclosed this issue in accordance with the containerd security policy.

For more information

If there are any questions or comments about this advisory:

  • Open an issue in containerd
  • Send an email to [security@containerd.io](mailto:security@containerd.io)

To report a security issue in containerd:

@github-actions

github-actions Bot commented Sep 6, 2026 •

Copy link
Copy Markdown

🔍 Vulnerabilities of permitio/pdp-v2:next

📦 Image Reference permitio/pdp-v2:next
digestsha256:33e004123aa53dd23649b90c1c7ea1a1d38df7436d41afd6f93f5e00843b9302
vulnerabilitiescritical: 0 high: 5 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: 2 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.

high : CVE--2026--84445 Improper Validation of Array Index

Affected range<1.82.2
Fixed version1.82.2
Description

A vulnerability exists in gRPC-Go servers configured with xds.NewGRPCServer() where a crafted request missing both :authority and Host headers can cause a server panic, resulting in a Denial of Service (DoS).

Servers built with xds.NewGRPCServer install an xDS routing interceptor on every RPC. This interceptor looks up the request’s :authority header to pick a virtual host. The HTTP/2 server transport previously accepted requests that had neither :authority nor Host. When this happened, the xDS routing interceptor attempted to access the first element of an empty slice of authorities, leading to an index out of bounds panic. Since the per-RPC goroutine does not recover from panics, the entire server process would terminate.

This panic occurs in the interceptor pipeline, meaning the transport credentials handshake (TLS, mTLS, or ALTS) and HTTP/2 connection establishment must complete successfully before the crafted request can reach this logic.

  • Insecure/Standard TLS: If the server permits insecure (plaintext) connections or standard credentials (where client certs are not checked), any unauthenticated remote attacker can trigger the crash.
  • mTLS / ALTS: If strict transport-level authentication is enforced at the network edge or transport layer (e.g., requiring a valid client certificate), the attacker must possess valid transport credentials to initiate the stream and trigger the panic.

Impact

An attacker can cause a complete outage of the gRPC server by sending a request missing both :authority and Host headers, provided they can successfully establish a transport connection.

Patches

The issue has been addressed in master (and backported to 1.83.2 and 1.82.2). The fix updates the HTTP/2 transport layer to reject requests missing both :authority and Host headers early, maintaining consistency with and other gRPC language implementations.

@github-advanced-security

Copy link
Copy Markdown

You are seeing this message because GitHub Code Scanning has recently been set up for this repository, or this pull request contains the workflow file for the Code Scanning tool.

What Enabling Code Scanning Means:

  • The 'Security' tab will display more code scanning analysis results (e.g., for the default branch).
  • Depending on your configuration and choice of analysis tool, future pull requests will be annotated with code scanning analysis results.
  • You will be able to see the analysis results for the pull request's branch on this overview once the scans have completed and the checks have passed.

For more information about GitHub Code Scanning, check out the documentation.

Copilot AI review requested due to automatic review settings September 14, 2026 12:59

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

EliMoshkovich added a commit that referenced this pull request Sep 14, 2026
I had these the wrong way round. Verified against docker/scout-action@v1
action.yaml:

  summary       "Publish the output as GitHub Action summary"   default true
  write-comment "Write the output as a Pull Request comment"    default true

`summary` writes to the job summary page and is wanted on EVERY run - a release
especially, since that is the run whose CVE report nobody can see afterwards. I
gated it on pull_request with the comment "Only a pull_request run has a PR to
comment on", which described `write-comment` while changing `summary`. Net
effect once this job started running on releases: the release job summary was
suppressed AND a PR comment was still attempted with no PR to attach it to.

Both scout steps now keep `summary: true` and gate `write-comment` instead. The
gate step needed it too - it set neither, so it inherited both defaults.

Found while reviewing #335, which reached the same conclusion
independently; credit there. This is the fourth instance in this series of the
same failure mode already documented in the runbook - config that is accepted
without error and does not do what it reads like.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@dshoen619 dshoen619 closed this Sep 16, 2026
@zeevmoney
zeevmoney deleted the david/per-15358-scout-gate-releases-vex branch October 3, 2026 10:39
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