Skip to content

ci+parser+test: fix all 7 audit findings (F1-F7, sha e289021)#82

Merged
eilandert merged 8 commits into
masterfrom
fix/f1-dispatch-shell-injection
Jul 15, 2026
Merged

ci+parser+test: fix all 7 audit findings (F1-F7, sha e289021)#82
eilandert merged 8 commits into
masterfrom
fix/f1-dispatch-shell-injection

Conversation

@eilandert

@eilandert eilandert commented Jul 15, 2026

Copy link
Copy Markdown
Member

Summary

Fixes all 7 findings (4 MAJOR, 3 MINOR) plus the noted NITs from the 2026-07-15 /audit-code pass (sha e289021, HIGH RISK verdict), one commit per finding:

  • F1 [MAJOR/Security] ci-deep.yml's fuzz_seconds dispatch input was spliced into a run: shell command (zizmor high-confidence template injection) → runner RCE. Fixed via env: passthrough + numeric/range validation.
  • F2 [MAJOR/Correctness] ci-build.sh always mv'd the extracted tarball dir onto itself on a clean root, breaking every fresh nginx-stable/Angie build. Guarded the mv on the paths actually differing.
  • F3 [MAJOR/Security] Archives built/executed without independent provenance: nginx PGP keys were bootstrapped from nginx.org itself (same origin as the tarball+signature), no sha256 pin existed for pinned nginx-stable, and cached tarballs skipped verification entirely. Vendored the signer keyring under tools/keys/, added an NGINX_SHA256 pin, made verification run unconditionally (cache-hit or fresh download), and extended the same PGP check to every other workflow that raw-downloads nginx (codeql, security-scanners, valgrind, build-test's two other jobs).
  • F4 [MAJOR/Compatibility] bump.yml pushed straight to master, which has a required-PR ruleset with no bypass actor — always rejected in practice. Now opens a PR via a classic PAT repo secret.
  • F5 [MINOR/Compatibility] Parser accepted trailing junk after qvalue OWS (zstd;q=1 garbage negotiated zstd). Added the missing post-OWS delimiter check; two new TAP cases (T78/T79) reproduce it against pre-fix code.
  • F6 [MINOR/Tests] soak.sh never asserted readiness and silently discarded curl failures, so an all-500 backend could "pass" a clean soak. Added a hard readiness-failure exit and per-worker ok/bad accounting.
  • F7 [MINOR/Maintainability] README/SECURITY/CONTRIBUTING materially disagreed with live CI (stale nginx version claim, wrong test counts, dead AGENTS.md link, missing CodeQL/valgrind/bump documentation, false -Werror fuzz claim). Corrected.
  • NITs: ruff-fixed two Python test files, shfmt (-i 4 -ci, matching this repo's style) on five scripts with formatting drift, corrected ci-deep.yml's stale "CodeQL is dropped" rationale.

Test plan

  • F1: zizmor no longer reports a template-injection finding on ci-deep.yml
  • F2: clean BUILD_ROOT, NO_CACHE=1 — nginx mainline (1.31.2), nginx-stable (1.30.3), and Angie (1.11.5) all build to completion
  • F3: all three flavors verify+build clean; corrupted a cached tarball and confirmed re-verification now catches it on cache-hit; bump-versions.sh's new nginx-stable digest helper reproduces the pinned hash independently
  • F4: bump.yml YAML validated, zizmor clean, dry-run of bump-versions.sh confirmed logic unchanged
  • F5: standalone harness (via fuzz/extract_parser.sh + ngx_shim.h) reproduced the bug pre-fix; T78/T79 fail on pre-fix code, pass post-fix; full t/00-filter.t (936 subtests) + t/01-static.t + t/02-conf-warn.t (332 more) green; fuzz target re-run 1.3M+ execs with no crash
  • F6: real soak against a static --add-module build passes clean (4000+ requests, 0 bad); confirmed curl -f returns nonzero against an induced all-500 backend, which the fixed readiness loop now treats as a hard failure
  • F7: docs cross-checked against actual workflow files and TAP counts
  • actionlint, shellcheck, gitleaks (no findings in tracked files) all clean across the diff

Summary by CodeRabbit

  • Security

    • Added cryptographic verification for downloaded nginx source archives across build, analysis, scanning, and memory-check workflows.
    • Added trusted signing keys and verified checksum pinning for stable releases.
  • Bug Fixes

    • Rejects malformed Accept-Encoding values containing trailing junk after quality parameters.
  • CI Improvements

    • Version updates now open pull requests instead of pushing directly.
    • Added validation for fuzzing time limits.
  • Documentation

    • Updated CI, compatibility, security verification, and local fuzzing guidance.

… input

fuzz_seconds was spliced directly into a run: shell command via
${{ }} expansion, so a dispatch value like 3600"; id; # became
executable shell on the self-hosted builder02 runner (zizmor
high-confidence finding, audit sha e289021 F1).

Fix: pass the raw input through env: (not interpolated into command
text), validate ^[0-9]+$ and a bounded range (<=14400s) before use,
write with printf instead of echo of an unvalidated value. zizmor
no longer reports a template-injection finding on this file.
DIR ("<flavor>-<version>") and SRCDIR ("$ROOT/<flavor>-<version>")
are the same path, so the tarball already extracts to $SRCDIR and the
subsequent mv failed with "cannot move to a subdirectory of itself"
on every clean-root / NO_CACHE=1 build -- meaning ci-deep's new
nginx-stable and angie build-flavors coverage never actually ran
(audit sha e289021 F2).

Guard the mv on the paths actually differing. Verified with a clean
BUILD_ROOT: both nginx 1.31.2 and angie 1.11.5 now configure, compile,
and produce the filter/static .so through to a full build success.
master carries a required-pull-request ruleset with no bypass actor,
so the weekly bump job's git push origin HEAD:master was always
rejected in practice -- pins/digests silently stopped updating
despite a green-looking scheduled job (audit sha e289021 F4).

Push a dated branch and open a PR via a classic PAT
(secrets.BUMP_PR_TOKEN, set as a repo secret) instead -- the default
GITHUB_TOKEN can't create PRs on myguard-labs repos. Normal
review/required-checks still gate the merge. Also dropped the
already-stale nginx-tests-submodule wording from the header comment
and tightened permissions: contents: read (git ops now go through the
PAT env, not the default token).
…fy on cache-hit too

Three related gaps in tarball provenance (audit sha e289021 F3):

1. Signer keys were fetched from nginx.org at CI time -- the same
   origin serving the tarball and signature, so an origin compromise
   could substitute all three. Keys now live vendored in tools/keys/
   (committed), imported only from there; rotation is a reviewed PR.
2. nginx-stable (statically pinned in ci-deep.yml's build matrix,
   unlike floating mainline) had no sha256 pin, only PGP. Added
   NGINX_SHA256 next to ANGIE_SHA256, checked the same way. Floating
   mainline still relies on PGP alone (no static pin is possible for
   it by design).
3. A cache-hit tarball skipped verification entirely -- only a fresh
   download was checked. Verification now runs unconditionally after
   the download-or-cache-hit branch, so a corrupted/tampered cached
   tarball is caught on the next run.

tools/bump-versions.sh's nginx-stable path now fetches+verifies the
new version's PGP signature (against the same vendored keyring) before
recording its sha256 pin -- a bump script that pins a digest without
checking provenance first would just relocate the trust gap.

Verified: nginx mainline (1.31.2, PGP-only), nginx-stable (1.30.3,
PGP+sha256), angie (1.11.5, sha256) all build clean from a fresh
BUILD_ROOT. Corrupted a cached nginx-stable tarball and confirmed the
now-unconditional verification step catches it on cache-hit
(previously would have silently reused it). bump-versions.sh's
sha256_for_nginx_stable() independently reproduces the same digest
pinned in ci-build.sh for 1.30.3.
…at downloads it

F3's scope wasn't limited to ci-build.sh -- codeql.yml, security-scanners.yml,
valgrind.yml, and two jobs in build-test.yml (old-libzstd, ASAN/UBSAN, and the
matrix-driven full-debug build) each did their own raw wget+tar of an nginx
tarball with ZERO verification (not even the PGP check ci-build.sh had before
this fix). Added the same vendored-keyring PGP verify step (tools/keys/) to
each, running unconditionally after the cache-hit-or-download branch so a
cached tarball gets re-checked too, not just a fresh download.

Left build-test.yml's 'Download nginx binary (mainline, from build matrix)'
step alone -- that's an actions/download-artifact pull of this same
workflow run's own already-verified build job output, not a raw
nginx.org fetch.
After a valid qvalue, the trailing-junk check only looked at the byte
immediately following the qvalue digits (catching "q=1x"), but a
subsequent OWS-skip loop consumed any space/tab BEFORE the outer
parameter loop re-checked for ';'/',' -- landing on an arbitrary
non-delimiter byte silently ended the loop as if there were no more
parameters, instead of rejecting. "zstd;q=1 garbage" and
"zstd;q=0.5\tjunk" both negotiated zstd despite trailing invalid text
(audit sha e289021 F5; the module's own comment and the independent
fuzz oracle already assumed this was rejected).

Added the same delimiter check after the general trailing-OWS skip
that already exists after the q-specific check. Verified: reproduced
the bug with a standalone harness built via fuzz/extract_parser.sh
against ngx_shim.h, confirmed the two new TAP cases (T78/T79) fail
against pre-fix code and pass after the fix, full t/00-filter.t
(936 subtests) + t/01-static.t + t/02-conf-warn.t (332 more) all green,
and re-ran the accept_encoding fuzz target 1.3M+ execs with no
crash/divergence.
… load

Two related gaps (audit sha e289021 F6): the readiness retry loop
never asserted success after its 10s window (a backend serving only
500s/connection-refused would silently 'pass' readiness and proceed
straight to load), and each worker's curl -f failure was caught by
the if/then with no else -- a failing request was silently discarded
rather than counted, so a live nginx returning nothing but errors
could finish a clean soak with zero successful requests recorded.

Readiness now exits 1 (dumping error.log) if no request ever
succeeds. Each worker now tracks ok/bad counts, logs a failed
request instead of dropping it, and requires zero bad + at least
one ok to pass; wait already captures the aggregate via fail=1 per
worker.

Verified: a real 15s/4-worker soak against a static --add-module
build passes clean (4000+ successful requests, 0 bad) with the new
accounting -- confirming the stricter checks don't regress the happy
path. Manually confirmed curl -f returns nonzero against an all-500
backend, which is exactly what the fixed readiness loop now treats
as a hard failure instead of silent success.
F7: README/SECURITY/CONTRIBUTING materially disagreed with live CI
(audit sha e289021). Fixed:
- README claimed 'nginx 1.31.0 mainline' as CI-verified and that every
  build exercises nginx+Angie together -- Build & Test never touches
  Angie at all (only ci-deep.yml does, monthly); mainline floats and
  isn't pinned to any single version. Corrected the Compatibility
  table and Testing & CI section, added the missing CodeQL row, added
  a Bump row/mention, and updated stale TAP counts (46/21 -> current
  79/28/4).
- SECURITY.md linked a removed AGENTS.md and omitted valgrind/ci-deep/
  bump from its workflow list.
- CONTRIBUTING.md claimed the fuzz harness builds with -Werror; it
  doesn't (only the module's own C sources do, in build-test.yml).
- ci-deep.yml's header said 'codeql.yml is dropped entirely' -- it
  exists, runs on every push/PR, and is documented in the README.
- Trailing whitespace in README.md.

NITs: ruff --fix on two Python test files (f-string-without-placeholder,
unused import). shfmt -i 4 -ci (matching this repo's existing 4-space
style, not shfmt's tab default) on the five scripts the audit flagged
for formatting drift; re-verified bump-versions.sh --dry-run, a full
ci-build.sh angie build, and a soak.sh run all still work correctly
after the reformat (whitespace-only diff, no behavior change intended
or observed).
@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The changes add detached PGP verification for NGINX downloads, verified SHA256 pinning for stable releases, parser regression coverage, pull-request-based version bumps, validated fuzz durations, updated CI documentation, and revised test-harness reporting.

Changes

Artifact integrity

Layer / File(s) Summary
Vendored keys and verification helpers
tools/keys/*, tools/ci-build.sh
Vendored PGP keys, detached-signature verification, optional NGINX SHA256 checks, dynamic mainline resolution, and safer extraction handling are added.
Verified stable-version pinning
tools/bump-versions.sh
Stable NGINX releases are signature-verified before their SHA256 values and CI matrix entries are updated.
CI workflow tarball verification
.github/workflows/build-test.yml, .github/workflows/codeql.yml, .github/workflows/security-scanners.yml, .github/workflows/valgrind.yml
CI jobs verify downloaded NGINX archives with repository keys before extraction or builds continue.

Accept-Encoding parser correctness

Layer / File(s) Summary
Trailing-junk rejection
ngx_http_zstd_common.h, t/00-filter.t
Malformed q-values with trailing junk after optional whitespace are rejected, with regression tests for spaces and tabs.

CI automation and documentation

Layer / File(s) Summary
Pull-request-based version bumps
.github/workflows/bump.yml
Version automation creates a branch, commits changes, pushes it, and opens a pull request.
Fuzz workflow validation
.github/workflows/ci-deep.yml, CONTRIBUTING.md
Manual fuzz durations are validated and local fuzzing guidance now describes ASAN/UBSAN compilation.
CI documentation and extraction updates
README.md, SECURITY.md, fuzz/extract_parser.sh
CI coverage documentation and parser extraction command formatting are updated while extraction checks remain unchanged.

Test harness maintenance

Layer / File(s) Summary
Soak-test result tracking
tools/soak.sh
Worker-level zstd failures are counted and final failure categories are reported separately.
Test utility cleanup
tools/test_reload_leak.sh, tools/test_test_package_artifact.py, tools/test_window_cap.py
Utility scripts receive formatting, literal-string, and import adjustments.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Workflow as GitHub Actions workflow
  participant Nginx as nginx.org
  participant Keys as tools/keys/*.key
  participant GPG as Temporary GNUPGHOME
  Workflow->>Nginx: Download tarball and .asc signature
  Workflow->>Keys: Read vendored public keys
  Workflow->>GPG: Import keys
  Workflow->>GPG: Verify tarball signature
  GPG-->>Workflow: Verification result
  Workflow->>Workflow: Continue build or fail
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the PR as a batch fix for CI, parser, and test audit findings.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/f1-dispatch-shell-injection

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

@eilandert
eilandert merged commit 74ea74d into master Jul 15, 2026
12 of 13 checks passed
@eilandert
eilandert deleted the fix/f1-dispatch-shell-injection branch July 15, 2026 02:58

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/bump.yml:
- Around line 58-71: Update the branch naming and PR creation flow around the
branch variable, git checkout, git push, and gh pr create so rerunning the
workflow on the same UTC day cannot collide with an existing branch or pull
request. Use a run-unique suffix for the bump branch, or explicitly detect and
update the existing branch and PR before creation, while preserving the current
automated commit and PR metadata.
- Around line 39-43: Update the bump workflow’s authentication setup after
GH_TOKEN is initialized to configure Git credentials before the git push, using
gh auth setup-git or an equivalent mechanism. Ensure the BUMP_PR_TOKEN has
repository write permission, while preserving the checkout step’s disabled
credential persistence.

In @.github/workflows/ci-deep.yml:
- Around line 136-146: Update the RAW_FUZZ_SECONDS validation before the numeric
-gt comparison to reject values exceeding the allowed range even when they
overflow shell integer parsing. After validating digits, trim leading zeroes and
enforce a bounded length or equivalent safe parse, while preserving valid
zero-padded values and the existing 14400-second maximum before writing
FUZZ_SECS to GITHUB_ENV.

In @.github/workflows/security-scanners.yml:
- Around line 57-64: In the GnuPG setup around gnupghome in
.github/workflows/security-scanners.yml lines 57-64, add an EXIT trap
immediately after creating the temporary directory and retain the existing
cleanup as appropriate. Apply the same trap in .github/workflows/valgrind.yml
lines 61-68, and remove that workflow’s success-only rm -rf cleanup so both
workflows clean up on success and failure.
- Around line 56-61: Install the gpg package in the dependency setup for both
verification workflows before their gpg usage: update
.github/workflows/security-scanners.yml at lines 56-61 and
.github/workflows/valgrind.yml at lines 59-65. Ensure the existing gpg --import
and gpg --verify steps can run without changing their verification behavior.

In `@tools/bump-versions.sh`:
- Around line 164-169: Replace the duplicated bump_nginx_sha256_pin and
bump_angie_sha256_pin helpers with a parameterized bump_sha256_pin accepting the
target array name, new version, and digest. Use the array parameter in the
existing grep and sed operations, then update both callers to pass ANGIE_SHA256
or NGINX_SHA256 accordingly while preserving the already-pinned check and
CHANGED behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 03644a72-b6c0-45cc-8706-70930afd039b

📥 Commits

Reviewing files that changed from the base of the PR and between e289021 and c67f616.

📒 Files selected for processing (24)
  • .github/workflows/build-test.yml
  • .github/workflows/bump.yml
  • .github/workflows/ci-deep.yml
  • .github/workflows/codeql.yml
  • .github/workflows/security-scanners.yml
  • .github/workflows/valgrind.yml
  • CONTRIBUTING.md
  • README.md
  • SECURITY.md
  • fuzz/extract_parser.sh
  • ngx_http_zstd_common.h
  • t/00-filter.t
  • tools/bump-versions.sh
  • tools/ci-build.sh
  • tools/keys/arut.key
  • tools/keys/maxim.key
  • tools/keys/nginx_signing.key
  • tools/keys/pluknet.key
  • tools/keys/sb.key
  • tools/keys/thresh.key
  • tools/soak.sh
  • tools/test_reload_leak.sh
  • tools/test_test_package_artifact.py
  • tools/test_window_cap.py
💤 Files with no reviewable changes (1)
  • tools/test_window_cap.py

Comment on lines 39 to 43
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5
with:
persist-credentials: true
submodules: true
persist-credentials: false
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

sed -n '1,220p' .github/workflows/bump.yml

Repository: myguard-labs/nginx-zstd-module

Length of output: 2744


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/bump.yml')
text = p.read_text()
for i, line in enumerate(text.splitlines(), 1):
    if 1 <= i <= 220:
        print(f"{i:4}: {line}")
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 3170


🏁 Script executed:

sed -n '1,220p' tools/bump-versions.sh

Repository: myguard-labs/nginx-zstd-module

Length of output: 7599


Authenticate git before the push. persist-credentials: false removes checkout’s credential helper, so git push origin "$branch" needs a git auth step after GH_TOKEN is set (gh auth setup-git or equivalent). Ensure BUMP_PR_TOKEN can write to the repo.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump.yml around lines 39 - 43, Update the bump workflow’s
authentication setup after GH_TOKEN is initialized to configure Git credentials
before the git push, using gh auth setup-git or an equivalent mechanism. Ensure
the BUMP_PR_TOKEN has repository write permission, while preserving the checkout
step’s disabled credential persistence.

Comment on lines +58 to +71
branch="bump/versions-$(date -u +%Y%m%d)"
git config user.name "myguard-bump-bot"
git config user.email "actions@users.noreply.github.com"
git checkout -b "$branch"
git add -A
git commit -m "chore: bump nginx-stable/angie/nginx-tests pins
git commit -m "chore: bump nginx-stable/angie pins

Automated weekly version check (tools/bump-versions.sh)."
git push origin HEAD:master
git push origin "$branch"
gh pr create \
--title "chore: bump nginx-stable/angie pins ($(date -u +%Y-%m-%d))" \
--body "Automated weekly version check (tools/bump-versions.sh). Review the diff and let required CI checks gate the merge." \
--base master \
--head "$branch"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make the bump branch and PR flow idempotent.

The date-only branch name collides whenever the workflow is rerun on the same UTC day. git checkout -b or the subsequent push will then fail if that branch or its PR already exists. Use a run-unique branch suffix or detect and update an existing branch/PR before creating a new one.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/bump.yml around lines 58 - 71, Update the branch naming
and PR creation flow around the branch variable, git checkout, git push, and gh
pr create so rerunning the workflow on the same UTC day cannot collide with an
existing branch or pull request. Use a run-unique suffix for the bump branch, or
explicitly detect and update the existing branch and PR before creation, while
preserving the current automated commit and PR metadata.

Comment on lines +136 to +146
case "$RAW_FUZZ_SECONDS" in
''|*[!0-9]*)
echo "::error::fuzz_seconds must be a non-negative integer, got: $RAW_FUZZ_SECONDS"
exit 1
;;
esac
if [ "$RAW_FUZZ_SECONDS" -gt 14400 ]; then
echo "::error::fuzz_seconds exceeds the 14400s (4h) budget: $RAW_FUZZ_SECONDS"
exit 1
fi
printf 'FUZZ_SECS=%s\n' "$RAW_FUZZ_SECONDS" >> "$GITHUB_ENV"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Reject oversized numeric inputs before the -gt comparison.

The digit-only check still accepts arbitrarily long values; once they overflow the shell’s integer comparison, the -gt 14400 guard can fail open and the invalid value gets written to GITHUB_ENV. Use a bounded parse or a string-length check after trimming leading zeroes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-deep.yml around lines 136 - 146, Update the
RAW_FUZZ_SECONDS validation before the numeric -gt comparison to reject values
exceeding the allowed range even when they overflow shell integer parsing. After
validating digits, trim leading zeroes and enforce a bounded length or
equivalent safe parse, while preserving valid zero-padded values and the
existing 14400-second maximum before writing FUZZ_SECS to GITHUB_ENV.

Comment on lines +56 to +61
wget -q "https://nginx.org/download/nginx-${ver}.tar.gz.asc" -O "nginx-${ver}.tar.gz.asc"
gnupghome="$(mktemp -d)"
export GNUPGHOME="$gnupghome"
chmod 700 "$gnupghome"
for keyfile in "$GITHUB_WORKSPACE"/tools/keys/*.key; do
gpg --quiet --import "$keyfile" 2>/dev/null

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- security-scanners.yml ---'
nl -ba .github/workflows/security-scanners.yml | sed -n '1,180p'

echo
echo '--- valgrind.yml ---'
nl -ba .github/workflows/valgrind.yml | sed -n '1,180p'

Repository: myguard-labs/nginx-zstd-module

Length of output: 238


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('.github/workflows/security-scanners.yml'), Path('.github/workflows/valgrind.yml')]:
    print(f'--- {path} ---')
    lines = path.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if 1 <= i <= 220:
            print(f'{i:4}: {line}')
    print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 8976


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('.github/workflows/security-scanners.yml'), Path('.github/workflows/valgrind.yml')]:
    print(f'=== {path} ===')
    text = path.read_text().splitlines()
    for start in [1, 41, 81, 121, 161]:
        if start > len(text):
            continue
        end = min(start + 59, len(text))
        print(f'--- lines {start}-{end} ---')
        for i in range(start, end + 1):
            print(f'{i:4}: {text[i-1]}')
    print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 12578


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('.github/workflows/security-scanners.yml'), Path('.github/workflows/valgrind.yml')]:
    print(f'=== {path} ===')
    lines = path.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if 'gpg' in line or 'apt' in line or 'apk' in line or 'yum' in line or 'dnf' in line or 'brew' in line or 'pacman' in line or 'zypper' in line or 'apt-get' in line:
            lo = max(1, i - 8)
            hi = min(len(lines), i + 8)
            print(f'--- context around line {i} ---')
            for j in range(lo, hi + 1):
                print(f'{j:4}: {lines[j-1]}')
    print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 7933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in [Path('.github/workflows/security-scanners.yml'), Path('.github/workflows/valgrind.yml')]:
    print(f'=== {path} ===')
    lines = path.read_text().splitlines()
    for i, line in enumerate(lines, 1):
        if any(tok in line for tok in ['gpg', 'apt', 'apk', 'yum', 'dnf', 'brew', 'pacman', 'zypper', 'apt-get']):
            lo = max(1, i - 8)
            hi = min(len(lines), i + 8)
            print(f'--- context around line {i} ---')
            for j in range(lo, hi + 1):
                print(f'{j:4}: {lines[j-1]}')
    print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 7933


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in Path('.github/workflows').glob('*.yml'):
    text = path.read_text()
    if 'gpg' in text or 'gnupg' in text:
        print(f'=== {path} ===')
        for i, line in enumerate(text.splitlines(), 1):
            if 'gpg' in line or 'gnupg' in line:
                print(f'{i:4}: {line}')
        print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 2281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

for path in Path('.').rglob('*'):
    if path.is_file() and path.suffix in {'.yml', '.yaml', '.md', '.txt'}:
        try:
            text = path.read_text()
        except Exception:
            continue
        if 'gpg' in text or 'gnupg' in text:
            print(f'=== {path} ===')
            for i, line in enumerate(text.splitlines(), 1):
                if 'gpg' in line or 'gnupg' in line:
                    print(f'{i:4}: {line}')
            print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 2281


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

patterns = ['gnupg', 'gpg', 'builder02', 'lxc', 'self-hosted', 'apt-get install -y', 'apt install -y']
for path in Path('.').rglob('*'):
    if not path.is_file():
        continue
    if path.suffix.lower() not in {'.yml', '.yaml', '.md', '.txt', '.adoc'}:
        continue
    try:
        lines = path.read_text(errors='ignore').splitlines()
    except Exception:
        continue
    hits = []
    for i, line in enumerate(lines, 1):
        if any(p in line for p in patterns):
            hits.append((i, line))
    if hits:
        print(f'=== {path} ===')
        for i, line in hits[:80]:
            print(f'{i:4}: {line}')
        print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 4408


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

targets = [Path('README.md'), Path('docs'), Path('.github')]
for root in targets:
    if root.is_file():
        paths = [root]
    elif root.is_dir():
        paths = [p for p in root.rglob('*') if p.is_file()]
    else:
        continue

    for path in paths:
        try:
            text = path.read_text(errors='ignore')
        except Exception:
            continue
        if any(k in text.lower() for k in ['gpg', 'gnupg', 'builder02', 'lxc', 'self-hosted runner', 'runner image', 'preinstalled']):
            print(f'=== {path} ===')
            for i, line in enumerate(text.splitlines(), 1):
                if any(k in line.lower() for k in ['gpg', 'gnupg', 'builder02', 'lxc', 'self-hosted runner', 'runner image', 'preinstalled']):
                    print(f'{i:4}: {line}')
            print()
PY

Repository: myguard-labs/nginx-zstd-module

Length of output: 3490


Install gpg before the verification steps. Both workflows call gpg --import/--verify, but neither job’s dependency list includes it.

📍 Affects 2 files
  • .github/workflows/security-scanners.yml#L56-L61 (this comment)
  • .github/workflows/valgrind.yml#L59-L65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security-scanners.yml around lines 56 - 61, Install the
gpg package in the dependency setup for both verification workflows before their
gpg usage: update .github/workflows/security-scanners.yml at lines 56-61 and
.github/workflows/valgrind.yml at lines 59-65. Ensure the existing gpg --import
and gpg --verify steps can run without changing their verification behavior.

Comment on lines +57 to +64
gnupghome="$(mktemp -d)"
export GNUPGHOME="$gnupghome"
chmod 700 "$gnupghome"
for keyfile in "$GITHUB_WORKSPACE"/tools/keys/*.key; do
gpg --quiet --import "$keyfile" 2>/dev/null
done
gpg --quiet --verify "nginx-${ver}.tar.gz.asc" "nginx-${ver}.tar.gz"
rm -rf "$gnupghome"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== security-scanners.yml ==\n'
sed -n '1,180p' .github/workflows/security-scanners.yml

printf '\n== valgrind.yml ==\n'
sed -n '1,180p' .github/workflows/valgrind.yml

Repository: myguard-labs/nginx-zstd-module

Length of output: 7796


Install an EXIT trap for GNUPGHOME.

A failed key import or signature check skips the final rm -rf, leaving temp GnuPG directories behind on self-hosted runners.

  • .github/workflows/security-scanners.yml#L57-L64: add trap 'rm -rf "$gnupghome"' EXIT right after creating gnupghome.
  • .github/workflows/valgrind.yml#L61-L68: add the same trap and drop the success-only cleanup.
📍 Affects 2 files
  • .github/workflows/security-scanners.yml#L57-L64 (this comment)
  • .github/workflows/valgrind.yml#L61-L68
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/security-scanners.yml around lines 57 - 64, In the GnuPG
setup around gnupghome in .github/workflows/security-scanners.yml lines 57-64,
add an EXIT trap immediately after creating the temporary directory and retain
the existing cleanup as appropriate. Apply the same trap in
.github/workflows/valgrind.yml lines 61-68, and remove that workflow’s
success-only rm -rf cleanup so both workflows clean up on success and failure.

Comment thread tools/bump-versions.sh
Comment on lines +164 to +169
bump_nginx_sha256_pin() {
local old="$1" new="$2" digest="$3"
grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
sed -i "/declare -A NGINX_SHA256=(/a\\ [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
CHANGED=1
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unify bump_nginx_sha256_pin with bump_angie_sha256_pin.

The two functions are identical except for the target array name (NGINX_SHA256 vs ANGIE_SHA256). Parameterize one helper instead of maintaining two copies.

♻️ Proposed refactor
-bump_angie_sha256_pin() {
-    local old="$1" new="$2" digest="$3"
-    grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
-    sed -i "/declare -A ANGIE_SHA256=(/a\\    [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
-    CHANGED=1
-}
-
-bump_nginx_sha256_pin() {
-    local old="$1" new="$2" digest="$3"
-    grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
-    sed -i "/declare -A NGINX_SHA256=(/a\\    [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
-    CHANGED=1
-}
+bump_sha256_pin() {
+    local array_name="$1" new="$2" digest="$3"
+    grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
+    sed -i "/declare -A ${array_name}=(/a\\    [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
+    CHANGED=1
+}

Callers become bump_sha256_pin ANGIE_SHA256 "$NEW" "$DIGEST" / bump_sha256_pin NGINX_SHA256 "$NEW" "$DIGEST".

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bump_nginx_sha256_pin() {
local old="$1" new="$2" digest="$3"
grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
sed -i "/declare -A NGINX_SHA256=(/a\\ [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
CHANGED=1
}
bump_sha256_pin() {
local array_name="$1" new="$2" digest="$3"
grep -q "\[\"${new}\"\]" tools/ci-build.sh && return 0 # already pinned
sed -i "/declare -A ${array_name}=(/a\\ [\"${new}\"]=\"${digest}\"" tools/ci-build.sh
CHANGED=1
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tools/bump-versions.sh` around lines 164 - 169, Replace the duplicated
bump_nginx_sha256_pin and bump_angie_sha256_pin helpers with a parameterized
bump_sha256_pin accepting the target array name, new version, and digest. Use
the array parameter in the existing grep and sed operations, then update both
callers to pass ANGIE_SHA256 or NGINX_SHA256 accordingly while preserving the
already-pinned check and CHANGED behavior.

eilandert added a commit that referenced this pull request Jul 15, 2026
PR #82 fixed all 7 audit findings but only F5 shipped with an automated
regression test (TAP T78/T79); F1/F2/F3/F4/F6/F7 relied on one-time manual
verification described in commit messages, leaving them free to silently
regress on the next change to ci-deep.yml/ci-build.sh/README.md.

Adds three new tools/test_*.sh scripts, wired into build-test.yml's
validation job (no nginx build required):

- test_fuzz_seconds_validation.sh (F1): drives the exact validation shape
  from ci-deep.yml's "Select fuzz duration" step against injection
  payloads, malformed input, and the 14400s boundary.
- test_ci_build_provenance.sh (F2+F3): reproduces ci-build.sh's
  extract+guarded-mv sequence against a tarball whose top-level dir name
  collides with SRCDIR (the pre-fix failure shape), plus wrong-sha256 and
  corrupted-cache-hit rejection cases. Includes one live clean-root angie
  build (skipped offline).
- test_docs_ci_drift.sh (F7): checks every .github/workflows/*.yml
  filename is referenced in README.md and vice versa, that SECURITY.md
  doesn't link the removed AGENTS.md, and that CONTRIBUTING.md's fuzz
  -Werror claim matches reality. Caught a real drift on first run --
  bump.yml existed but was never referenced by filename in README.md;
  fixed by adding a Bump row to the workflow table.

F4 (bump.yml's PR-open path) and F6 (soak.sh's failure detection) are
left as accepted gaps: F4 needs a live protected-branch fixture repo to
test meaningfully, and F6's own fix already runs as the regression guard
in every existing soak invocation.

All three scripts pass shellcheck/shfmt -i 4 -ci/actionlint clean.
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.

1 participant