Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/scripts/test-vulnerability-triage.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
MATCH_FILTER="$SCRIPT_DIR/match-vulnerability-issue.jq"
CLASSIFY_FILTER="$SCRIPT_DIR/classify-vulnerability-issues.jq"
WORKFLOW_FILE="$SCRIPT_DIR/../workflows/vulnerability-triage.yml"
PREFIX="[sourcebot-dev/example]"

assert_json() {
Expand All @@ -20,6 +21,22 @@ assert_json() {
fi
}

assert_workflow_contains() {
local description="$1"
local expected="$2"

if ! grep -Fq -- "$expected" "$WORKFLOW_FILE"; then
echo "FAIL: $description"
echo "Expected workflow to contain: $expected"
exit 1
fi
}

assert_workflow_contains "checks out assets from the called workflow repository" 'repository: ${{ job.workflow_repository }}'
assert_workflow_contains "pins assets to the called workflow revision" 'ref: ${{ job.workflow_sha }}'
assert_workflow_contains "uses the shared match filter" '-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq'
assert_workflow_contains "uses the shared classification filter" '-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq'

match() {
local finding_id="$1"
jq -c --arg prefix "$PREFIX" --arg findingId "$finding_id" -f "$MATCH_FILTER"
Expand Down
108 changes: 72 additions & 36 deletions .github/workflows/vulnerability-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ on:
required: false
type: boolean
default: false
skip_codeql:
description: 'Skip CodeQL only when code scanning is unavailable for the caller repository.'
required: false
type: boolean
default: false
linear_assignee_id:
description: 'Linear user UUID to assign. Leave empty to use assign_to_api_key_owner behavior.'
required: false
Expand Down Expand Up @@ -160,6 +165,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPENDABOT_PAT: ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
SKIP_CODEQL: ${{ inputs.skip_codeql || false }}
run: |
set -euo pipefail
HAS_ALERTS=false
Expand Down Expand Up @@ -188,25 +194,33 @@ jobs:
exit 1
fi

# Check CodeQL alerts (uses GITHUB_TOKEN with security-events: read)
CODEQL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=1")
if [ "$CODEQL_STATUS" = "200" ]; then
CODEQL_COUNT=$(curl -s \
# Check CodeQL alerts (uses GITHUB_TOKEN with security-events: read).
# Private repositories without GitHub Code Security return HTTP 403,
# which is indistinguishable from a real authorization failure. Those
# callers must opt out explicitly rather than weakening fail-closed
# handling for every 403 response.
if [ "$SKIP_CODEQL" = "true" ]; then
echo "CodeQL is unavailable for this repository by caller configuration. Skipping."
else
CODEQL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=1" | jq 'length')
if [ "$CODEQL_COUNT" -gt 0 ]; then
echo "Found open CodeQL alerts"
HAS_ALERTS=true
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=1")
if [ "$CODEQL_STATUS" = "200" ]; then
CODEQL_COUNT=$(curl -s \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=1" | jq 'length')
if [ "$CODEQL_COUNT" -gt 0 ]; then
echo "Found open CodeQL alerts"
HAS_ALERTS=true
fi
elif [ "$CODEQL_STATUS" = "404" ]; then
echo "CodeQL is not enabled for this repository. Skipping."
else
echo "::error::Could not fetch CodeQL alerts (HTTP $CODEQL_STATUS). Reconciliation requires a complete alert snapshot."
exit 1
fi
elif [ "$CODEQL_STATUS" = "404" ]; then
echo "CodeQL is not enabled for this repository. Skipping."
else
echo "::error::Could not fetch CodeQL alerts (HTTP $CODEQL_STATUS). Reconciliation requires a complete alert snapshot."
exit 1
fi

echo "has_alerts=$HAS_ALERTS" >> "$GITHUB_OUTPUT"
Expand All @@ -215,6 +229,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DEPENDABOT_PAT: ${{ secrets.DEPENDABOT_PAT || secrets.GITHUB_TOKEN }}
SKIP_CODEQL: ${{ inputs.skip_codeql || false }}
run: |
echo "## Dependabot & CodeQL Alert Check" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
Expand All @@ -241,29 +256,34 @@ jobs:
echo "" >> "$GITHUB_STEP_SUMMARY"

# CodeQL status
CODEQL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=100")
if [ "$CODEQL_STATUS" = "404" ]; then
if [ "$SKIP_CODEQL" = "true" ]; then
echo "### CodeQL" >> "$GITHUB_STEP_SUMMARY"
echo "Not enabled for this repository." >> "$GITHUB_STEP_SUMMARY"
elif [ "$CODEQL_STATUS" = "200" ]; then
CODEQL_RESPONSE=$(curl -s \
echo "Unavailable (explicit caller configuration)." >> "$GITHUB_STEP_SUMMARY"
else
CODEQL_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=100")
CODEQL_COUNT=$(echo "$CODEQL_RESPONSE" | jq 'length')
echo "### CodeQL — $CODEQL_COUNT open alert(s)" >> "$GITHUB_STEP_SUMMARY"
if [ "$CODEQL_COUNT" -gt 0 ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Rule ID | Severity | Tool | File | Lines | Link |" >> "$GITHUB_STEP_SUMMARY"
echo "|---------|----------|------|------|-------|------|" >> "$GITHUB_STEP_SUMMARY"
echo "$CODEQL_RESPONSE" | jq -r '.[] | "| \(.rule.id // "—") | \(.rule.security_severity_level // "—") | \(.tool.name // "—") | \(.most_recent_instance.location.path // "—") | \(.most_recent_instance.location.start_line // "—")-\(.most_recent_instance.location.end_line // "—") | [View](\(.html_url)) |"' >> "$GITHUB_STEP_SUMMARY"
if [ "$CODEQL_STATUS" = "404" ]; then
echo "### CodeQL" >> "$GITHUB_STEP_SUMMARY"
echo "Not enabled for this repository." >> "$GITHUB_STEP_SUMMARY"
elif [ "$CODEQL_STATUS" = "200" ]; then
CODEQL_RESPONSE=$(curl -s \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/code-scanning/alerts?state=open&per_page=100")
CODEQL_COUNT=$(echo "$CODEQL_RESPONSE" | jq 'length')
echo "### CodeQL — $CODEQL_COUNT open alert(s)" >> "$GITHUB_STEP_SUMMARY"
if [ "$CODEQL_COUNT" -gt 0 ]; then
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Rule ID | Severity | Tool | File | Lines | Link |" >> "$GITHUB_STEP_SUMMARY"
echo "|---------|----------|------|------|-------|------|" >> "$GITHUB_STEP_SUMMARY"
echo "$CODEQL_RESPONSE" | jq -r '.[] | "| \(.rule.id // "—") | \(.rule.security_severity_level // "—") | \(.tool.name // "—") | \(.most_recent_instance.location.path // "—") | \(.most_recent_instance.location.start_line // "—")-\(.most_recent_instance.location.end_line // "—") | [View](\(.html_url)) |"' >> "$GITHUB_STEP_SUMMARY"
fi
else
echo "### CodeQL" >> "$GITHUB_STEP_SUMMARY"
echo "Failed to check (HTTP $CODEQL_STATUS)" >> "$GITHUB_STEP_SUMMARY"
fi
else
echo "### CodeQL" >> "$GITHUB_STEP_SUMMARY"
echo "Failed to check (HTTP $CODEQL_STATUS)" >> "$GITHUB_STEP_SUMMARY"
fi

echo "" >> "$GITHUB_STEP_SUMMARY"
Expand All @@ -285,6 +305,15 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4

- name: Checkout reusable workflow assets
uses: actions/checkout@v4
with:
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
sparse-checkout: .github/scripts
path: .vulnerability-triage-workflow
persist-credentials: false

- name: Download scan results
if: needs.scan.outputs.has_vulnerabilities == 'true'
uses: actions/download-artifact@v4
Expand Down Expand Up @@ -388,8 +417,15 @@ jobs:
- name: Fetch CodeQL alerts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SKIP_CODEQL: ${{ inputs.skip_codeql || false }}
run: |
set -euo pipefail
if [ "$SKIP_CODEQL" = "true" ]; then
echo "CodeQL is unavailable for this repository by caller configuration. Writing empty results."
echo "[]" > codeql-alerts.json
exit 0
fi

ALL_ALERTS="[]"
PAGE=1

Expand Down Expand Up @@ -672,7 +708,7 @@ jobs:
SELECTED=$(echo "$RESPONSE" | jq \
--arg prefix "[$REPOSITORY]" \
--arg findingId "$CVE_ID" \
-f .github/scripts/match-vulnerability-issue.jq)
-f .vulnerability-triage-workflow/.github/scripts/match-vulnerability-issue.jq)

MERGED=$(echo "$finding" "$SELECTED" | jq -s '.[0] + .[1]')
jq --argjson item "$MERGED" '. + [$item]' /tmp/matched.json > /tmp/matched.tmp && mv /tmp/matched.tmp /tmp/matched.json
Expand Down Expand Up @@ -954,7 +990,7 @@ jobs:
jq \
--arg prefix "$PREFIX" \
--slurpfile findings findings.json \
-f .github/scripts/classify-vulnerability-issues.jq \
-f .vulnerability-triage-workflow/.github/scripts/classify-vulnerability-issues.jq \
/tmp/open-issues.json > /tmp/classified-issues.json

CURRENT_COUNT=$(jq '.cves | length' findings.json)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed
- Vulnerability triage now keeps Linear issues synchronized with current security findings.
- Fixed vulnerability triage for reusable-workflow callers and repositories without CodeQL. [#1515](https://github.com/sourcebot-dev/sourcebot/pull/1515)
Comment thread
msukkari marked this conversation as resolved.
Outdated

## [5.1.4] - 2026-07-24

Expand Down
Loading