From 7c4baf8c93a5dc6439c50a39b70a43b410943b8c Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:07:24 -0700 Subject: [PATCH 01/14] feat(workflows): add weekly-security-scanning workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create weekly-security-scanning.yml on Monday 03:00 UTC schedule - Reuse gh-code-scanning skill script; no duplication to scripts/security/ - Deduplicate backlog issues via automation marker per unique rule - Detect scan analysis failures and file labeled ci-scanning-failure issue ๐Ÿ”’ - Generated by Copilot --- .../workflows/weekly-security-scanning.yml | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 .github/workflows/weekly-security-scanning.yml diff --git a/.github/workflows/weekly-security-scanning.yml b/.github/workflows/weekly-security-scanning.yml new file mode 100644 index 000000000..892a35da3 --- /dev/null +++ b/.github/workflows/weekly-security-scanning.yml @@ -0,0 +1,108 @@ +name: Weekly Security Scanning + +on: + schedule: + - cron: '0 3 * * 1' # Mondays at 03:00 UTC + workflow_dispatch: + inputs: + owner: + description: 'Repository owner (defaults to current repo owner)' + required: false + type: string + repo: + description: 'Repository name (defaults to current repo name)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + issues: write + security-events: read + +jobs: + scan-and-backlog: + name: Scan Code Alerts and Create Backlog Issues + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + security-events: read + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ inputs.owner || github.repository_owner }} + REPO: ${{ inputs.repo || github.event.repository.name }} + steps: + - name: Checkout code + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Fetch code scanning alerts + shell: pwsh + run: | + $alerts = & .github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 ` + -Owner $env:OWNER ` + -Repo $env:REPO ` + -OutputFormat Json + $alerts | Set-Content alerts.json -Encoding utf8 + + - name: Create backlog issues for new findings + shell: bash + run: | + while IFS= read -r alert; do + RULE_ID=$(echo "$alert" | jq -r '.RuleId') + RULE_DESC=$(echo "$alert" | jq -r '.RuleDescription') + SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') + TOOL=$(echo "$alert" | jq -r '.Tool') + COUNT=$(echo "$alert" | jq -r '.Count') + PATHS=$(echo "$alert" | jq -r '.SamplePaths | join(", ")') + MARKER="automation:security-scan:${RULE_ID}" + + existing=$(gh issue list \ + --repo "${OWNER}/${REPO}" \ + --search "\"[Security] ${RULE_DESC}\" in:title" \ + --state open --json number --jq '.[0].number // empty') + + if [[ -z "$existing" ]]; then + gh issue create \ + --repo "${OWNER}/${REPO}" \ + --title "[Security] ${RULE_DESC}" \ + --label "security" \ + --body " + ## Code Scanning Alert: ${RULE_DESC} + + **Rule:** \`${RULE_ID}\` + **Severity:** ${SEVERITY} + **Tool:** ${TOOL} + **Affected files:** ${COUNT} occurrences + + ### Sample affected paths + ${PATHS} + " + fi + done < <(jq -c '.[]' alerts.json) + + - name: Check for scan analysis errors + shell: bash + run: | + errors=$(gh api "repos/${OWNER}/${REPO}/code-scanning/analyses" \ + --jq '[.[] | select(.error != "" and .error != null)] | length' 2>/dev/null || echo "0") + if [[ "$errors" -gt 0 ]]; then + gh issue create \ + --repo "${OWNER}/${REPO}" \ + --title "[Security] Code scanning analysis failure detected" \ + --label "security,ci-scanning-failure" \ + --body "## Code Scanning Analysis Failure + + The weekly security scanning workflow detected ${errors} failed analysis run(s). + + **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + **Detection Date:** $(date -u '+%Y-%m-%d') + + Review the code scanning analyses page for details. + " + fi From 1206f8f49fbde77606052351b4988925877a2f30 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Thu, 30 Apr 2026 14:24:43 -0700 Subject: [PATCH 02/14] refactor(workflows): extract gh-security-scanning reusable workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete weekly-security-scanning.yml (replaced by two new files) - Create gh-security-scanning.yml as workflow_call-only reusable - Create weekly-gh-security-scanning.yml as thin schedule orchestrator โ™ป๏ธ - Generated by Copilot --- ...-scanning.yml => gh-security-scanning.yml} | 20 +++++------ .../workflows/weekly-gh-security-scanning.yml | 36 +++++++++++++++++++ 2 files changed, 44 insertions(+), 12 deletions(-) rename .github/workflows/{weekly-security-scanning.yml => gh-security-scanning.yml} (87%) create mode 100644 .github/workflows/weekly-gh-security-scanning.yml diff --git a/.github/workflows/weekly-security-scanning.yml b/.github/workflows/gh-security-scanning.yml similarity index 87% rename from .github/workflows/weekly-security-scanning.yml rename to .github/workflows/gh-security-scanning.yml index 892a35da3..6ca9a3a0f 100644 --- a/.github/workflows/weekly-security-scanning.yml +++ b/.github/workflows/gh-security-scanning.yml @@ -1,22 +1,18 @@ -name: Weekly Security Scanning +name: GitHub Security Scanning on: - schedule: - - cron: '0 3 * * 1' # Mondays at 03:00 UTC - workflow_dispatch: + workflow_call: inputs: owner: - description: 'Repository owner (defaults to current repo owner)' + description: 'Repository owner' required: false type: string + default: '' repo: - description: 'Repository name (defaults to current repo name)' + description: 'Repository name' required: false type: string - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false + default: '' permissions: contents: read @@ -33,8 +29,8 @@ jobs: security-events: read env: GH_TOKEN: ${{ github.token }} - OWNER: ${{ inputs.owner || github.repository_owner }} - REPO: ${{ inputs.repo || github.event.repository.name }} + OWNER: ${{ inputs.owner != '' && inputs.owner || github.repository_owner }} + REPO: ${{ inputs.repo != '' && inputs.repo || github.event.repository.name }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/weekly-gh-security-scanning.yml b/.github/workflows/weekly-gh-security-scanning.yml new file mode 100644 index 000000000..24a4745db --- /dev/null +++ b/.github/workflows/weekly-gh-security-scanning.yml @@ -0,0 +1,36 @@ +name: Weekly GitHub Security Scanning + +on: + schedule: + - cron: '0 3 * * 1' # Mondays at 03:00 UTC + workflow_dispatch: + inputs: + owner: + description: 'Repository owner (defaults to current repo owner)' + required: false + type: string + repo: + description: 'Repository name (defaults to current repo name)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: false + +permissions: + contents: read + issues: write + security-events: read + +jobs: + gh-security-scanning: + name: GitHub Security Scanning + uses: ./.github/workflows/gh-security-scanning.yml + permissions: + contents: read + issues: write + security-events: read + with: + owner: ${{ inputs.owner || '' }} + repo: ${{ inputs.repo || '' }} From 5c764c2431fa66c65dca0c76d02999d3e9a634c8 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:54:31 -0700 Subject: [PATCH 03/14] feat(workflows): extract security scanning issue creation into reusable workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add create-gh-security-scanning-issues.yml as workflow_call reusable - Strip issue-creation steps from gh-security-scanning.yml; add artifact upload - Update weekly orchestrator to call both workflows via needs: ๐Ÿ”’ - Generated by Copilot --- .../create-gh-security-scanning-issues.yml | 98 +++++++++++++++++++ .github/workflows/gh-security-scanning.yml | 69 ++----------- .../workflows/weekly-gh-security-scanning.yml | 10 ++ 3 files changed, 117 insertions(+), 60 deletions(-) create mode 100644 .github/workflows/create-gh-security-scanning-issues.yml diff --git a/.github/workflows/create-gh-security-scanning-issues.yml b/.github/workflows/create-gh-security-scanning-issues.yml new file mode 100644 index 000000000..1fdb96f75 --- /dev/null +++ b/.github/workflows/create-gh-security-scanning-issues.yml @@ -0,0 +1,98 @@ +name: Create GitHub Security Scanning Issues + +on: + workflow_call: + inputs: + owner: + description: 'Repository owner (defaults to current repo owner)' + required: false + type: string + default: '' + repo: + description: 'Repository name (defaults to current repo name)' + required: false + type: string + default: '' + artifact-name: + description: 'Name of the artifact containing code scanning alerts' + required: false + type: string + default: gh-security-scanning-alerts + +permissions: + issues: write + security-events: read + +jobs: + create-gh-security-scanning-issues: + name: Create GitHub Security Scanning Issues + runs-on: ubuntu-latest + permissions: + issues: write + security-events: read + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ inputs.owner != '' && inputs.owner || github.repository_owner }} + REPO: ${{ inputs.repo != '' && inputs.repo || github.event.repository.name }} + steps: + - name: Download alerts artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.artifact-name }} + + - name: Create backlog issues for new findings + shell: bash + run: | + while IFS= read -r alert; do + RULE_ID=$(echo "$alert" | jq -r '.RuleId') + RULE_DESC=$(echo "$alert" | jq -r '.RuleDescription') + SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') + TOOL=$(echo "$alert" | jq -r '.Tool') + COUNT=$(echo "$alert" | jq -r '.Count') + PATHS=$(echo "$alert" | jq -r '.SamplePaths | join(", ")') + MARKER="automation:security-scan:${RULE_ID}" + + existing=$(gh issue list \ + --repo "${OWNER}/${REPO}" \ + --search "\"[Security] ${RULE_DESC}\" in:title" \ + --state open --json number --jq '.[0].number // empty') + + if [[ -z "$existing" ]]; then + gh issue create \ + --repo "${OWNER}/${REPO}" \ + --title "[Security] ${RULE_DESC}" \ + --label "security" \ + --body " + ## Code Scanning Alert: ${RULE_DESC} + + **Rule:** \`${RULE_ID}\` + **Severity:** ${SEVERITY} + **Tool:** ${TOOL} + **Affected files:** ${COUNT} occurrences + + ### Sample affected paths + ${PATHS} + " + fi + done < <(jq -c '.[]' alerts.json) + + - name: Check for scan analysis errors + shell: bash + run: | + errors=$(gh api "repos/${OWNER}/${REPO}/code-scanning/analyses" \ + --jq '[.[] | select(.error != "" and .error != null)] | length' 2>/dev/null || echo "0") + if [[ "$errors" -gt 0 ]]; then + gh issue create \ + --repo "${OWNER}/${REPO}" \ + --title "[Security] Code scanning analysis failure detected" \ + --label "security,ci-scanning-failure" \ + --body "## Code Scanning Analysis Failure + + The weekly security scanning workflow detected ${errors} failed analysis run(s). + + **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + **Detection Date:** $(date -u '+%Y-%m-%d') + + Review the code scanning analyses page for details. + " + fi diff --git a/.github/workflows/gh-security-scanning.yml b/.github/workflows/gh-security-scanning.yml index 6ca9a3a0f..a36f99948 100644 --- a/.github/workflows/gh-security-scanning.yml +++ b/.github/workflows/gh-security-scanning.yml @@ -16,16 +16,14 @@ on: permissions: contents: read - issues: write security-events: read jobs: - scan-and-backlog: - name: Scan Code Alerts and Create Backlog Issues + scan: + name: Fetch Code Scanning Alerts runs-on: ubuntu-latest permissions: contents: read - issues: write security-events: read env: GH_TOKEN: ${{ github.token }} @@ -46,59 +44,10 @@ jobs: -OutputFormat Json $alerts | Set-Content alerts.json -Encoding utf8 - - name: Create backlog issues for new findings - shell: bash - run: | - while IFS= read -r alert; do - RULE_ID=$(echo "$alert" | jq -r '.RuleId') - RULE_DESC=$(echo "$alert" | jq -r '.RuleDescription') - SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') - TOOL=$(echo "$alert" | jq -r '.Tool') - COUNT=$(echo "$alert" | jq -r '.Count') - PATHS=$(echo "$alert" | jq -r '.SamplePaths | join(", ")') - MARKER="automation:security-scan:${RULE_ID}" - - existing=$(gh issue list \ - --repo "${OWNER}/${REPO}" \ - --search "\"[Security] ${RULE_DESC}\" in:title" \ - --state open --json number --jq '.[0].number // empty') - - if [[ -z "$existing" ]]; then - gh issue create \ - --repo "${OWNER}/${REPO}" \ - --title "[Security] ${RULE_DESC}" \ - --label "security" \ - --body " - ## Code Scanning Alert: ${RULE_DESC} - - **Rule:** \`${RULE_ID}\` - **Severity:** ${SEVERITY} - **Tool:** ${TOOL} - **Affected files:** ${COUNT} occurrences - - ### Sample affected paths - ${PATHS} - " - fi - done < <(jq -c '.[]' alerts.json) - - - name: Check for scan analysis errors - shell: bash - run: | - errors=$(gh api "repos/${OWNER}/${REPO}/code-scanning/analyses" \ - --jq '[.[] | select(.error != "" and .error != null)] | length' 2>/dev/null || echo "0") - if [[ "$errors" -gt 0 ]]; then - gh issue create \ - --repo "${OWNER}/${REPO}" \ - --title "[Security] Code scanning analysis failure detected" \ - --label "security,ci-scanning-failure" \ - --body "## Code Scanning Analysis Failure - - The weekly security scanning workflow detected ${errors} failed analysis run(s). - - **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - **Detection Date:** $(date -u '+%Y-%m-%d') - - Review the code scanning analyses page for details. - " - fi + - name: Upload alerts artifact + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: gh-security-scanning-alerts + path: alerts.json + retention-days: 30 diff --git a/.github/workflows/weekly-gh-security-scanning.yml b/.github/workflows/weekly-gh-security-scanning.yml index 24a4745db..43d030888 100644 --- a/.github/workflows/weekly-gh-security-scanning.yml +++ b/.github/workflows/weekly-gh-security-scanning.yml @@ -29,6 +29,16 @@ jobs: uses: ./.github/workflows/gh-security-scanning.yml permissions: contents: read + security-events: read + with: + owner: ${{ inputs.owner || '' }} + repo: ${{ inputs.repo || '' }} + + create-gh-security-scanning-issues: + name: Create GitHub Security Scanning Issues + needs: [gh-security-scanning] + uses: ./.github/workflows/create-gh-security-scanning-issues.yml + permissions: issues: write security-events: read with: From 9882e45940bccc2d71ff2112277e306394c01e7b Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:58:21 -0700 Subject: [PATCH 04/14] refactor(workflows): replace owner/repo inputs with github context vars MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove owner and repo inputs from gh-security-scanning.yml - Remove owner and repo inputs from create-gh-security-scanning-issues.yml - Drop with: blocks from weekly orchestrator jobs ๐Ÿ”ง - Generated by Copilot --- .../create-gh-security-scanning-issues.yml | 14 ++------------ .github/workflows/gh-security-scanning.yml | 15 ++------------- .github/workflows/weekly-gh-security-scanning.yml | 15 --------------- 3 files changed, 4 insertions(+), 40 deletions(-) diff --git a/.github/workflows/create-gh-security-scanning-issues.yml b/.github/workflows/create-gh-security-scanning-issues.yml index 1fdb96f75..69edf0e8d 100644 --- a/.github/workflows/create-gh-security-scanning-issues.yml +++ b/.github/workflows/create-gh-security-scanning-issues.yml @@ -3,16 +3,6 @@ name: Create GitHub Security Scanning Issues on: workflow_call: inputs: - owner: - description: 'Repository owner (defaults to current repo owner)' - required: false - type: string - default: '' - repo: - description: 'Repository name (defaults to current repo name)' - required: false - type: string - default: '' artifact-name: description: 'Name of the artifact containing code scanning alerts' required: false @@ -32,8 +22,8 @@ jobs: security-events: read env: GH_TOKEN: ${{ github.token }} - OWNER: ${{ inputs.owner != '' && inputs.owner || github.repository_owner }} - REPO: ${{ inputs.repo != '' && inputs.repo || github.event.repository.name }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} steps: - name: Download alerts artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 diff --git a/.github/workflows/gh-security-scanning.yml b/.github/workflows/gh-security-scanning.yml index a36f99948..eaf60011b 100644 --- a/.github/workflows/gh-security-scanning.yml +++ b/.github/workflows/gh-security-scanning.yml @@ -2,17 +2,6 @@ name: GitHub Security Scanning on: workflow_call: - inputs: - owner: - description: 'Repository owner' - required: false - type: string - default: '' - repo: - description: 'Repository name' - required: false - type: string - default: '' permissions: contents: read @@ -27,8 +16,8 @@ jobs: security-events: read env: GH_TOKEN: ${{ github.token }} - OWNER: ${{ inputs.owner != '' && inputs.owner || github.repository_owner }} - REPO: ${{ inputs.repo != '' && inputs.repo || github.event.repository.name }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} steps: - name: Checkout code uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/weekly-gh-security-scanning.yml b/.github/workflows/weekly-gh-security-scanning.yml index 43d030888..dfd7b2d5a 100644 --- a/.github/workflows/weekly-gh-security-scanning.yml +++ b/.github/workflows/weekly-gh-security-scanning.yml @@ -4,15 +4,6 @@ on: schedule: - cron: '0 3 * * 1' # Mondays at 03:00 UTC workflow_dispatch: - inputs: - owner: - description: 'Repository owner (defaults to current repo owner)' - required: false - type: string - repo: - description: 'Repository name (defaults to current repo name)' - required: false - type: string concurrency: group: ${{ github.workflow }} @@ -30,9 +21,6 @@ jobs: permissions: contents: read security-events: read - with: - owner: ${{ inputs.owner || '' }} - repo: ${{ inputs.repo || '' }} create-gh-security-scanning-issues: name: Create GitHub Security Scanning Issues @@ -41,6 +29,3 @@ jobs: permissions: issues: write security-events: read - with: - owner: ${{ inputs.owner || '' }} - repo: ${{ inputs.repo || '' }} From 96f79d8ec579640a0c2f61ac1eb4c4691de9ee60 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 08:34:15 -0700 Subject: [PATCH 05/14] chore(scripts): remove unused symlink for Get-CodeScanningAlerts.ps1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿงน - Generated by Copilot --- scripts/security/Get-CodeScanningAlerts.ps1 | 1 - 1 file changed, 1 deletion(-) delete mode 120000 scripts/security/Get-CodeScanningAlerts.ps1 diff --git a/scripts/security/Get-CodeScanningAlerts.ps1 b/scripts/security/Get-CodeScanningAlerts.ps1 deleted file mode 120000 index 7f294ef5e..000000000 --- a/scripts/security/Get-CodeScanningAlerts.ps1 +++ /dev/null @@ -1 +0,0 @@ -../../.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 \ No newline at end of file From 2dc55203da1a2c8501c1e5148bc4da8536f110f1 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 09:18:57 -0700 Subject: [PATCH 06/14] feat(workflows): rename gh-security-scanning to gh-code-scanning and apply Q1/Q2/Q3 fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove deferred Check for scan analysis errors step (Q1) - Rename all three workflow files to gh-code-scanning naming (Q2) - Add else branch to issue loop: gh issue edit + gh issue comment (Q3) ๐Ÿ”ง - Generated by Copilot --- .../create-gh-code-scanning-issues.yml | 77 +++++++++++++++++++ .../create-gh-security-scanning-issues.yml | 21 ----- ...rity-scanning.yml => gh-code-scanning.yml} | 4 +- ...anning.yml => weekly-gh-code-scanning.yml} | 16 ++-- 4 files changed, 87 insertions(+), 31 deletions(-) create mode 100644 .github/workflows/create-gh-code-scanning-issues.yml rename .github/workflows/{gh-security-scanning.yml => gh-code-scanning.yml} (93%) rename .github/workflows/{weekly-gh-security-scanning.yml => weekly-gh-code-scanning.yml} (51%) diff --git a/.github/workflows/create-gh-code-scanning-issues.yml b/.github/workflows/create-gh-code-scanning-issues.yml new file mode 100644 index 000000000..4a28c887c --- /dev/null +++ b/.github/workflows/create-gh-code-scanning-issues.yml @@ -0,0 +1,77 @@ +name: Create GitHub Code Scanning Issues + +on: + workflow_call: + inputs: + artifact-name: + description: 'Name of the artifact containing code scanning alerts' + required: false + type: string + default: gh-code-scanning-alerts + +permissions: + issues: write + security-events: read + +jobs: + create-gh-code-scanning-issues: + name: Create GitHub Code Scanning Issues + runs-on: ubuntu-latest + permissions: + issues: write + security-events: read + env: + GH_TOKEN: ${{ github.token }} + OWNER: ${{ github.repository_owner }} + REPO: ${{ github.event.repository.name }} + steps: + - name: Download alerts artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: ${{ inputs.artifact-name }} + + - name: Create backlog issues for new findings + shell: bash + run: | + while IFS= read -r alert; do + RULE_ID=$(echo "$alert" | jq -r '.RuleId') + RULE_DESC=$(echo "$alert" | jq -r '.RuleDescription') + SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') + TOOL=$(echo "$alert" | jq -r '.Tool') + COUNT=$(echo "$alert" | jq -r '.Count') + PATHS=$(echo "$alert" | jq -r '.SamplePaths | join(", ")') + MARKER="automation:security-scan:${RULE_ID}" + + existing=$(gh issue list \ + --repo "${OWNER}/${REPO}" \ + --search "\"[Security] ${RULE_DESC}\" in:title" \ + --state open --json number --jq '.[0].number // empty') + + ISSUE_BODY=" + ## Code Scanning Alert: ${RULE_DESC} + + **Rule:** \`${RULE_ID}\` + **Severity:** ${SEVERITY} + **Tool:** ${TOOL} + **Affected files:** ${COUNT} occurrences + + ### Sample affected paths + ${PATHS} + " + + if [[ -z "$existing" ]]; then + gh issue create \ + --repo "${OWNER}/${REPO}" \ + --title "[Security] ${RULE_DESC}" \ + --label "security" \ + --body "${ISSUE_BODY}" + else + gh issue edit "${existing}" \ + --repo "${OWNER}/${REPO}" \ + --body "${ISSUE_BODY}" + UPDATE_DATE=$(date -u '+%Y-%m-%d') + gh issue comment "${existing}" \ + --repo "${OWNER}/${REPO}" \ + --body "Weekly scan update: ${COUNT} occurrence(s) as of ${UPDATE_DATE}." + fi + done < <(jq -c '.[]' alerts.json) diff --git a/.github/workflows/create-gh-security-scanning-issues.yml b/.github/workflows/create-gh-security-scanning-issues.yml index 69edf0e8d..84f128b0a 100644 --- a/.github/workflows/create-gh-security-scanning-issues.yml +++ b/.github/workflows/create-gh-security-scanning-issues.yml @@ -65,24 +65,3 @@ jobs: " fi done < <(jq -c '.[]' alerts.json) - - - name: Check for scan analysis errors - shell: bash - run: | - errors=$(gh api "repos/${OWNER}/${REPO}/code-scanning/analyses" \ - --jq '[.[] | select(.error != "" and .error != null)] | length' 2>/dev/null || echo "0") - if [[ "$errors" -gt 0 ]]; then - gh issue create \ - --repo "${OWNER}/${REPO}" \ - --title "[Security] Code scanning analysis failure detected" \ - --label "security,ci-scanning-failure" \ - --body "## Code Scanning Analysis Failure - - The weekly security scanning workflow detected ${errors} failed analysis run(s). - - **Workflow Run:** ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} - **Detection Date:** $(date -u '+%Y-%m-%d') - - Review the code scanning analyses page for details. - " - fi diff --git a/.github/workflows/gh-security-scanning.yml b/.github/workflows/gh-code-scanning.yml similarity index 93% rename from .github/workflows/gh-security-scanning.yml rename to .github/workflows/gh-code-scanning.yml index eaf60011b..a69c00012 100644 --- a/.github/workflows/gh-security-scanning.yml +++ b/.github/workflows/gh-code-scanning.yml @@ -1,4 +1,4 @@ -name: GitHub Security Scanning +name: GitHub Code Scanning on: workflow_call: @@ -37,6 +37,6 @@ jobs: if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: - name: gh-security-scanning-alerts + name: gh-code-scanning-alerts path: alerts.json retention-days: 30 diff --git a/.github/workflows/weekly-gh-security-scanning.yml b/.github/workflows/weekly-gh-code-scanning.yml similarity index 51% rename from .github/workflows/weekly-gh-security-scanning.yml rename to .github/workflows/weekly-gh-code-scanning.yml index dfd7b2d5a..79c1d158d 100644 --- a/.github/workflows/weekly-gh-security-scanning.yml +++ b/.github/workflows/weekly-gh-code-scanning.yml @@ -1,4 +1,4 @@ -name: Weekly GitHub Security Scanning +name: Weekly GitHub Code Scanning on: schedule: @@ -15,17 +15,17 @@ permissions: security-events: read jobs: - gh-security-scanning: - name: GitHub Security Scanning - uses: ./.github/workflows/gh-security-scanning.yml + gh-code-scanning: + name: GitHub Code Scanning + uses: ./.github/workflows/gh-code-scanning.yml permissions: contents: read security-events: read - create-gh-security-scanning-issues: - name: Create GitHub Security Scanning Issues - needs: [gh-security-scanning] - uses: ./.github/workflows/create-gh-security-scanning-issues.yml + create-gh-code-scanning-issues: + name: Create GitHub Code Scanning Issues + needs: [gh-code-scanning] + uses: ./.github/workflows/create-gh-code-scanning-issues.yml permissions: issues: write security-events: read From 24c53033773658ff98ee2c40fbcbe4c203b26d9f Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 09:50:32 -0700 Subject: [PATCH 07/14] test(workflows): add temporary pull_request trigger for live validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add on: pull_request targeting main with opened/synchronize/reopened/ready_for_review types - Update concurrency to cancel stale PR runs, preserve scheduled run behavior ๐Ÿงช - Generated by Copilot --- .github/workflows/weekly-gh-code-scanning.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/weekly-gh-code-scanning.yml b/.github/workflows/weekly-gh-code-scanning.yml index 79c1d158d..3df16e2ce 100644 --- a/.github/workflows/weekly-gh-code-scanning.yml +++ b/.github/workflows/weekly-gh-code-scanning.yml @@ -4,10 +4,18 @@ on: schedule: - cron: '0 3 * * 1' # Mondays at 03:00 UTC workflow_dispatch: + pull_request: + branches: + - main + types: + - opened + - synchronize + - reopened + - ready_for_review concurrency: - group: ${{ github.workflow }} - cancel-in-progress: false + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} permissions: contents: read From 8e1cce411072dccc2dea7da94b9402acbabe416d Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 10:53:33 -0700 Subject: [PATCH 08/14] fix(workflows): improve code scanning issue quality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extend PS grouper: sentinel filter, AffectedPaths, HasFilePaths, AlertUrl, FindingDescription - Rewrite bash step: bulleted linked paths, conditional repo-level section - Fix labels, title severity, dedup to body marker, grammar, enriched body ๐Ÿ”’ - Generated by Copilot --- .../scripts/Get-CodeScanningAlerts.ps1 | 21 ++++++--- .../create-gh-code-scanning-issues.yml | 46 +++++++++++++++---- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 b/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 index 575b409aa..b345b397c 100644 --- a/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 +++ b/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 @@ -85,13 +85,22 @@ if ($MyInvocation.InvocationName -ne '.') { $Grouped = $Alerts | Group-Object { $_.rule.description } | ForEach-Object { + $paths = @( + $_.Group | + ForEach-Object { $_.most_recent_instance.location.path } | + Where-Object { $_ -and $_ -notmatch '(?i)no file' } | + Sort-Object -Unique + ) [PSCustomObject]@{ - RuleDescription = $_.Name - RuleId = $_.Group[0].rule.id - Tool = $_.Group[0].tool.name - SecuritySeverity = $_.Group[0].rule.security_severity_level - Count = $_.Count - SamplePaths = @($_.Group | ForEach-Object { $_.most_recent_instance.location.path } | Sort-Object -Unique) + RuleDescription = $_.Name + RuleId = $_.Group[0].rule.id + Tool = $_.Group[0].tool.name + SecuritySeverity = $_.Group[0].rule.security_severity_level + Count = $_.Count + AffectedPaths = $paths + HasFilePaths = ($paths.Count -gt 0) + AlertUrl = $_.Group[0].html_url + FindingDescription = $_.Group[0].most_recent_instance.message.text } } | Sort-Object -Property Count -Descending diff --git a/.github/workflows/create-gh-code-scanning-issues.yml b/.github/workflows/create-gh-code-scanning-issues.yml index 4a28c887c..542bc230a 100644 --- a/.github/workflows/create-gh-code-scanning-issues.yml +++ b/.github/workflows/create-gh-code-scanning-issues.yml @@ -24,6 +24,9 @@ jobs: GH_TOKEN: ${{ github.token }} OWNER: ${{ github.repository_owner }} REPO: ${{ github.event.repository.name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_REPOSITORY: ${{ github.repository }} + GITHUB_RUN_ID: ${{ github.run_id }} steps: - name: Download alerts artifact uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 @@ -39,12 +42,26 @@ jobs: SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') TOOL=$(echo "$alert" | jq -r '.Tool') COUNT=$(echo "$alert" | jq -r '.Count') - PATHS=$(echo "$alert" | jq -r '.SamplePaths | join(", ")') + HAS_FILE_PATHS=$(echo "$alert" | jq -r '.HasFilePaths') + ALERT_URL=$(echo "$alert" | jq -r '.AlertUrl // ""') + FINDING_DESC=$(echo "$alert" | jq -r '.FindingDescription // ""') MARKER="automation:security-scan:${RULE_ID}" + OCCURRENCE_WORD="$([ "$COUNT" = "1" ] && echo "occurrence" || echo "occurrences")" + DETECTION_DATE=$(date -u '+%Y-%m-%d') + RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" + REPO_BASE="https://github.com/${OWNER}/${REPO}/blob/main" + + if [[ "$HAS_FILE_PATHS" == "true" ]]; then + AFFECTED_LIST=$(echo "$alert" | jq -r --arg base "$REPO_BASE" '.AffectedPaths[] | "- [\(.)](\($base + "/" + .))"') + PATHS_SECTION=$'### Affected paths\n\n'"${AFFECTED_LIST}" + else + PATHS_SECTION=$'### Repository configuration finding\n\nThis alert refers to a repository-level configuration setting with no associated source file.' + [[ -n "$FINDING_DESC" ]] && PATHS_SECTION+=$'\n'"**Finding:** ${FINDING_DESC}" + fi existing=$(gh issue list \ --repo "${OWNER}/${REPO}" \ - --search "\"[Security] ${RULE_DESC}\" in:title" \ + --search "\"${MARKER}\" in:body" \ --state open --json number --jq '.[0].number // empty') ISSUE_BODY=" @@ -53,25 +70,36 @@ jobs: **Rule:** \`${RULE_ID}\` **Severity:** ${SEVERITY} **Tool:** ${TOOL} - **Affected files:** ${COUNT} occurrences + **Occurrences:** ${COUNT} ${OCCURRENCE_WORD} + $([ -n "${ALERT_URL}" ] && echo "**Alert:** ${ALERT_URL}") + + ### What was found + $([ -n "${FINDING_DESC}" ] && echo "${FINDING_DESC}" || echo "See the linked alert for details.") + + ${PATHS_SECTION} + + --- + **Detection Date:** ${DETECTION_DATE} + **Workflow Run:** ${RUN_URL} - ### Sample affected paths - ${PATHS} + ### Action Required + - [ ] Review the alert and confirm it is not a false positive + - [ ] Remediate or dismiss the finding with a documented reason + - [ ] Close this issue after the fix is merged " if [[ -z "$existing" ]]; then gh issue create \ --repo "${OWNER}/${REPO}" \ - --title "[Security] ${RULE_DESC}" \ - --label "security" \ + --title "[Security][${SEVERITY}] ${RULE_DESC}" \ + --label "security,automated,needs-triage" \ --body "${ISSUE_BODY}" else gh issue edit "${existing}" \ --repo "${OWNER}/${REPO}" \ --body "${ISSUE_BODY}" - UPDATE_DATE=$(date -u '+%Y-%m-%d') gh issue comment "${existing}" \ --repo "${OWNER}/${REPO}" \ - --body "Weekly scan update: ${COUNT} occurrence(s) as of ${UPDATE_DATE}." + --body "Weekly scan update: ${COUNT} ${OCCURRENCE_WORD} as of ${DETECTION_DATE}." fi done < <(jq -c '.[]' alerts.json) From 01413f1cb934c754c949d060f2df0c2949237a8b Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:09:15 -0700 Subject: [PATCH 09/14] docs(skills): update gh-code-scanning SKILL.md output shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename SamplePaths to AffectedPaths in JSON example and prose - add HasFilePaths, AlertUrl, FindingDescription to example and field list - fix BranchProtectionID example to show empty AffectedPaths array - add GroupedJson alias and Key fields API path disambiguation note ๐Ÿ“ - Generated by Copilot --- .../skills/github/gh-code-scanning/SKILL.md | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/skills/github/gh-code-scanning/SKILL.md b/.github/skills/github/gh-code-scanning/SKILL.md index 0da504884..78f122b33 100644 --- a/.github/skills/github/gh-code-scanning/SKILL.md +++ b/.github/skills/github/gh-code-scanning/SKILL.md @@ -45,7 +45,7 @@ This returns a JSON array of alert groups sorted by occurrence count, descending |-----------------|--------|----------|---------|---------------------------------------------------------------------------| | `-Owner` | String | Yes | | GitHub organization or user that owns the repository | | `-Repo` | String | Yes | | Repository name | -| `-OutputFormat` | String | No | Table | Output format: agents must always use `Json` for programmatic consumption | +| `-OutputFormat` | String | No | Table | Output format: agents must always use `Json` for programmatic consumption; `GroupedJson` is accepted as an alias for `Json` | | `-Branch` | String | No | `main` | Branch to scope alert results | > These parameters apply to `Get-CodeScanningAlerts.ps1`. For bash script flags including `-s {severity}`, see the Script Reference section below. @@ -109,10 +109,13 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "Tool": "CodeQL", "SecuritySeverity": null, "Count": 23, - "SamplePaths": [ + "AffectedPaths": [ "scripts/collections/Get-CollectionItems.py", "scripts/linting/Validate-MarkdownFrontmatter.py" - ] + ], + "HasFilePaths": true, + "AlertUrl": "https://github.com/microsoft/hve-core/security/code-scanning/42", + "FindingDescription": "'except' clause does nothing but pass and there is no explanatory comment." }, { "RuleDescription": "Code injection", @@ -120,9 +123,12 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "Tool": "CodeQL", "SecuritySeverity": "medium", "Count": 2, - "SamplePaths": [ + "AffectedPaths": [ ".github/workflows/validate.yml" - ] + ], + "HasFilePaths": true, + "AlertUrl": "https://github.com/microsoft/hve-core/security/code-scanning/17", + "FindingDescription": "Potential code injection in ${{ inputs.version }}, which may be controlled by an external user." }, { "RuleDescription": "Branch-Protection", @@ -130,14 +136,15 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "Tool": "Scorecard", "SecuritySeverity": "high", "Count": 1, - "SamplePaths": [ - "no file associated with this alert" - ] + "AffectedPaths": [], + "HasFilePaths": false, + "AlertUrl": "https://github.com/microsoft/hve-core/security/code-scanning/1", + "FindingDescription": "score is 9: branch protection is not maximal on development and all release branches" } ] ``` -`SecuritySeverity` is `null` when the rule has no severity tier assigned. `SamplePaths` is always a JSON array. When an alert has no associated source file (for example, `BranchProtectionID`), the array contains the sentinel string `"no file associated with this alert"`. +`SecuritySeverity` is `null` when the rule has no severity tier assigned. `AffectedPaths` is always a JSON array of unique, sorted file paths with sentinel strings filtered out. `HasFilePaths` is `false` and `AffectedPaths` is `[]` when an alert has no associated source file (for example, `BranchProtectionID`). `AlertUrl` links directly to the alert in the GitHub Security tab. `FindingDescription` is the most recent alert message text. ### Get single alert detail @@ -149,10 +156,12 @@ gh api repos/{owner}/{repo}/code-scanning/alerts/{alert_number} ### List affected file paths -Use `-OutputFormat Json` and read the `SamplePaths` field from each rule group. The JSON output includes `RuleDescription`, `RuleId`, `Tool`, `SecuritySeverity`, `Count`, and `SamplePaths` (unique, sorted file paths) per group. +Use `-OutputFormat Json` and read the `AffectedPaths` field from each rule group. The JSON output includes `RuleDescription`, `RuleId`, `Tool`, `SecuritySeverity`, `Count`, `AffectedPaths` (unique, sorted file paths), `HasFilePaths` (boolean: `false` for repo-level rules that have no associated source file), `AlertUrl` (string: direct link to the alert in the GitHub Security tab), and `FindingDescription` (string: most recent alert message text from the analysis tool) per group. ### Key fields +These are GitHub API response field paths, not output object properties. The grouped output object field names are listed in the JSON output shape section above. + * `rule.security_severity_level`: severity tier: `critical`, `high`, `medium`, or `low` * `rule.id`: rule identifier used for deduplication and cross-referencing * `tool.name`: analysis tool that produced the alert (for example, `CodeQL`) @@ -203,8 +212,8 @@ if [[ -z "$existing" ]]; then **Tool:** {tool} **Affected files:** {count} occurrences -### Sample affected paths -{sample_paths} +### Affected paths +{affected_paths} " fi ``` From bc030f7c656ed289fb26b6a693a01f5a2424fb60 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:13:00 -0700 Subject: [PATCH 10/14] Removing ms.date from gh-code-scanning skill --- .github/skills/github/gh-code-scanning/SKILL.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/skills/github/gh-code-scanning/SKILL.md b/.github/skills/github/gh-code-scanning/SKILL.md index 78f122b33..d4f8fc070 100644 --- a/.github/skills/github/gh-code-scanning/SKILL.md +++ b/.github/skills/github/gh-code-scanning/SKILL.md @@ -3,7 +3,6 @@ name: gh-code-scanning description: 'Retrieves and groups GitHub code scanning alerts by rule and severity using the gh CLI - Brought to you by microsoft/hve-core' license: MIT compatibility: 'Requires pwsh 7+ and gh CLI authenticated with the security_events scope. Bash script requires jq.' -ms.date: 2026-04-21 metadata: authors: "microsoft/hve-core" spec_version: "1.0" From 30d556ca60514c20e1a591ece6fd0e2561140cce Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:21:33 -0700 Subject: [PATCH 11/14] fix(workflows): fix gh issue edit missing title, labels, and duplicate finding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add --title and --add-label flags to gh issue edit on existing issues - remove duplicate FindingDescription from repo-level PATHS_SECTION ๐Ÿ”’ - Generated by Copilot --- .github/workflows/create-gh-code-scanning-issues.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/create-gh-code-scanning-issues.yml b/.github/workflows/create-gh-code-scanning-issues.yml index 542bc230a..117d1c1fc 100644 --- a/.github/workflows/create-gh-code-scanning-issues.yml +++ b/.github/workflows/create-gh-code-scanning-issues.yml @@ -56,7 +56,6 @@ jobs: PATHS_SECTION=$'### Affected paths\n\n'"${AFFECTED_LIST}" else PATHS_SECTION=$'### Repository configuration finding\n\nThis alert refers to a repository-level configuration setting with no associated source file.' - [[ -n "$FINDING_DESC" ]] && PATHS_SECTION+=$'\n'"**Finding:** ${FINDING_DESC}" fi existing=$(gh issue list \ @@ -97,7 +96,11 @@ jobs: else gh issue edit "${existing}" \ --repo "${OWNER}/${REPO}" \ + --title "[Security][${SEVERITY}] ${RULE_DESC}" \ --body "${ISSUE_BODY}" + gh issue edit "${existing}" \ + --repo "${OWNER}/${REPO}" \ + --add-label "automated,needs-triage" gh issue comment "${existing}" \ --repo "${OWNER}/${REPO}" \ --body "Weekly scan update: ${COUNT} ${OCCURRENCE_WORD} as of ${DETECTION_DATE}." From 9d1e51e6da671b7626eed89cf1d74e6e4907b2a2 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:29:54 -0700 Subject: [PATCH 12/14] fix(workflows): omit severity bracket when unknown, add Severity fallback field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add Severity (rule.severity) field to PS script grouped output - fall back to Severity when SecuritySeverity is null - omit [SEVERITY] title bracket and severity body line when both are null ๐Ÿ”ง - Generated by Copilot --- .../skills/github/gh-code-scanning/SKILL.md | 22 +++++++++++-------- .../scripts/Get-CodeScanningAlerts.ps1 | 1 + .../create-gh-code-scanning-issues.yml | 13 +++++++---- 3 files changed, 23 insertions(+), 13 deletions(-) diff --git a/.github/skills/github/gh-code-scanning/SKILL.md b/.github/skills/github/gh-code-scanning/SKILL.md index d4f8fc070..8d1cba0bc 100644 --- a/.github/skills/github/gh-code-scanning/SKILL.md +++ b/.github/skills/github/gh-code-scanning/SKILL.md @@ -40,12 +40,12 @@ This returns a JSON array of alert groups sorted by occurrence count, descending ## Parameters Reference -| Parameter | Type | Required | Default | Description | -|-----------------|--------|----------|---------|---------------------------------------------------------------------------| -| `-Owner` | String | Yes | | GitHub organization or user that owns the repository | -| `-Repo` | String | Yes | | Repository name | +| Parameter | Type | Required | Default | Description | +|-----------------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------| +| `-Owner` | String | Yes | | GitHub organization or user that owns the repository | +| `-Repo` | String | Yes | | Repository name | | `-OutputFormat` | String | No | Table | Output format: agents must always use `Json` for programmatic consumption; `GroupedJson` is accepted as an alias for `Json` | -| `-Branch` | String | No | `main` | Branch to scope alert results | +| `-Branch` | String | No | `main` | Branch to scope alert results | > These parameters apply to `Get-CodeScanningAlerts.ps1`. For bash script flags including `-s {severity}`, see the Script Reference section below. @@ -107,6 +107,7 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "RuleId": "py/empty-except", "Tool": "CodeQL", "SecuritySeverity": null, + "Severity": "warning", "Count": 23, "AffectedPaths": [ "scripts/collections/Get-CollectionItems.py", @@ -121,6 +122,7 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "RuleId": "actions/code-injection/medium", "Tool": "CodeQL", "SecuritySeverity": "medium", + "Severity": "error", "Count": 2, "AffectedPaths": [ ".github/workflows/validate.yml" @@ -134,6 +136,7 @@ Use `-Branch {branch}` to scope to a branch other than `main`. "RuleId": "BranchProtectionID", "Tool": "Scorecard", "SecuritySeverity": "high", + "Severity": "error", "Count": 1, "AffectedPaths": [], "HasFilePaths": false, @@ -143,7 +146,7 @@ Use `-Branch {branch}` to scope to a branch other than `main`. ] ``` -`SecuritySeverity` is `null` when the rule has no severity tier assigned. `AffectedPaths` is always a JSON array of unique, sorted file paths with sentinel strings filtered out. `HasFilePaths` is `false` and `AffectedPaths` is `[]` when an alert has no associated source file (for example, `BranchProtectionID`). `AlertUrl` links directly to the alert in the GitHub Security tab. `FindingDescription` is the most recent alert message text. +`SecuritySeverity` is `null` for code quality rules that have no security classification; `Severity` (the non-security rule severity: `error`, `warning`, `note`, `none`) provides a fallback. `AffectedPaths` is always a JSON array of unique, sorted file paths with sentinel strings filtered out. `HasFilePaths` is `false` and `AffectedPaths` is `[]` when an alert has no associated source file (for example, `BranchProtectionID`). `AlertUrl` links directly to the alert in the GitHub Security tab. `FindingDescription` is the most recent alert message text. ### Get single alert detail @@ -155,13 +158,14 @@ gh api repos/{owner}/{repo}/code-scanning/alerts/{alert_number} ### List affected file paths -Use `-OutputFormat Json` and read the `AffectedPaths` field from each rule group. The JSON output includes `RuleDescription`, `RuleId`, `Tool`, `SecuritySeverity`, `Count`, `AffectedPaths` (unique, sorted file paths), `HasFilePaths` (boolean: `false` for repo-level rules that have no associated source file), `AlertUrl` (string: direct link to the alert in the GitHub Security tab), and `FindingDescription` (string: most recent alert message text from the analysis tool) per group. +Use `-OutputFormat Json` and read the `AffectedPaths` field from each rule group. The JSON output includes `RuleDescription`, `RuleId`, `Tool`, `SecuritySeverity`, `Severity`, `Count`, `AffectedPaths` (unique, sorted file paths), `HasFilePaths` (boolean: `false` for repo-level rules that have no associated source file), `AlertUrl` (string: direct link to the alert in the GitHub Security tab), and `FindingDescription` (string: most recent alert message text from the analysis tool) per group. ### Key fields These are GitHub API response field paths, not output object properties. The grouped output object field names are listed in the JSON output shape section above. -* `rule.security_severity_level`: severity tier: `critical`, `high`, `medium`, or `low` +* `rule.security_severity_level`: security severity tier: `critical`, `high`, `medium`, or `low`; `null` for code quality rules +* `rule.severity`: non-security rule severity: `error`, `warning`, `note`, or `none`; always populated * `rule.id`: rule identifier used for deduplication and cross-referencing * `tool.name`: analysis tool that produced the alert (for example, `CodeQL`) * `most_recent_instance.location.path`: source file path of the most recent alert occurrence @@ -207,7 +211,7 @@ if [[ -z "$existing" ]]; then ## Code Scanning Alert: {rule_description} **Rule:** \`{rule_id}\` -**Severity:** {security_severity} +$([ -n "{severity}" ] && echo "**Severity:** {severity}") **Tool:** {tool} **Affected files:** {count} occurrences diff --git a/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 b/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 index b345b397c..960c0bf33 100644 --- a/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 +++ b/.github/skills/github/gh-code-scanning/scripts/Get-CodeScanningAlerts.ps1 @@ -96,6 +96,7 @@ if ($MyInvocation.InvocationName -ne '.') { RuleId = $_.Group[0].rule.id Tool = $_.Group[0].tool.name SecuritySeverity = $_.Group[0].rule.security_severity_level + Severity = $_.Group[0].rule.severity Count = $_.Count AffectedPaths = $paths HasFilePaths = ($paths.Count -gt 0) diff --git a/.github/workflows/create-gh-code-scanning-issues.yml b/.github/workflows/create-gh-code-scanning-issues.yml index 117d1c1fc..bb7c583f3 100644 --- a/.github/workflows/create-gh-code-scanning-issues.yml +++ b/.github/workflows/create-gh-code-scanning-issues.yml @@ -39,13 +39,18 @@ jobs: while IFS= read -r alert; do RULE_ID=$(echo "$alert" | jq -r '.RuleId') RULE_DESC=$(echo "$alert" | jq -r '.RuleDescription') - SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // "unspecified"') + SEVERITY=$(echo "$alert" | jq -r '.SecuritySeverity // .Severity // empty') TOOL=$(echo "$alert" | jq -r '.Tool') COUNT=$(echo "$alert" | jq -r '.Count') HAS_FILE_PATHS=$(echo "$alert" | jq -r '.HasFilePaths') ALERT_URL=$(echo "$alert" | jq -r '.AlertUrl // ""') FINDING_DESC=$(echo "$alert" | jq -r '.FindingDescription // ""') MARKER="automation:security-scan:${RULE_ID}" + if [[ -n "$SEVERITY" ]]; then + ISSUE_TITLE="[Security][${SEVERITY}] ${RULE_DESC}" + else + ISSUE_TITLE="[Security] ${RULE_DESC}" + fi OCCURRENCE_WORD="$([ "$COUNT" = "1" ] && echo "occurrence" || echo "occurrences")" DETECTION_DATE=$(date -u '+%Y-%m-%d') RUN_URL="${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}" @@ -67,7 +72,7 @@ jobs: ## Code Scanning Alert: ${RULE_DESC} **Rule:** \`${RULE_ID}\` - **Severity:** ${SEVERITY} + $([ -n "${SEVERITY}" ] && echo "**Severity:** ${SEVERITY}") **Tool:** ${TOOL} **Occurrences:** ${COUNT} ${OCCURRENCE_WORD} $([ -n "${ALERT_URL}" ] && echo "**Alert:** ${ALERT_URL}") @@ -90,13 +95,13 @@ jobs: if [[ -z "$existing" ]]; then gh issue create \ --repo "${OWNER}/${REPO}" \ - --title "[Security][${SEVERITY}] ${RULE_DESC}" \ + --title "${ISSUE_TITLE}" \ --label "security,automated,needs-triage" \ --body "${ISSUE_BODY}" else gh issue edit "${existing}" \ --repo "${OWNER}/${REPO}" \ - --title "[Security][${SEVERITY}] ${RULE_DESC}" \ + --title "${ISSUE_TITLE}" \ --body "${ISSUE_BODY}" gh issue edit "${existing}" \ --repo "${OWNER}/${REPO}" \ From cf56d7e2d1a21a71feab0b25203ef9d8eb59c935 Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:48:30 -0700 Subject: [PATCH 13/14] test(skills): update gh-code-scanning tests for AffectedPaths rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rename SamplePaths references to AffectedPaths in three tests - update no-file test to assert empty array and HasFilePaths false ๐Ÿงช - Generated by Copilot --- .../tests/Get-CodeScanningAlerts.Tests.ps1 | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/skills/github/gh-code-scanning/tests/Get-CodeScanningAlerts.Tests.ps1 b/.github/skills/github/gh-code-scanning/tests/Get-CodeScanningAlerts.Tests.ps1 index bb78c2ce7..6dae74664 100644 --- a/.github/skills/github/gh-code-scanning/tests/Get-CodeScanningAlerts.Tests.ps1 +++ b/.github/skills/github/gh-code-scanning/tests/Get-CodeScanningAlerts.Tests.ps1 @@ -84,17 +84,17 @@ Describe 'Get-CodeScanningAlerts' -Tag 'Unit' { $parsed.Count | Should -BeGreaterThan 0 } - It 'Serializes SamplePaths as a JSON array even when only one path exists' { + It 'Serializes AffectedPaths as a JSON array even when only one path exists' { # js/xss has a single occurrence; verify the raw JSON uses bracket notation, # not a bare string (ConvertFrom-Json re-unwraps single-element arrays so # the raw string is the authoritative check) $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json $rawJson = $result | Out-String - $rawJson | Should -Match '"SamplePaths":\s*\[' + $rawJson | Should -Match '"AffectedPaths":\s*\[' } - It 'Serializes SamplePaths as a JSON array when alert has no associated file path' { + It 'Serializes AffectedPaths as empty array and sets HasFilePaths false when alert has no associated file path' { $noPathJson = '[{"number":10,"rule":{"id":"BranchProtectionID","description":"Branch-Protection","security_severity_level":"high"},"tool":{"name":"Scorecard"},"most_recent_instance":{"location":{"path":"no file associated with this alert"}}}]' ${Function:gh} = { $global:LASTEXITCODE = 0 @@ -102,13 +102,13 @@ Describe 'Get-CodeScanningAlerts' -Tag 'Unit' { }.GetNewClosure() $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json - $rawJson = $result | Out-String + $parsed = $result | ConvertFrom-Json - $rawJson | Should -Match '"SamplePaths":\s*\[' - $rawJson | Should -Match 'no file associated with this alert' + $parsed[0].AffectedPaths | Should -HaveCount 0 + $parsed[0].HasFilePaths | Should -BeFalse } - It 'Deduplicates and sorts SamplePaths across multiple occurrences of the same rule' { + It 'Deduplicates and sorts AffectedPaths across multiple occurrences of the same rule' { $multiPathJson = '[{"number":1,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/b.py"}}},{"number":2,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/a.py"}}},{"number":3,"rule":{"id":"py/empty-except","description":"Empty except","security_severity_level":null},"tool":{"name":"CodeQL"},"most_recent_instance":{"location":{"path":"scripts/a.py"}}}]' ${Function:gh} = { $global:LASTEXITCODE = 0 @@ -118,9 +118,9 @@ Describe 'Get-CodeScanningAlerts' -Tag 'Unit' { $result = & $script:ScriptPath -Owner 'testorg' -Repo 'testrepo' -OutputFormat Json $parsed = $result | ConvertFrom-Json - $parsed[0].SamplePaths | Should -HaveCount 2 - $parsed[0].SamplePaths[0] | Should -Be 'scripts/a.py' - $parsed[0].SamplePaths[1] | Should -Be 'scripts/b.py' + $parsed[0].AffectedPaths | Should -HaveCount 2 + $parsed[0].AffectedPaths[0] | Should -Be 'scripts/a.py' + $parsed[0].AffectedPaths[1] | Should -Be 'scripts/b.py' } } From 902d65f555b6ba4bbc795d4e70caf10eb9b791ef Mon Sep 17 00:00:00 2001 From: rezatnoMsirhC <17170709+rezatnoMsirhC@users.noreply.github.com> Date: Fri, 1 May 2026 11:49:36 -0700 Subject: [PATCH 14/14] revert(workflows): remove temporary pull_request trigger from weekly scan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ๐Ÿ”ง - Generated by Copilot --- .github/workflows/weekly-gh-code-scanning.yml | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/.github/workflows/weekly-gh-code-scanning.yml b/.github/workflows/weekly-gh-code-scanning.yml index 3df16e2ce..79c1d158d 100644 --- a/.github/workflows/weekly-gh-code-scanning.yml +++ b/.github/workflows/weekly-gh-code-scanning.yml @@ -4,18 +4,10 @@ on: schedule: - cron: '0 3 * * 1' # Mondays at 03:00 UTC workflow_dispatch: - pull_request: - branches: - - main - types: - - opened - - synchronize - - reopened - - ready_for_review concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: ${{ github.event_name == 'pull_request' }} + group: ${{ github.workflow }} + cancel-in-progress: false permissions: contents: read