Skip to content

fix(ci): gate Bandit and OSV Scanner jobs on detected security-relevant changes - #300

Merged
williaby merged 1 commit into
mainfrom
claude/wire-security-change-gating-0
Sep 5, 2026
Merged

fix(ci): gate Bandit and OSV Scanner jobs on detected security-relevant changes#300
williaby merged 1 commit into
mainfrom
claude/wire-security-change-gating-0

Conversation

@williaby

@williaby williaby commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes a dead-code defect in the python-security-analysis.yml reusable workflow: the detect-changes job (dorny/paths-filter) published a security_files output that nothing consumed. Every run paid for the extra Detect Security Changes job while the two expensive downstream jobs (Python SAST (Bandit), OSV Vulnerability Scanner) ran unconditionally on nothing but their input toggles.

Fix

Wired needs.detect-changes.outputs.security_files into both jobs' if: conditions, ANDed with the existing run-bandit / run-osv input gates:

# python-security
if: ${{ inputs.run-bandit && (github.event_name != 'pull_request' || needs.detect-changes.outputs.security_files == 'true') }}

# osv-scanner
if: ${{ inputs.run-osv && (github.event_name != 'pull_request' || needs.detect-changes.outputs.security_files == 'true') }}

Why the pull_request restriction

dorny/paths-filter has no diff base on schedule, push, or workflow_dispatch, so its output will not reliably be 'true' on those events. The github.event_name != 'pull_request' clause short-circuits the || to true on every non-pull_request event, so both jobs keep running exactly as they do today outside pull_request. The change-based skip applies ONLY to pull_request events.

This distinction matters most for OSV Scanner: it is LIVE-STATE-DEPENDENT, not commit-determined. It resolves a frozen lockfile against a continuously updated CVE feed, so its result can change with zero commits in the repo. Its scheduled run is the entire point of scheduling it; a lockfile clean last week can be vulnerable today. Bandit, by contrast, is commit-determined (a pure function of the source at that commit), so skipping it on an unchanged-tree pull_request is safe.

Three-scenario walkthrough

  1. Pull request touching only docs: github.event_name != 'pull_request' is false. security_files does not match doc-only changes, so it is not 'true'; the || evaluates false. inputs.run-bandit/run-osv && false = false -> both jobs skip.
  2. Pull request touching a .py file: github.event_name != 'pull_request' is false, but security_files == 'true' (the filter includes **/*.py); the || evaluates true. inputs.enabled && true = true -> both jobs run.
  3. Schedule event: github.event_name != 'pull_request' is true, short-circuiting the || to true regardless of the (unreliable, no-diff-base) filter output. inputs.enabled && true = true -> both jobs run, exactly as before.

Explicit parentheses are used throughout so the intended A && (B || C) grouping is unambiguous regardless of GitHub Actions' native &&/|| precedence.

Security-gate skip handling

Checked whether security-gate (needs: [detect-changes, python-security, osv-scanner], if: always()) would read a newly-skipped dependency as a failure. Its validation step already handles this correctly:

is_acceptable() {
  [[ "$1" == "success" || "$1" == "skipped" ]]
}

if is_acceptable "$RESULT_PYTHON_SECURITY" && \
   is_acceptable "$RESULT_OSV_SCANNER"; then
  echo "Security gate passed"
else
  echo "Security gate failed - review security scan results"
  exit 1
fi

Verdict: no change needed. is_acceptable already treats "skipped" as passing alongside "success", so a job newly skipped by this change reads as a pass, not a failure.

RAD tags

Added #CRITICAL / #ASSUME / #EDGE / #VERIFY comment blocks above both modified if: lines explaining the pull_request-only restriction and, for OSV specifically, why it must never be skipped on schedule.

What was NOT touched

  • No job name: value changed (these are required status check contexts in org rulesets).
  • detect-changes job and its paths-filter step are unchanged; the existing filter list (including .github/workflows/**) is untouched.
  • No workflow input name or default changed.
  • No other workflow file touched.

Rollout note

Consumer repos are SHA-pinned to this reusable workflow. This change reaches a repo only when that repo's Renovate/manual pin bump picks up the new commit; it cannot silently regress any consumer until they re-pin.

Verification

  • actionlint .github/workflows/python-security-analysis.yml: no new findings introduced (only pre-existing, unrelated shellcheck info/style notices on unmodified run: blocks elsewhere in the file, confirmed identical on origin/main).
  • pre-commit run --all-files: all hooks passed.

Generated with Claude Code

Summary by CodeRabbit

  • CI/CD
    • Security analysis now runs for scheduled, push, and manually triggered workflows regardless of detected file changes.
    • Pull request security scans remain limited to changes affecting security-relevant files.
    • Added workflow documentation clarifying when security checks are executed.

…nt changes

The detect-changes job (dorny/paths-filter) published a security_files
output that no downstream job consumed, so every run paid for the
Detect Security Changes job while python-security and osv-scanner ran
unconditionally on nothing but the input toggle.

Wire needs.detect-changes.outputs.security_files into both jobs'
if: conditions, ANDed with the existing run-bandit/run-osv gates so
current opt-outs keep working. The skip applies only on pull_request
events: paths-filter has no diff base on schedule, push, or
workflow_dispatch, so treating those as change-detected via
github.event_name != 'pull_request' keeps Bandit and OSV Scanner
running unconditionally outside pull_request, exactly as before.

OSV Scanner in particular must never be skipped on schedule: it
resolves a frozen lockfile against a live CVE feed, so its result can
change with zero commits in the repo. The scheduled run is the entire
point of scheduling it.

security-gate already treats a skipped dependency as passing via its
is_acceptable() helper (checks for success or skipped), so no gate
change was needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 5, 2026 16:59
@coderabbitai

coderabbitai Bot commented Sep 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Team

Run ID: 18a67a73-774b-41d2-aada-0c1405cb5e85

📥 Commits

Reviewing files that changed from the base of the PR and between 63197b1 and d1d6911.

📒 Files selected for processing (1)
  • .github/workflows/python-security-analysis.yml

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The workflow now restricts security path filtering to pull requests. Bandit and OSV Scanner run on schedule, push, and manual events when their inputs enable them.

Changes

Security workflow event gating

Layer / File(s) Summary
Event-aware security job conditions
.github/workflows/python-security-analysis.yml
Bandit and OSV Scanner now require security-file changes only for pull_request events. Both jobs continue to respect their input flags and run on other supported events.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to d1d69

Bandit and OSV scans now skip pull requests without security-relevant changes while continuing to run when enabled on push, scheduled, and manual workflows. The change is ready to merge.

Suggested reviewers: byronwilliamscpa

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main workflow change: gating the Bandit and OSV Scanner jobs on detected security-relevant changes.
Description check ✅ Passed The description is detailed and covers the change, rationale, event-specific behavior, skip handling, scope, and verification results. It does not use all template sections, including the related issu…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/wire-security-change-gating-0

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.

@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟢 Approval recommended

The gating logic is sound and preserves non-PR execution, with only minor documentation/comment accuracy tweaks suggested.

Pull request overview

This PR updates the org reusable GitHub Actions workflow to skip the Bandit and OSV Scanner jobs on pull requests that do not touch security-relevant files, using the existing detect-changes (dorny/paths-filter) job output.

Changes:

  • Gate python-security (Bandit) on inputs.run-bandit and needs.detect-changes.outputs.security_files for pull_request events.
  • Gate osv-scanner on inputs.run-osv and needs.detect-changes.outputs.security_files for pull_request events.
  • Add inline rationale and verification notes above both modified if: conditions.
File summaries
File Description
.github/workflows/python-security-analysis.yml Uses detect-changes output to skip Bandit and OSV Scanner on non-security PRs while preserving non-PR behavior.
Review details

Suppressed comments (1)

.github/workflows/python-security-analysis.yml:307

  • The #ASSUME note claims dorny/paths-filter has no diff base on schedule/push/workflow_dispatch. paths-filter can compute changes on these events (with different base semantics), so the comment is misleading. It would be clearer to document that we only use the paths-filter output to skip on pull_request, and force this job to run on all other events to avoid accidentally suppressing scheduled CVE scans.
    # #ASSUME paths-filter has no diff base on schedule, push, or
    # workflow_dispatch, so needs.detect-changes.outputs.security_files will
    # not reliably be 'true' on those events. The `github.event_name !=
    # 'pull_request'` clause short-circuits the OR to true there, so this job
    # always runs outside pull_request regardless of the filter output. The
  • Files reviewed: 1/1 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +181 to +185
# #ASSUME paths-filter has no diff base on schedule, push, or
# workflow_dispatch, so needs.detect-changes.outputs.security_files will
# not reliably be 'true' on those events. The `github.event_name !=
# 'pull_request'` clause short-circuits the OR to true there, so the job
# always runs outside pull_request regardless of the filter output.
Comment on lines +190 to +192
# #VERIFY run `gh api repos/ByronWilliamsCPA/.github/actions/workflows` and
# inspect a recent scheduled run of this workflow to confirm python-security
# shows result "success" (not "skipped") even though no PR diff exists.
@williaby
williaby added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit cb0742c Sep 5, 2026
37 checks passed
@williaby
williaby deleted the claude/wire-security-change-gating-0 branch September 5, 2026 17:30
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.

2 participants