-
Notifications
You must be signed in to change notification settings - Fork 0
feat: central gap-filling security workflows (bandit/semgrep/gitleaks/pip-audit + periodic) + trivy/osv reconciliation #374
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
23 commits
Select commit
Hold shift + click to select a range
bf61979
feat: add central gap-filling security workflows + trivy/osv reconcil…
5fabf12
fix(security): hash-pin pip installs in central python-security & sas…
6ddc74f
fix(security): mark clean OSV SARIF comprehensive
seonghobae 844b396
fix(ci): scope PR workflow concurrency by PR number
seonghobae 475c530
fix(noema): isolate workflow-run followup concurrency
seonghobae 8b469c5
fix(security): preserve OSV scan analysis category
seonghobae 271dfcd
fix(security): upload OSV clean SARIF to PR merge ref
seonghobae 080cf35
fix(security): resolve PR merge SHA for OSV SARIF
seonghobae 9969744
fix(security): upload OSV SARIF against PR head
seonghobae 2857aec
fix(security): fetch PR merge ref before OSV SARIF upload
seonghobae ab64e9d
fix(security): upload OSV SARIF against PR head
seonghobae 963d979
fix(security): keep Strix status publishing least privilege
seonghobae d142329
test(strix): align smoke with read-only status token
seonghobae 3308105
fix(security): checkout PR merge ref before OSV upload
seonghobae ee343c0
fix(strix): restore trusted smoke status contract
seonghobae eef23ee
fix(security): pin OSV SARIF upload to PR head
seonghobae d79e230
fix: log concrete scheduler merge blockers
seonghobae 2658edd
docs: record central ruleset approval gate
seonghobae 7597427
fix(coverage): restore governance and docstring evidence
seonghobae 3eab19e
test: align scorecard governance contract
seonghobae 8c19679
ci: bound opencode model pool queue stalls
seonghobae cb89e0b
Merge remote-tracking branch 'origin/main' into codex-pr374-osv-sarif…
seonghobae 35366b7
fix(strix): keep scan token status read-only
seonghobae 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
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,247 @@ | ||
| # Central Python security gate for every ContextualWisdomLab repo. | ||
| # | ||
| # Fills a governance gap left when duplicate LOCAL workflows were removed in | ||
| # favour of the central required workflows: the central Security Scan bundle | ||
| # covers supply-chain (osv / dependency-review / trivy) and posture (scorecard), | ||
| # but NOT Python source SAST (bandit) or the Python dependency audit | ||
| # (pip-audit) that some repos ran locally (naruon, bandscope, | ||
| # xtrmLLMBatchPython, contextual-orchestrator). | ||
| # | ||
| # bandit Python SAST -> SARIF uploaded under category "bandit" | ||
| # pip-audit dep audit -> HARD gate by job result (like osv/trivy) | ||
| # | ||
| # Both jobs are CONDITIONAL on the repo actually containing Python, so | ||
| # non-Python repos are a no-op. Gating is by the JOB result, ref-independent, | ||
| # exactly like the trivy-fs / osv-scan jobs in security-scan.yml. The SARIF is | ||
| # uploaded under a DISTINCT category ("bandit"); it is NOT added to the | ||
| # code_scanning ruleset rule, which stays CodeQL-only on purpose (requiring | ||
| # multiple tools in that rule is unsatisfiable across PR head/merge refs), so | ||
| # it does not affect auto-merge. | ||
| # | ||
| # High sensitivity: bandit fails on MEDIUM+ severity & MEDIUM+ confidence; | ||
| # pip-audit fails on any known vulnerability. | ||
| name: Python Security | ||
|
|
||
| on: | ||
| pull_request: | ||
| types: [opened, synchronize, reopened, ready_for_review, closed] | ||
| branches: [main, master, develop] | ||
| push: | ||
| branches: [main, master, develop] | ||
| # Periodic full-repo coverage so non-PR drift is caught (the removed local | ||
| # workflows ran on push + schedule). | ||
| schedule: | ||
| - cron: "17 3 * * 1" | ||
| workflow_dispatch: {} | ||
|
|
||
| concurrency: | ||
| group: python-security-${{ github.event.pull_request.base.repo.full_name || github.repository }}-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| cancel-closed-pr-runs: | ||
| if: github.event.action == 'closed' | ||
| runs-on: ubuntu-latest | ||
| steps: | ||
| - run: echo "PR closed; this run only cancels older runs through workflow concurrency." | ||
|
|
||
| detect-python: | ||
| name: Detect Python | ||
| if: github.event.action != 'closed' | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| has_python: ${{ steps.detect.outputs.has_python }} | ||
| has_manifest: ${{ steps.detect.outputs.has_manifest }} | ||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Detect Python sources and dependency manifests | ||
| id: detect | ||
| run: | | ||
| set -euo pipefail | ||
| has_python=false | ||
| if find . -type f -name '*.py' -not -path './.git/*' | head -1 | grep -q .; then | ||
| has_python=true | ||
| fi | ||
| has_manifest=false | ||
| if find . -type f \ | ||
| \( -name 'requirements*.txt' -o -name 'pyproject.toml' \ | ||
| -o -name 'pylock.*.toml' \) \ | ||
| -not -path './.git/*' | head -1 | grep -q .; then | ||
| has_manifest=true | ||
| fi | ||
| echo "has_python=${has_python}" >> "$GITHUB_OUTPUT" | ||
| echo "has_manifest=${has_manifest}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| bandit: | ||
| name: Bandit (Python SAST) | ||
| needs: detect-python | ||
| if: github.event.action != 'closed' && needs.detect-python.outputs.has_python == 'true' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| security-events: write | ||
| actions: read | ||
| steps: | ||
| - name: Harden the runner (Audit all outbound calls) | ||
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | ||
| with: | ||
| egress-policy: audit | ||
| - name: Checkout | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Set up Python | ||
| uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 | ||
| with: | ||
| python-version: "3.12" | ||
| - name: Install bandit | ||
| # Hash-pinned lock (uv-generated) satisfies Scorecard Pinned-Dependencies. | ||
| run: python -m pip install --require-hashes -r requirements-bandit-ci-hashes.txt | ||
| - name: Run bandit (SARIF) | ||
| id: bandit | ||
| run: | | ||
| set +e | ||
| bandit --recursive . \ | ||
| --severity-level medium \ | ||
| --confidence-level medium \ | ||
| --exclude ./.git,./.github,./node_modules,./.venv,./venv,./tests,./test \ | ||
| --format json \ | ||
| --output bandit-results.json | ||
| echo "rc=$?" >> "$GITHUB_OUTPUT" | ||
| set -e | ||
| if [ ! -s bandit-results.json ]; then | ||
| echo "::error::Bandit did not produce bandit-results.json; inspect the Bandit command output above." | ||
| exit 2 | ||
| fi | ||
| python - <<'PY' | ||
| import json | ||
| from pathlib import Path | ||
|
|
||
| data = json.loads(Path("bandit-results.json").read_text(encoding="utf-8")) | ||
| issues = data.get("results", []) | ||
| rules = {} | ||
| results = [] | ||
| levels = {"HIGH": "error", "MEDIUM": "warning", "LOW": "note"} | ||
|
|
||
| for issue in issues: | ||
| rule_id = issue.get("test_id") or "bandit" | ||
| name = issue.get("test_name") or rule_id | ||
| text = issue.get("issue_text") or "Bandit finding" | ||
| severity = issue.get("issue_severity", "UNKNOWN") | ||
| confidence = issue.get("issue_confidence", "UNKNOWN") | ||
| filename = (issue.get("filename") or "").replace("\\", "/").lstrip("./") | ||
| line = int(issue.get("line_number") or 1) | ||
| message = f"{rule_id}: {text} (severity={severity}, confidence={confidence})" | ||
|
|
||
| print(f"::error file={filename},line={line},title={rule_id}::{message}") | ||
| rules.setdefault( | ||
| rule_id, | ||
| { | ||
| "id": rule_id, | ||
| "name": name, | ||
| "shortDescription": {"text": name}, | ||
| "fullDescription": {"text": text}, | ||
| "helpUri": issue.get("more_info", "https://bandit.readthedocs.io/"), | ||
| }, | ||
| ) | ||
| results.append( | ||
| { | ||
| "ruleId": rule_id, | ||
| "level": levels.get(severity, "warning"), | ||
| "message": {"text": message}, | ||
| "locations": [ | ||
| { | ||
| "physicalLocation": { | ||
| "artifactLocation": {"uri": filename}, | ||
| "region": {"startLine": line}, | ||
| } | ||
| } | ||
| ], | ||
| } | ||
| ) | ||
|
|
||
| print(f"Bandit findings at configured threshold: {len(issues)}") | ||
| sarif = { | ||
| "$schema": "https://json.schemastore.org/sarif-2.1.0.json", | ||
| "version": "2.1.0", | ||
| "runs": [ | ||
| { | ||
| "tool": { | ||
| "driver": { | ||
| "name": "Bandit", | ||
| "informationUri": "https://bandit.readthedocs.io/", | ||
| "rules": list(rules.values()), | ||
| } | ||
| }, | ||
| "results": results, | ||
| } | ||
| ], | ||
| } | ||
| Path("bandit-results.sarif").write_text(json.dumps(sarif, indent=2), encoding="utf-8") | ||
| PY | ||
| - name: Upload Bandit SARIF to code scanning | ||
| if: always() && hashFiles('bandit-results.sarif') != '' | ||
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | ||
| with: | ||
| sarif_file: bandit-results.sarif | ||
| category: bandit | ||
| - name: Enforce bandit gate (fail on MEDIUM+ findings) | ||
| if: steps.bandit.outputs.rc != '0' | ||
| run: | | ||
| echo "::error::Bandit found MEDIUM+ severity/confidence issues. See the 'bandit' code scanning category." | ||
| exit 1 | ||
|
|
||
| pip-audit: | ||
| name: pip-audit (Python dependency audit) | ||
| needs: detect-python | ||
| if: github.event.action != 'closed' && needs.detect-python.outputs.has_manifest == 'true' | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - name: Harden the runner (Audit all outbound calls) | ||
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | ||
| with: | ||
| egress-policy: audit | ||
| - name: Checkout | ||
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | ||
| with: | ||
| persist-credentials: false | ||
| - name: Set up Python | ||
| uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 | ||
| with: | ||
| python-version: "3.12" | ||
| - name: Install pip-audit | ||
| # Hash-pinned lock (uv-generated) satisfies Scorecard Pinned-Dependencies. | ||
| run: python -m pip install --require-hashes -r requirements-pip-audit-ci-hashes.txt | ||
| - name: Run pip-audit (hard gate on any known vulnerability) | ||
|
github-advanced-security[bot] marked this conversation as resolved.
Fixed
|
||
| run: | | ||
| set -euo pipefail | ||
| status=0 | ||
|
|
||
| # Audit every discovered requirements file. | ||
| while IFS= read -r req; do | ||
| echo "::group::pip-audit -r ${req}" | ||
| pip-audit --strict --desc=on -r "${req}" || status=1 | ||
| echo "::endgroup::" | ||
| done < <(find . -type f -name 'requirements*.txt' -not -path './.git/*') | ||
|
|
||
| # Audit the project itself when a PEP 621 / lock manifest exists. | ||
| if find . -maxdepth 2 -type f \ | ||
| \( -name 'pyproject.toml' -o -name 'pylock.*.toml' \) \ | ||
| -not -path './.git/*' | head -1 | grep -q .; then | ||
| echo "::group::pip-audit . (project manifest)" | ||
| pip-audit --strict --desc=on . || status=1 | ||
| echo "::endgroup::" | ||
| fi | ||
|
|
||
| if [ "${status}" != "0" ]; then | ||
| echo "::error::pip-audit reported known-vulnerable Python dependencies." | ||
| exit 1 | ||
| fi | ||
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.
Uh oh!
There was an error while loading. Please reload this page.