This is an automated email from the ASF dual-hosted git repository.

jdaugherty pushed a commit to branch 7.0.x
in repository https://gitbox.apache.org/repos/asf/grails-core.git


The following commit(s) were added to refs/heads/7.0.x by this push:
     new 63479045c8 Run the vulnerability scan on pull requests labeled 
"vulnerability scan"
63479045c8 is described below

commit 63479045c8c3f1c7cd3ce2851949f87cab68ee09
Author: James Daugherty <[email protected]>
AuthorDate: Fri Aug 21 10:23:04 2026 -0400

    Run the vulnerability scan on pull requests labeled "vulnerability scan"
    
    Applying the label starts an OSS Index audit of that pull request and posts
    the result back as a single comment that later runs update in place.
    
    A fork pull request cannot reach the Sonatype credentials, so a
    pull_request_target job -- which checks out the base branch and never runs
    anything from the fork -- comments that scanning is unavailable instead.
    
    Extracts the report rendering and the comment upsert into shared scripts, 
and
    drops clean coordinates from the report so findings are not crowded out of a
    size-capped comment.
---
 .github/scripts/ossIndexReport.sh        |  94 +++++++++++++++++++++
 .github/scripts/postStickyComment.sh     |  60 ++++++++++++++
 .github/workflows/vulnerability-scan.yml | 135 ++++++++++++++++++++++++-------
 3 files changed, 260 insertions(+), 29 deletions(-)

diff --git a/.github/scripts/ossIndexReport.sh 
b/.github/scripts/ossIndexReport.sh
new file mode 100755
index 0000000000..90c59fa027
--- /dev/null
+++ b/.github/scripts/ossIndexReport.sh
@@ -0,0 +1,94 @@
+#!/bin/bash
+
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+
+# Renders a Markdown vulnerability report from the log of an `ossIndexAudit 
--info` run
+# and writes it to stdout. Shared by the job summary and the pull request 
comment so both
+# present the scan identically.
+#
+# Usage: ossIndexReport.sh <scan-log> <scan-outcome> <title> [max-report-bytes]
+#
+#   scan-log          path to the tee'd `ossIndexAudit` output
+#   scan-outcome      `success` when the audit found nothing, anything else 
otherwise
+#   title             heading text for the report
+#   max-report-bytes  truncate the vulnerability listing to this many bytes; 0 
(default)
+#                     leaves it untruncated. Used to stay under GitHub's 
comment size cap.
+
+set -uo pipefail
+
+SCAN_LOG="${1:?path to the ossIndexAudit log is required}"
+SCAN_OUTCOME="${2:?scan outcome is required}"
+TITLE="${3:?report title is required}"
+MAX_REPORT_BYTES="${4:-0}"
+
+echo "## ${TITLE}"
+
+if [ "$SCAN_OUTCOME" = "success" ]; then
+    echo "✅ No vulnerabilities found."
+    exit 0
+fi
+
+# The audit prints a line per resolved coordinate; hold each one back and 
print it only when a
+# vulnerability follows, so a clean dependency contributes nothing. Report each
+# coordinate and each CVE once even though a CVE may be reported against 
several modules.
+REPORT=$(awk '
+    BEGIN { in_section=0; in_vuln=0 }
+    { gsub(/\033\[[0-9;]*m/, "") }
+    /^##\[ossIndexAudit:begin\]/ { in_section=1; next }
+    /^##\[ossIndexAudit:end\]/ { in_section=0; in_vuln=0; next }
+    !in_section { next }
+    /^\[[0-9]+\/[0-9]+\] - pkg:maven\// {
+      sub(/^\[[0-9]+\/[0-9]+\] - /, "")
+      coord=$0
+      next
+    }
+    /^   Vulnerability Title:/ { in_vuln=1; block=$0 "\n"; cve_id=""; next }
+    in_vuln && /^   CVE:/ { match($0,/CVE-[0-9-]+/); if (RSTART) 
cve_id=substr($0,RSTART,RLENGTH); block=block $0 "\n"; next }
+    in_vuln && /^   Reference:/ {
+      block=block $0 "\n"
+      if (cve_id && !seen_cve[cve_id]++) {
+        if (coord != "" && !seen_coord[coord]++) { print ""; print coord }
+        printf "%s",block
+      }
+      in_vuln=0
+      next
+    }
+    in_vuln { block=block $0 "\n" }
+' "$SCAN_LOG" 2>/dev/null)
+
+TRUNCATED=''
+if [ "$MAX_REPORT_BYTES" -gt 0 ] && [ "$(printf '%s' "$REPORT" | wc -c)" -gt 
"$MAX_REPORT_BYTES" ]; then
+    REPORT=$(printf '%s' "$REPORT" | head -c "$MAX_REPORT_BYTES")
+    TRUNCATED='yes'
+fi
+
+if [ -z "$REPORT" ]; then
+    REPORT='(no scan output captured — check the full log)'
+fi
+
+echo "❌ Vulnerabilities detected."
+echo
+echo '```'
+printf '%s\n' "$REPORT"
+if [ -n "$TRUNCATED" ]; then
+    echo
+    echo '… report truncated; see the workflow run for the complete listing.'
+fi
+echo '```'
diff --git a/.github/scripts/postStickyComment.sh 
b/.github/scripts/postStickyComment.sh
new file mode 100755
index 0000000000..e9f584f46a
--- /dev/null
+++ b/.github/scripts/postStickyComment.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#    https://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing,
+#  software distributed under the License is distributed on an
+#  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+#  KIND, either express or implied.  See the License for the
+#  specific language governing permissions and limitations
+#  under the License.
+#
+
+# Creates a comment on a pull request, or updates the one a previous run left 
behind, so a
+# workflow that runs repeatedly on the same pull request keeps a single up to 
date comment
+# instead of appending a new one each time. The comment is identified by an 
HTML marker
+# written as its first line.
+#
+# Usage: postStickyComment.sh <pull-request-number> <marker> <body-file>
+#
+# Requires the `gh` CLI, a GH_TOKEN with `pull-requests: write`, and 
GITHUB_REPOSITORY.
+
+set -euo pipefail
+
+PR_NUMBER="${1:?pull request number is required}"
+MARKER="${2:?comment marker is required}"
+BODY_FILE="${3:?path to the comment body is required}"
+
+REPO="${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is not set}"
+
+FULL_BODY_FILE=$(mktemp)
+trap 'rm -f "$FULL_BODY_FILE"' EXIT
+{
+    printf '%s\n\n' "$MARKER"
+    cat "$BODY_FILE"
+} > "$FULL_BODY_FILE"
+
+# Match on the marker rather than on the comment author so a run cannot adopt 
an unrelated
+# comment the same bot left on the pull request.
+EXISTING_IDS=$(gh api --paginate "repos/${REPO}/issues/${PR_NUMBER}/comments" \
+    --jq "[.[] | select((.body // \"\") | startswith(\"${MARKER}\")) | .id] | 
.[]")
+EXISTING_ID=$(printf '%s\n' "$EXISTING_IDS" | head -n 1)
+
+if [ -n "$EXISTING_ID" ]; then
+    echo "Updating existing comment ${EXISTING_ID} on pull request 
#${PR_NUMBER}"
+    jq -n --rawfile body "$FULL_BODY_FILE" '{body: $body}' \
+        | gh api -X PATCH "repos/${REPO}/issues/comments/${EXISTING_ID}" 
--input - --silent
+else
+    echo "Creating comment on pull request #${PR_NUMBER}"
+    jq -n --rawfile body "$FULL_BODY_FILE" '{body: $body}' \
+        | gh api -X POST "repos/${REPO}/issues/${PR_NUMBER}/comments" --input 
- --silent
+fi
diff --git a/.github/workflows/vulnerability-scan.yml 
b/.github/workflows/vulnerability-scan.yml
index 6edf523095..7195dc86b8 100644
--- a/.github/workflows/vulnerability-scan.yml
+++ b/.github/workflows/vulnerability-scan.yml
@@ -19,13 +19,26 @@ on:
     # Run every Monday at 03:00 UTC
     - cron: '0 3 * * 1'
   workflow_dispatch:
-# Do not scan concurrently; OSS Index has per-account rate limits
+  # Opt in per pull request by applying the "vulnerability scan" label. 
Applying a label
+  # requires write access, so only a committer can start a scan.
+  pull_request:
+    types: [labeled, synchronize, reopened]
+  # Used only to tell a fork pull request that it cannot be scanned. A 
`pull_request` run
+  # raised from a fork gets neither the Sonatype credentials nor a token that 
can comment,
+  # so the notice has to come from `pull_request_target`. That job checks out 
the base
+  # branch and never runs anything from the pull request.
+  pull_request_target:
+    types: [labeled, synchronize, reopened]
+# Do not scan concurrently; OSS Index has per-account rate limits.
+# The event name is part of the group so the `pull_request` scan and the 
`pull_request_target`
+# notice for the same pull request cannot cancel one another.
 concurrency:
-  group: ${{ github.workflow }}-${{ github.ref }}
-  cancel-in-progress: false
+  group: ${{ github.workflow }}-${{ github.event_name }}-${{ 
github.event.pull_request.number || github.ref }}
+  cancel-in-progress: ${{ github.event_name == 'pull_request' || 
github.event_name == 'pull_request_target' }}
 jobs:
   scan-grails-core:
     name: "OSS Index Scan - grails-core"
+    if: github.event_name == 'schedule' || github.event_name == 
'workflow_dispatch'
     runs-on: ubuntu-24.04
     permissions:
       contents: read
@@ -50,30 +63,94 @@ jobs:
         run: ./gradlew ossIndexAudit --continue --info 2>&1 | tee 
/tmp/ossindex-scan.log; exit ${PIPESTATUS[0]}
       - name: "📋 Publish Vulnerability Summary"
         if: always()
+        run: >
+          .github/scripts/ossIndexReport.sh
+          /tmp/ossindex-scan.log
+          '${{ steps.scan.outcome }}'
+          '🔍 OSS Index Vulnerability Scan — grails-core'
+          >> $GITHUB_STEP_SUMMARY
+
+  scan-pull-request:
+    name: "OSS Index Scan - pull request"
+    # `synchronize` and `reopened` re-scan a pull request that already carries 
the label.
+    if: >-
+      github.event_name == 'pull_request'
+      && contains(github.event.pull_request.labels.*.name, 'vulnerability 
scan')
+      && github.event.pull_request.head.repo.full_name == github.repository
+    runs-on: ubuntu-24.04
+    permissions:
+      contents: read
+      pull-requests: write
+    steps:
+      - name: "📥 Checkout pull request"
+        uses: actions/checkout@v6
+      - name: "☕️ Setup JDK"
+        uses: actions/setup-java@v4
+        with:
+          distribution: liberica
+          java-version: 17
+      - name: "🐘 Setup Gradle"
+        uses: 
gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
+        with:
+          develocity-access-key: ${{ secrets.DEVELOCITY_ACCESS_KEY }}
+      - name: "🔍 Run OSS Index Vulnerability Scan"
+        env:
+          SONATYPE_GUIDE_USERNAME: ${{ secrets.SONATYPE_GUIDE_USERNAME }}
+          SONATYPE_GUIDE_TOKEN: ${{ secrets.SONATYPE_GUIDE_TOKEN }}
+        continue-on-error: true
+        id: scan
+        run: ./gradlew ossIndexAudit --continue --info 2>&1 | tee 
/tmp/ossindex-scan.log; exit ${PIPESTATUS[0]}
+      - name: "📋 Build Vulnerability Report"
+        if: always()
+        # 60000 bytes keeps the listing clear of GitHub's 65536 character 
comment limit.
+        run: >
+          .github/scripts/ossIndexReport.sh
+          /tmp/ossindex-scan.log
+          '${{ steps.scan.outcome }}'
+          '🔍 OSS Index Vulnerability Scan — pull request'
+          60000
+          > /tmp/ossindex-report.md
+      - name: "📝 Publish Vulnerability Summary"
+        if: always()
+        run: cat /tmp/ossindex-report.md >> $GITHUB_STEP_SUMMARY
+      - name: "💬 Comment Vulnerability Report"
+        if: always()
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: >
+          .github/scripts/postStickyComment.sh
+          '${{ github.event.pull_request.number }}'
+          '<!-- grails-vulnerability-scan -->'
+          /tmp/ossindex-report.md
+
+  notify-fork-pull-request:
+    name: "OSS Index Scan - unavailable"
+    # A fork pull request cannot reach the Sonatype credentials, so say so 
instead of
+    # leaving the label looking as though a scan ran.
+    if: >-
+      github.event_name == 'pull_request_target'
+      && contains(github.event.pull_request.labels.*.name, 'vulnerability 
scan')
+      && github.event.pull_request.head.repo.full_name != github.repository
+    runs-on: ubuntu-24.04
+    permissions:
+      pull-requests: write
+    steps:
+      # Checks out the base branch, not the pull request; nothing from the 
fork is executed.
+      - name: "📥 Checkout base branch"
+        uses: actions/checkout@v6
+      - name: "📋 Build Notice"
         run: |
-          echo "## 🔍 OSS Index Vulnerability Scan — grails-core" >> 
$GITHUB_STEP_SUMMARY
-          if [ "${{ steps.scan.outcome }}" = "success" ]; then
-            echo "✅ No vulnerabilities found." >> $GITHUB_STEP_SUMMARY
-          else
-            echo "❌ Vulnerabilities detected." >> $GITHUB_STEP_SUMMARY
-            echo "" >> $GITHUB_STEP_SUMMARY
-            echo '```' >> $GITHUB_STEP_SUMMARY
-            awk '
-              BEGIN { in_section=0; in_vuln=0 }
-              { gsub(/\033\[[0-9;]*m/, "") }
-              /^##\[ossIndexAudit:begin\]/ { in_section=1; next }
-              /^##\[ossIndexAudit:end\]/ { in_section=0; in_vuln=0; next }
-              !in_section { next }
-              /^\[[0-9]+\/[0-9]+\] - pkg:maven\// {
-                sub(/^\[[0-9]+\/[0-9]+\] - /, "")
-                if (!seen_coord[$0]++) { print ""; print }
-                next
-              }
-              /^   Vulnerability Title:/ { in_vuln=1; block=$0 "\n"; 
cve_id=""; next }
-              in_vuln && /^   CVE:/ { match($0,/CVE-[0-9-]+/); if (RSTART) 
cve_id=substr($0,RSTART,RLENGTH); block=block $0 "\n"; next }
-              in_vuln && /^   Reference:/ { block=block $0 "\n"; if (cve_id && 
!seen_cve[cve_id]++) printf "%s",block; in_vuln=0; next }
-              in_vuln { block=block $0 "\n" }
-            ' /tmp/ossindex-scan.log >> $GITHUB_STEP_SUMMARY \
-              || echo "(no scan output captured — check the full log)" >> 
$GITHUB_STEP_SUMMARY
-            echo '```' >> $GITHUB_STEP_SUMMARY
-          fi
+          {
+            echo "## 🔍 OSS Index Vulnerability Scan — pull request"
+            echo "⚠️ Vulnerability scanning is not available because this 
branch is not on \`${{ github.repository }}\`."
+            echo
+            echo "The scan needs Sonatype Guide credentials, which GitHub 
withholds from workflow runs raised by a fork. Push the branch to \`${{ 
github.repository }}\` and open a pull request from there to have it scanned."
+          } > /tmp/ossindex-report.md
+      - name: "💬 Comment Notice"
+        env:
+          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        run: >
+          .github/scripts/postStickyComment.sh
+          '${{ github.event.pull_request.number }}'
+          '<!-- grails-vulnerability-scan -->'
+          /tmp/ossindex-report.md

Reply via email to