Repository navigation
ci: centralize version pins and lane the PR suite #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
0a3ddcf
ci: version pins, laned suite, local lint gate
eilandert 4fcb7e2
ci(linter): fail closed on an empty checker selection, verify actionlint
eilandert 6466df8
ci: correct the lane header -- lane B is at budget, not under it
eilandert e741934
ci(lint): surface git/env failures instead of masking as empty
eilandert 49aeabf
ci(scripts): guard GITHUB_ENV, give compute-versions.sh the fetch-ver…
eilandert 7b6e72d
ci: extract the asan changed-paths gate pattern to one variable
eilandert 0265fbe
ci: wire the actionlint pre-commit hook to .github/actionlint.yaml ex…
eilandert 672eb15
ci(linter): pin ruff, matching the semgrep pin
eilandert 8b47982
ci: let detect-private-key see .pem files again
eilandert File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #!/usr/bin/env bash | ||
| # .githooks/pre-commit -- tracked, reviewable commit gate. | ||
| # | ||
| # Lives in the repo (unlike .git/hooks/, which is per-clone and invisible to | ||
| # review), so a change to what the gate checks arrives as a diff. | ||
| # | ||
| # Enable once per clone: | ||
| # git config core.hooksPath .githooks | ||
| # Emergency bypass: | ||
| # git commit --no-verify | ||
| # | ||
| # It lints only the STAGED files, so a commit is never blocked by a finding in | ||
| # a file it does not touch. Run the whole tree yourself with: | ||
| # ci/linter/run-all.sh | ||
| # | ||
| # Exit 2 (a linter is not installed) blocks the commit on purpose: a gate that | ||
| # skips itself when its tool is missing reports green while checking nothing. | ||
| set -uo pipefail | ||
|
|
||
| ROOT="$(git rev-parse --show-toplevel)" | ||
| "$ROOT/ci/linter/run-all.sh" --staged | ||
| rc=$? | ||
| if [ "$rc" -eq 2 ]; then | ||
| echo "pre-commit: missing linters -- run ci/linter/install-linters.sh" >&2 | ||
| elif [ "$rc" -ne 0 ]; then | ||
| echo "pre-commit: lint failed -- fix, or commit with --no-verify" >&2 | ||
| fi | ||
| exit "$rc" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| #!/usr/bin/env bash | ||
| # compute-versions.sh -- resolve the latest upstream versions + their sha256 and | ||
| # rewrite .github/versions.env in place. Used by bump.yml (weekly). Prints a | ||
| # short summary of what it resolved to stdout, which bump.yml quotes into the | ||
| # PR body. | ||
| # | ||
| # Resolves: | ||
| # nginx mainline (odd minor) + stable (even minor) -- nginx.org download page | ||
| # Angie latest release -- GitHub API tag_name | ||
| # | ||
| # Angie is RESOLVED from the GitHub API (the only machine-readable index of | ||
| # Angie releases) but DOWNLOADED from download.angie.software, which is what | ||
| # ci/tools/ci-build.sh fetches. The two archives differ byte-for-byte, so the | ||
| # digest must come from the URL we actually build from -- hashing the GitHub | ||
| # tag archive here would pin a sha that never matches at build time. | ||
| # | ||
| # Run locally to preview a bump: bash .github/scripts/compute-versions.sh | ||
| # (it rewrites versions.env; `git diff` to review, `git checkout` to discard). | ||
| # | ||
| # Requires: curl, jq, sha256sum. GITHUB_TOKEN honoured for API rate limits -- | ||
| # unauthenticated api.github.com from a shared runner IP gets 403-throttled. | ||
| set -euo pipefail | ||
|
|
||
| VERSIONS_FILE=".github/versions.env" | ||
| FV=".github/scripts/fetch-verify.sh" | ||
| tmp="$(mktemp -d)" | ||
| trap 'rm -rf "$tmp"' EXIT | ||
|
|
||
| api() { | ||
| local url="$1" | ||
| # Same resilience budget as fetch-verify.sh: a transient blip or a stalled | ||
| # connection must retry, not fail the weekly bump job or hang the runner. | ||
| local -a opts=(-fsSL --retry 3 --retry-delay 2 --connect-timeout 30 --max-time 300) | ||
| if [ -n "${GITHUB_TOKEN:-}" ]; then | ||
| curl "${opts[@]}" -H "Authorization: Bearer $GITHUB_TOKEN" "$url" | ||
| else | ||
| curl "${opts[@]}" "$url" | ||
| fi | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| # sha256 of a URL (download to scratch, hash). Fails the job on download error. | ||
| sha_of_url() { | ||
| local url="$1" out="$tmp/dl.$RANDOM" sha | ||
| # In "-" mode fetch-verify.sh sends progress text to stderr, so stdout is | ||
| # exactly one "SHA OUTFILE" line. Read the first field directly rather than | ||
| # picking the last line out of mixed output -- an extra stdout line there | ||
| # would otherwise be captured as a digest with no error. | ||
| read -r sha _ < <(bash "$FV" "$url" - "$out") | ||
| # Validate the shape rather than trusting position. Moving the progress text | ||
| # to stderr fixes today's contamination; this catches the next one, because a | ||
| # non-digest silently written into versions.env would pin every future build | ||
| # to a value that can never match. | ||
| if ! printf '%s' "$sha" | grep -qE '^[0-9a-f]{64}$'; then | ||
| echo "::error::expected a sha256 from $FV for $url, got: ${sha:-<empty>}" >&2 | ||
| return 1 | ||
| fi | ||
| printf '%s' "$sha" | ||
| } | ||
|
|
||
| echo "resolving nginx versions from nginx.org..." | ||
| dl_html="$(curl -fsSL --retry 3 --retry-delay 2 --connect-timeout 30 --max-time 300 \ | ||
| https://nginx.org/en/download.html)" | ||
| # nginx numbers mainline with an odd minor and stable with an even one. Take | ||
| # the highest of each rather than trusting page order. | ||
| NGX_MAINLINE="$(printf '%s' "$dl_html" | grep -oE 'nginx-1\.[0-9]+\.[0-9]+' \ | ||
| | awk -F. '$2%2==1' | sort -uV | tail -1 | sed 's/nginx-//')" | ||
| NGX_STABLE="$(printf '%s' "$dl_html" | grep -oE 'nginx-1\.[0-9]+\.[0-9]+' \ | ||
| | awk -F. '$2%2==0' | sort -uV | tail -1 | sed 's/nginx-//')" | ||
| [ -n "$NGX_MAINLINE" ] && [ -n "$NGX_STABLE" ] || { echo "::error::failed to resolve nginx versions" >&2; exit 1; } | ||
|
|
||
| echo "resolving Angie latest release..." | ||
| ANGIE_TAG="$(api 'https://api.github.com/repos/webserver-llc/angie/releases/latest' | jq -r '.tag_name')" | ||
| [ -n "$ANGIE_TAG" ] && [ "$ANGIE_TAG" != "null" ] || { echo "::error::failed to resolve Angie" >&2; exit 1; } | ||
| # Upstream tags releases "Angie-1.12.1"; ci-build.sh wants the bare version. | ||
| ANGIE="${ANGIE_TAG#Angie-}" | ||
| if ! printf '%s' "$ANGIE" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$'; then | ||
| echo "::error::unexpected Angie tag format: $ANGIE_TAG" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "hashing archives..." | ||
| NGX_MAINLINE_SHA="$(sha_of_url "https://nginx.org/download/nginx-${NGX_MAINLINE}.tar.gz")" | ||
| NGX_STABLE_SHA="$(sha_of_url "https://nginx.org/download/nginx-${NGX_STABLE}.tar.gz")" | ||
| ANGIE_SHA="$(sha_of_url "https://download.angie.software/files/angie-${ANGIE}.tar.gz")" | ||
|
|
||
| cat > "$VERSIONS_FILE" <<EOF | ||
| # Central version + sha256 pins for all CI workflows. | ||
| # | ||
| # SINGLE SOURCE OF TRUTH. Every workflow loads this file into \$GITHUB_ENV as its | ||
| # first step (via .github/scripts/load-versions.sh); the weekly bump.yml job | ||
| # rewrites it and opens a PR. Tarballs are pinned by version string (release | ||
| # archives are immutable) AND verified against the sha256 recorded here, so a | ||
| # compromised or changed upstream archive fails the build instead of being | ||
| # compiled. | ||
| # | ||
| # Version and digest live on adjacent lines on purpose: they are bumped by one | ||
| # writer (compute-versions.sh) in one file, so a version can no longer move | ||
| # while its digest stays behind. | ||
| # | ||
| # Regenerate with .github/scripts/compute-versions.sh (bump.yml runs it weekly). | ||
| # Keep KEY=value, no spaces, no quotes -- this file is both \`source\`d by | ||
| # ci/tools/ci-build.sh and \`cat\`d into \$GITHUB_ENV. | ||
|
|
||
| # nginx mainline (odd minor) -- the default build everywhere; also the | ||
| # ci-deep "mainline" matrix cell. | ||
| NGINX_MAINLINE=${NGX_MAINLINE} | ||
| NGINX_MAINLINE_SHA256=${NGX_MAINLINE_SHA} | ||
|
|
||
| # nginx stable (even minor) -- ci-deep "stable" matrix cell only. | ||
| NGINX_STABLE=${NGX_STABLE} | ||
| NGINX_STABLE_SHA256=${NGX_STABLE_SHA} | ||
|
|
||
| # nginx version used by every single-version job (build-test, asan, valgrind, | ||
| # codeql, fuzzing, security-scanners, ci-deep memcheck). Tracks mainline. | ||
| NGINX_VERSION=${NGX_MAINLINE} | ||
| NGINX_VERSION_SHA256=${NGX_MAINLINE_SHA} | ||
|
|
||
| # Angie (webserver-llc) -- ci-deep "angie" matrix cell. Pinned to the tarball | ||
| # from download.angie.software, NOT the GitHub tag archive: different bytes, | ||
| # so this digest is not interchangeable with a github.com/webserver-llc one. | ||
| # ANGIE_VERSION is the bare version; the upstream release tag is "Angie-<ver>". | ||
| ANGIE_VERSION=${ANGIE} | ||
| ANGIE_SHA256=${ANGIE_SHA} | ||
| EOF | ||
|
|
||
| echo "----- resolved -----" | ||
| echo "nginx mainline: ${NGX_MAINLINE}" | ||
| echo "nginx stable: ${NGX_STABLE}" | ||
| echo "angie: ${ANGIE}" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| #!/usr/bin/env bash | ||
| # fetch-verify.sh URL EXPECTED_SHA256 OUTFILE | ||
| # | ||
| # Download URL to OUTFILE and verify its sha256 against EXPECTED_SHA256. | ||
| # On mismatch: print the actual sha and exit 1 (fails the CI job — a changed | ||
| # or tampered upstream archive never reaches the build). | ||
| # | ||
| # Pass EXPECTED_SHA256="-" to skip verification and just print the computed | ||
| # sha (used by bump.yml to harvest fresh hashes for a version bump). | ||
| # | ||
| # If OUTFILE already exists with the right sha (warm actions/cache hit) the | ||
| # download is skipped — the sha check still runs, so a poisoned cache is caught. | ||
| set -euo pipefail | ||
|
|
||
| url="${1:?usage: fetch-verify.sh URL SHA256 OUTFILE}" | ||
| want="${2:?missing expected sha256}" | ||
| out="${3:?missing output path}" | ||
|
|
||
| sha_of() { sha256sum "$1" | cut -d' ' -f1; } | ||
|
|
||
| if [ -f "$out" ] && [ "$want" != "-" ] && [ "$(sha_of "$out")" = "$want" ]; then | ||
| echo "cache hit (sha ok): $out" | ||
| exit 0 | ||
| fi | ||
|
|
||
| # stderr, not stdout: in "-" mode stdout must carry ONLY the final | ||
| # "$got $out" line, which compute-versions.sh reads as the digest. | ||
| echo "downloading: $url" >&2 | ||
| # -f: fail on HTTP errors; -S: show errors; -L: follow redirects; retries. | ||
| # --connect-timeout/--max-time: a stalled upstream must not hold a runner open. | ||
| curl -fSL --retry 3 --retry-delay 2 \ | ||
| --connect-timeout 30 --max-time 300 -o "$out" "$url" | ||
|
|
||
| got="$(sha_of "$out")" | ||
| if [ "$want" = "-" ]; then | ||
| echo "$got $out" | ||
| exit 0 | ||
| fi | ||
|
|
||
| if [ "$got" != "$want" ]; then | ||
| echo "::error::sha256 MISMATCH for $url" >&2 | ||
| echo " expected: $want" >&2 | ||
| echo " actual: $got" >&2 | ||
| exit 1 | ||
| fi | ||
| echo "sha256 verified: $out" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| #!/usr/bin/env bash | ||
| # load-versions.sh — export every pin from .github/versions.env into the | ||
| # workflow environment. Run as the first step of every CI job: | ||
| # | ||
| # - run: bash .github/scripts/load-versions.sh | ||
| # | ||
| # Skips comments/blanks; validates each line is KEY=value so a malformed | ||
| # versions.env fails loudly instead of injecting garbage into $GITHUB_ENV. | ||
| set -euo pipefail | ||
|
|
||
| f=".github/versions.env" | ||
| [ -f "$f" ] || { echo "::error::$f not found" >&2; exit 1; } | ||
| # Outside a GitHub Actions step $GITHUB_ENV is unset, and set -u turns that | ||
| # into a bare "unbound variable" -- give anyone running this by hand a | ||
| # message that says what actually went wrong. | ||
| [ -n "${GITHUB_ENV:-}" ] || { echo "::error::GITHUB_ENV unset -- run this inside a GitHub Actions step" >&2; exit 1; } | ||
|
|
||
| while IFS= read -r line || [ -n "$line" ]; do | ||
| case "$line" in | ||
| ''|\#*) continue ;; | ||
| esac | ||
| if ! printf '%s' "$line" | grep -qE '^[A-Za-z_][A-Za-z0-9_]*='; then | ||
| echo "::error::malformed line in $f: $line" >&2 | ||
| exit 1 | ||
| fi | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| echo "$line" >> "$GITHUB_ENV" | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| echo "loaded: ${line%%=*}" | ||
| done < "$f" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| # Central version + sha256 pins for all CI workflows. | ||
| # | ||
| # SINGLE SOURCE OF TRUTH. Every workflow loads this file into $GITHUB_ENV as its | ||
| # first step (via .github/scripts/load-versions.sh); the weekly bump.yml job | ||
| # rewrites it and opens a PR. Tarballs are pinned by version string (release | ||
| # archives are immutable) AND verified against the sha256 recorded here, so a | ||
| # compromised or changed upstream archive fails the build instead of being | ||
| # compiled. | ||
| # | ||
| # Version and digest live on adjacent lines on purpose: they are bumped by one | ||
| # writer (compute-versions.sh) in one file, so a version can no longer move | ||
| # while its digest stays behind. | ||
| # | ||
| # Regenerate with .github/scripts/compute-versions.sh (bump.yml runs it weekly). | ||
| # Keep KEY=value, no spaces, no quotes -- this file is both `source`d by | ||
| # ci/tools/ci-build.sh and `cat`d into $GITHUB_ENV. | ||
|
|
||
| # nginx mainline (odd minor) -- the default build everywhere; also the | ||
| # ci-deep "mainline" matrix cell. | ||
| NGINX_MAINLINE=1.31.3 | ||
| NGINX_MAINLINE_SHA256=a7657c50811c2d92d9895395e8b873ef60398142c4db21eb647811c38f6dd525 | ||
|
|
||
| # nginx stable (even minor) -- ci-deep "stable" matrix cell only. | ||
| NGINX_STABLE=1.30.4 | ||
| NGINX_STABLE_SHA256=4261dc90e9e47c1c4041276e9aaa3d48ebe2e664f728e14fa95ae6c67d57a08b | ||
|
|
||
| # nginx version used by every single-version job (build-test, asan, valgrind, | ||
| # codeql, fuzzing, security-scanners, ci-deep memcheck). Tracks mainline. | ||
| NGINX_VERSION=1.31.3 | ||
| NGINX_VERSION_SHA256=a7657c50811c2d92d9895395e8b873ef60398142c4db21eb647811c38f6dd525 | ||
|
|
||
| # Angie (webserver-llc) -- ci-deep "angie" matrix cell. Pinned to the tarball | ||
| # from download.angie.software, NOT the GitHub tag archive: different bytes, | ||
| # so this digest is not interchangeable with a github.com/webserver-llc one. | ||
| # ANGIE_VERSION is the bare version; the upstream release tag is "Angie-<ver>". | ||
| ANGIE_VERSION=1.12.1 | ||
| ANGIE_SHA256=5f4f203be2aca6fe20770b489c720e46e51d337e521065e7e472b61e24e3d2f5 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: myguard-labs/nginx-skeleton-module
Length of output: 11197
🌐 Web query:
Git core.hooksPath working tree directory path relative to current branch checkout security💡 Result:
The git configuration variable core.hooksPath allows users to specify a custom directory for Git hooks instead of the default.git/hooks directory [1][2]. When a relative path is provided, it is interpreted as relative to the root of the working tree in a non-bare repository, or relative to the $GIT_DIR in a bare repository [1][2]. Security Implications The ability to configure core.hooksPath, particularly when stored in repository-local configuration (.git/config), presents a significant security risk [3][4]. If an attacker can influence the repository configuration—for example, through an application that writes configuration entries without sanitization or via direct repo poisoning—they can redirect Git to execute malicious scripts placed within the working tree [3][5][6]. Because these hooks are executed in the context of the user running the Git command, this is a common vector for Remote Code Execution (RCE) [3][5][6]. Key Security Considerations: 1. Attack Vector: If an application allows users to manipulate Git configurations (e.g., setting author names, emails, or arbitrary config keys), an attacker may inject a newline character followed by a core.hooksPath entry into the configuration file to hijack hook execution [3][5]. 2. Defense-in-Depth: Git maintains security features like safe.directory to restrict operations on repositories owned by other users [7]. While there have been attempts to add protections against malicious core.hooksPath settings specifically during clone operations, these were complex to implement without breaking valid workflows and have faced revisions [8][9][10][4]. 3. Trusted Environment: Because core.hooksPath can redirect execution to arbitrary scripts, it should only be set in "protected" configuration scopes (system-wide or user-wide configuration) rather than in repository-local configuration, whenever possible, to prevent untrusted repositories from tampering with execution behavior [7][10]. Users should treat repository-local configurations as untrusted, especially in environments where multiple users or external processes interact with the same Git repositories [3][5]. Application developers should sanitize all inputs that are written to Git configuration files to prevent command or configuration injection [3][5][6].
Citations:
Other (CWE-829): Inclusion of Functionality from Untrusted Control Sphere
Reachability: External
Exploitability: Moderate
Don’t point
core.hooksPathat the mutable worktreegit config core.hooksPath .githooksmakes Git execute hook scripts from the currently checked-out branch. If.githooks/pre-commitorci/linter/run-all.share changed without explicit developer approval, Git will run that modified shell code with the developer’s privileges on commit/amend. Install a fixed trusted hook in.git/hooksor a user-controlled absolute hooks directory, or validate that both the hook and lint helper match an approved revision/cached installer before execution.