-
Notifications
You must be signed in to change notification settings - Fork 2
fix(ci): stop the security-evidence guard failing every out-of-scope PR #2523
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
Changes from all commits
d069847
777f17c
755c270
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,9 +44,24 @@ or finished below the absolute duration floor with no execution evidence (#2337) | |
| EOF | ||
| } | ||
|
|
||
| # pr_touches_security_paths <base-ref> | ||
| # | ||
| # Prints the VERDICT on stdout — `in-scope` or `out-of-scope` — and reserves a | ||
| # non-zero EXIT for a genuine fault (missing python3, an unreadable paths file, | ||
| # an interpreter traceback). The two channels are separate on purpose. | ||
| # | ||
| # The earlier contract signalled out-of-scope by returning 1, which collided | ||
| # with "the check itself broke" on the one status a crashing `python3` also | ||
| # returns. Under `set -e` a bare call then killed the script with no message — | ||
| # every out-of-scope pull request went red beside a lane that had correctly | ||
| # skipped. Consuming that return with `||` fixes the red check but keeps the | ||
| # collision, and turns it fail-OPEN: a crashed scope check reads as | ||
| # out-of-scope and waves the pull request past a security guard. ShellCheck | ||
| # names this trap directly (SC2310, enabled on purpose in this repo's | ||
| # `.shellcheckrc`). Separating verdict from status closes both. | ||
| pr_touches_security_paths() { | ||
| local base_ref="$1" | ||
| [[ -f "$PATHS_FILE" ]] || return 0 | ||
| [[ -f "$PATHS_FILE" ]] || { printf 'in-scope\n'; return 0; } | ||
| python3 - "$PATHS_FILE" "$base_ref" <<'PY' | ||
| import fnmatch | ||
| import re | ||
|
|
@@ -84,12 +99,20 @@ diff = subprocess.run( | |
| capture_output=True, | ||
| text=True, | ||
| ) | ||
| if diff.returncode != 0: | ||
| sys.stderr.write( | ||
| diff.stderr | ||
| or f"git diff --name-only origin/{base_ref}...HEAD failed (exit {diff.returncode})\n" | ||
| ) | ||
| sys.exit(1) | ||
|
Comment on lines
+102
to
+107
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Test-coverage gap: this fail-closed branch has no regression test. This block was added in the third commit (755c270, "fail closed when scope git diff cannot resolve base ref") specifically to close the fail-open hole @chatgpt-codex-connector flagged — a Every other behavior change in this PR got a dedicated regression test in Nothing here currently exercises "the Python helper's Not a blocker on a merged PR, just flagging for a follow-up. |
||
| changed = [line for line in diff.stdout.splitlines() if line] | ||
| for path in changed: | ||
| for pat in patterns: | ||
| if pattern_matches(path, pat): | ||
| print("in-scope") | ||
| sys.exit(0) | ||
| sys.exit(1) | ||
| print("out-of-scope") | ||
| sys.exit(0) | ||
|
kyle-sexton marked this conversation as resolved.
|
||
| PY | ||
| } | ||
|
|
||
|
|
@@ -133,12 +156,25 @@ main() { | |
|
|
||
| local base_ref="${GITHUB_BASE_REF:-main}" | ||
| git fetch origin "$base_ref" --depth=1 >/dev/null 2>&1 || true | ||
| pr_touches_security_paths "$base_ref" | ||
| local in_scope=$? | ||
| if (( in_scope != 0 )); then | ||
| echo "diff does not touch security-relevant paths — guard not applicable" | ||
| exit 0 | ||
| fi | ||
| # A command substitution keeps `set -e` live for the helper (no `||` | ||
| # suppression), so a genuine fault inside it still aborts the guard — while | ||
| # the in-scope decision travels on stdout, where it cannot be confused with | ||
| # one. An unrecognised verdict is treated as a fault, never as a pass: this | ||
| # is a security guard, and the only safe default when it cannot tell whether | ||
| # a pull request is in scope is to fail loudly. | ||
| local scope_verdict | ||
| scope_verdict="$(pr_touches_security_paths "$base_ref")" | ||
| case "$scope_verdict" in | ||
| in-scope) ;; | ||
| out-of-scope) | ||
| echo "diff does not touch security-relevant paths — guard not applicable" | ||
| exit 0 | ||
| ;; | ||
| *) | ||
| echo "ERROR: scope check returned an unrecognised verdict: ${scope_verdict}" >&2 | ||
| exit 1 | ||
| ;; | ||
| esac | ||
|
|
||
| local jobs_json | ||
| jobs_json="$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID/jobs" --paginate)" | ||
|
|
||
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.
Test-coverage gap: this fail-closed branch has no regression test.
This block was added in the third commit (755c270, "fail closed when scope git diff cannot resolve base ref") specifically to close the fail-open hole flagged in review — a
git difffailure (unresolvableorigin/$base_ref, shallow-fetch merge-base failure, etc.) previously fell through toout-of-scopewith exit 0.Every other behavior change in this PR got a dedicated regression test in
verify-security-review-evidence.sh.test.sh(the bare-call-under-set -erepro, the||-suppression static guard, the unrecognised-verdict catch-all). This branch didn't — diffing the follow-up commit against its parent showsverify-security-review-evidence.sh.test.shwasn't touched:Nothing currently exercises "the Python helper's
git diffsubprocess fails" and asserts the guard fails closed (nonzero exit, not a silentout-of-scope). Given this is precisely the failure mode this block exists to prevent, and the rest of the PR's own bar is regression coverage for every fail-open path found, this seems worth closing — e.g. a case that runspr_touches_security_paths(or an equivalent harness) against abase_refthat can't resolve, and asserts nonzero exit.Not a blocker on an already-merged PR, just flagging for a follow-up.