fix(ci): route critical supply-chain findings through review gate (#68833)
Let the scanner report critical findings without failing. The review-label gate owns the action-required status and blocking result, allowing the ci-reviewed label rerun to clear both CI and the PR comment.
This commit is contained in:
@@ -158,12 +158,13 @@ jobs:
|
||||
|
||||
review-labels:
|
||||
name: Review label gate
|
||||
needs: detect
|
||||
if: needs.detect.outputs.event_name == 'pull_request' && (needs.detect.outputs.ci_review == 'true' || needs.detect.outputs.mcp_catalog == 'true')
|
||||
needs: [detect, supply-chain]
|
||||
if: always() && needs.detect.outputs.event_name == 'pull_request' && (needs.detect.outputs.ci_review == 'true' || needs.detect.outputs.mcp_catalog == 'true' || needs.supply-chain.outputs.critical_findings == 'true')
|
||||
uses: ./.github/workflows/review-labels.yml
|
||||
with:
|
||||
ci_review: ${{ needs.detect.outputs.ci_review == 'true' }}
|
||||
mcp_catalog: ${{ needs.detect.outputs.mcp_catalog == 'true' }}
|
||||
supply_chain: ${{ needs.supply-chain.outputs.critical_findings == 'true' }}
|
||||
secrets: inherit
|
||||
|
||||
osv-scanner:
|
||||
|
||||
@@ -27,6 +27,10 @@ on:
|
||||
description: Whether the MCP catalog / installer changed.
|
||||
type: boolean
|
||||
default: false
|
||||
supply_chain:
|
||||
description: Whether the critical supply-chain scan found a risk requiring review.
|
||||
type: boolean
|
||||
default: false
|
||||
outputs:
|
||||
ci_reviewed:
|
||||
description: Whether the ci-reviewed label is present. Empty when neither input was true.
|
||||
@@ -42,7 +46,7 @@ permissions:
|
||||
jobs:
|
||||
check:
|
||||
name: Review label gate
|
||||
if: inputs.ci_review || inputs.mcp_catalog
|
||||
if: inputs.ci_review || inputs.mcp_catalog || inputs.supply_chain
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 2
|
||||
outputs:
|
||||
@@ -75,15 +79,17 @@ jobs:
|
||||
env:
|
||||
CI_REVIEW: ${{ inputs.ci_review }}
|
||||
MCP_CATALOG: ${{ inputs.mcp_catalog }}
|
||||
SUPPLY_CHAIN: ${{ inputs.supply_chain }}
|
||||
LABEL_PRESENT: ${{ steps.label-check.outputs.ci_reviewed }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
ARGS=""
|
||||
if [ "$CI_REVIEW" = "true" ]; then ARGS="$ARGS --ci-review"; fi
|
||||
if [ "$MCP_CATALOG" = "true" ]; then ARGS="$ARGS --mcp-catalog"; fi
|
||||
if [ "$LABEL_PRESENT" = "true" ]; then ARGS="$ARGS --label-present"; fi
|
||||
args=()
|
||||
if [ "$CI_REVIEW" = "true" ]; then args+=(--ci-review); fi
|
||||
if [ "$MCP_CATALOG" = "true" ]; then args+=(--mcp-catalog); fi
|
||||
if [ "$SUPPLY_CHAIN" = "true" ]; then args+=(--supply-chain); fi
|
||||
if [ "$LABEL_PRESENT" = "true" ]; then args+=(--label-present); fi
|
||||
|
||||
python3 scripts/ci/emit_review_status.py $ARGS --output "$GITHUB_OUTPUT"
|
||||
python3 scripts/ci/emit_review_status.py "${args[@]}" --output "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Fail on missing label
|
||||
if: steps.label-check.outputs.ci_reviewed != 'true'
|
||||
|
||||
@@ -16,11 +16,12 @@ name: Supply Chain Audit
|
||||
# ``review-labels.yml`` so it can be rerun independently.
|
||||
#
|
||||
# Outputs:
|
||||
# review_status — JSON array of status objects consumed by the review
|
||||
# comment assembler (scripts/ci/assemble_review_comment.py).
|
||||
# Each job (``scan``, ``dep-bounds``) emits its own
|
||||
# array; an ``aggregate`` job merges them into the
|
||||
# workflow-level output.
|
||||
# review_status — JSON array of status objects consumed by the review
|
||||
# comment assembler (scripts/ci/assemble_review_comment.py).
|
||||
# critical_findings — "true" when the narrow critical-pattern scan found
|
||||
# something. The review-label gate consumes this and
|
||||
# owns the action-required result, so adding
|
||||
# ``ci-reviewed`` can heal the run on rerun.
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
@@ -41,6 +42,9 @@ on:
|
||||
review_status:
|
||||
description: JSON array of review status objects for the review comment assembler.
|
||||
value: ${{ jobs.aggregate.outputs.review_status }}
|
||||
critical_findings:
|
||||
description: Whether the critical-pattern scan found a risk requiring maintainer review.
|
||||
value: ${{ jobs.aggregate.outputs.critical_findings }}
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
@@ -54,6 +58,7 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
review_status: ${{ steps.emit-status.outputs.review_status }}
|
||||
critical_findings: ${{ steps.scan.outputs.found }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
@@ -165,33 +170,15 @@ jobs:
|
||||
python3 - <<'PYEOF'
|
||||
import json, os
|
||||
|
||||
found = os.environ.get("FOUND", "") == "true"
|
||||
|
||||
if found:
|
||||
with open("/tmp/findings.md", encoding="utf-8") as f:
|
||||
detail = f.read()
|
||||
status = [{
|
||||
"source": "supply chain",
|
||||
"results": [{
|
||||
"kind": "error",
|
||||
"title": "Critical supply chain risk",
|
||||
"summary": "Critical supply chain risk patterns detected in this PR.",
|
||||
"detail": detail,
|
||||
"how_to_fix": "Review the flagged code carefully. If intentional, add the `ci-reviewed` label."
|
||||
}]
|
||||
}]
|
||||
else:
|
||||
status = []
|
||||
# The review-label gate renders and blocks critical findings. Keep
|
||||
# this scan a fact-finder so adding ci-reviewed can rerun the gate
|
||||
# without requiring the scanner itself to fail again.
|
||||
status = []
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
|
||||
f.write(f"review_status={json.dumps(status)}\n")
|
||||
PYEOF
|
||||
|
||||
- name: Fail on critical findings
|
||||
if: steps.scan.outputs.found == 'true'
|
||||
run: |
|
||||
echo "::error::CRITICAL supply chain risk patterns detected in this PR. See the review comment for details."
|
||||
exit 1
|
||||
|
||||
dep-bounds:
|
||||
name: Check PyPI dependency upper bounds
|
||||
@@ -279,12 +266,14 @@ jobs:
|
||||
timeout-minutes: 15
|
||||
outputs:
|
||||
review_status: ${{ steps.merge.outputs.review_status }}
|
||||
critical_findings: ${{ steps.merge.outputs.critical_findings }}
|
||||
steps:
|
||||
- name: Merge review statuses
|
||||
id: merge
|
||||
env:
|
||||
SCAN_STATUS: ${{ needs.scan.outputs.review_status }}
|
||||
DEP_STATUS: ${{ needs.dep-bounds.outputs.review_status }}
|
||||
CRITICAL_FINDINGS: ${{ needs.scan.outputs.critical_findings }}
|
||||
run: |
|
||||
python3 - <<'PYEOF'
|
||||
import json, os
|
||||
@@ -303,4 +292,5 @@ jobs:
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as f:
|
||||
f.write(f"review_status={json.dumps(merged)}\n")
|
||||
f.write("critical_findings=" + os.environ.get("CRITICAL_FINDINGS", "false") + "\n")
|
||||
PYEOF
|
||||
|
||||
@@ -18,8 +18,8 @@ The ``source`` field is the workflow name that declared the status; the
|
||||
assembler uses it to exclude the corresponding job from the synthesized
|
||||
❌ Error list (the job already has its own status section).
|
||||
|
||||
The array can contain 0, 1, or 2 results — one per lane that ran
|
||||
(``ci_review``, ``mcp_catalog``). When the ``ci-reviewed`` label is
|
||||
The array can contain 0 to 3 results — one per lane that ran
|
||||
(``ci_review``, ``mcp_catalog``, ``supply_chain``). When the ``ci-reviewed`` label is
|
||||
present, the kind is ``info``; when missing, it's ``action_required``
|
||||
with the verification checklist.
|
||||
"""
|
||||
@@ -44,6 +44,7 @@ SOURCE = "review-label-gate"
|
||||
def build_results(
|
||||
ci_review: bool,
|
||||
mcp_catalog: bool,
|
||||
supply_chain: bool,
|
||||
label_present: bool,
|
||||
) -> list[dict]:
|
||||
"""Build the list of result objects for this source."""
|
||||
@@ -99,16 +100,28 @@ def build_results(
|
||||
),
|
||||
})
|
||||
|
||||
if supply_chain and not label_present:
|
||||
results.append({
|
||||
"kind": "action_required",
|
||||
"title": "Critical supply chain risk",
|
||||
"summary": "Critical supply chain risk patterns were detected in this PR.",
|
||||
"how_to_fix": (
|
||||
"Review the flagged code carefully. If it is intentional, add the "
|
||||
"`ci-reviewed` label to confirm maintainer review."
|
||||
),
|
||||
})
|
||||
|
||||
return results
|
||||
|
||||
|
||||
def build_statuses(
|
||||
ci_review: bool,
|
||||
mcp_catalog: bool,
|
||||
supply_chain: bool,
|
||||
label_present: bool,
|
||||
) -> list[dict]:
|
||||
"""Build the full review_status array (one entry with a results list)."""
|
||||
results = build_results(ci_review, mcp_catalog, label_present)
|
||||
results = build_results(ci_review, mcp_catalog, supply_chain, label_present)
|
||||
if not results:
|
||||
return []
|
||||
return [{"source": SOURCE, "results": results}]
|
||||
@@ -120,13 +133,17 @@ def main() -> int:
|
||||
help="Whether CI-sensitive files changed.")
|
||||
parser.add_argument("--mcp-catalog", action="store_true",
|
||||
help="Whether the MCP catalog / installer changed.")
|
||||
parser.add_argument("--supply-chain", action="store_true",
|
||||
help="Whether the critical supply-chain scanner found a risk.")
|
||||
parser.add_argument("--label-present", action="store_true",
|
||||
help="Whether the ci-reviewed label is present.")
|
||||
parser.add_argument("--output", default="-",
|
||||
help="Output file ('-' for stdout, or a GITHUB_OUTPUT path).")
|
||||
args = parser.parse_args()
|
||||
|
||||
statuses = build_statuses(args.ci_review, args.mcp_catalog, args.label_present)
|
||||
statuses = build_statuses(
|
||||
args.ci_review, args.mcp_catalog, args.supply_chain, args.label_present
|
||||
)
|
||||
json_str = json.dumps(statuses)
|
||||
|
||||
if args.output == "-":
|
||||
|
||||
Reference in New Issue
Block a user