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

jongyoul pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/zeppelin.git


The following commit(s) were added to refs/heads/master by this push:
     new 9f7092b4a8 [MINOR] Add daily npm audit remediation workflow
9f7092b4a8 is described below

commit 9f7092b4a82e0fb42641cb9fc55eda463ee6dff3
Author: Jongyoul Lee <[email protected]>
AuthorDate: Fri Aug 14 14:55:29 2026 +0900

    [MINOR] Add daily npm audit remediation workflow
    
    ### What is this PR for?
    
    Add a daily and manually triggered workflow that detects high/critical `npm 
audit` failures in `zeppelin-react`.
    
    When a lockfile-only fix is available, the workflow:
    
    - changes only `zeppelin-react/package-lock.json`;
    - validates audit, lint, tests, and production build;
    - creates or updates one Draft PR from a fixed automation branch;
    - skips creating a duplicate when the lockfile content is unchanged.
    
    It never uses `npm audit fix --force`, pushes directly to `master`, or 
merges automatically.
    
    ### What type of PR is it?
    
    Improvement
    
    ### What is the Jira issue?
    
    N/A
    
    ### How should this be tested?
    
    - The workflow's `prepare` job passed on this PR.
    - The end-to-end remediation path passed audit, lint, 14 tests, and 
production build locally.
    
    
    Closes #5390 from jongyoul/codex/daily-npm-audit-fix-pr.
    
    Signed-off-by: Jongyoul Lee <[email protected]>
---
 .github/workflows/npm-audit-remediation.yml | 291 ++++++++++++++++++++++++++++
 1 file changed, 291 insertions(+)

diff --git a/.github/workflows/npm-audit-remediation.yml 
b/.github/workflows/npm-audit-remediation.yml
new file mode 100644
index 0000000000..841bf3aab2
--- /dev/null
+++ b/.github/workflows/npm-audit-remediation.yml
@@ -0,0 +1,291 @@
+# 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
+#
+#     http://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.
+
+name: npm audit remediation
+
+on:
+  schedule:
+    - cron: '23 2 * * *'
+  workflow_dispatch:
+  pull_request:
+    paths:
+      - '.github/workflows/npm-audit-remediation.yml'
+
+concurrency:
+  group: npm-audit-remediation
+  cancel-in-progress: false
+
+env:
+  FRONTEND_DIRECTORY: zeppelin-web-angular
+  PACKAGE_DIRECTORY: zeppelin-web-angular/projects/zeppelin-react
+  LOCKFILE: zeppelin-web-angular/projects/zeppelin-react/package-lock.json
+  REMEDIATION_BRANCH: automation/npm-audit-fix-zeppelin-react
+  PR_TITLE: '[HOTFIX] Refresh zeppelin-react lockfile for npm audit'
+
+jobs:
+  prepare:
+    runs-on: ubuntu-24.04
+    timeout-minutes: 30
+    permissions:
+      contents: read
+    outputs:
+      audited_sha: ${{ steps.revision.outputs.sha }}
+      needs_remediation: ${{ steps.audit.outputs.needs_remediation }}
+    steps:
+      - name: Checkout
+        uses: actions/checkout@v5
+        with:
+          fetch-depth: 0
+          persist-credentials: false
+          ref: ${{ github.event_name == 'pull_request' && github.sha || 
'master' }}
+
+      - id: revision
+        name: Record audited revision
+        shell: bash
+        run: echo "sha=$(git rev-parse HEAD)" >> "${GITHUB_OUTPUT}"
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v5
+        with:
+          node-version-file: 'zeppelin-web-angular/.nvmrc'
+
+      - id: audit
+        name: Check npm audit
+        working-directory: ${{ env.PACKAGE_DIRECTORY }}
+        shell: bash
+        run: |
+          set +e
+          npm audit --package-lock-only --audit-level=high --json \
+            > "${RUNNER_TEMP}/npm-audit-before.json"
+          audit_status=$?
+          set -e
+
+          if [[ ${audit_status} -eq 0 ]]; then
+            echo "needs_remediation=false" >> "${GITHUB_OUTPUT}"
+            exit 0
+          fi
+
+          vulnerable_count=$(node - "${RUNNER_TEMP}/npm-audit-before.json" 
<<'NODE'
+          const fs = require('fs');
+          const report = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
+          const counts = report.metadata && report.metadata.vulnerabilities;
+          if (!counts) {
+            process.exit(2);
+          }
+          console.log((counts.high || 0) + (counts.critical || 0));
+          NODE
+          ) || {
+            cat "${RUNNER_TEMP}/npm-audit-before.json"
+            echo "::error::npm audit failed without a valid vulnerability 
report"
+            exit "${audit_status}"
+          }
+
+          if [[ ${vulnerable_count} -eq 0 ]]; then
+            cat "${RUNNER_TEMP}/npm-audit-before.json"
+            echo "::error::npm audit failed without a high or critical 
vulnerability"
+            exit "${audit_status}"
+          fi
+
+          echo "needs_remediation=true" >> "${GITHUB_OUTPUT}"
+          echo "Found ${vulnerable_count} high or critical vulnerabilities"
+
+      - name: Generate a lockfile-only fix
+        if: steps.audit.outputs.needs_remediation == 'true'
+        working-directory: ${{ env.PACKAGE_DIRECTORY }}
+        run: npm audit fix --package-lock-only --ignore-scripts 
--audit-level=high
+
+      - name: Verify the generated diff
+        if: steps.audit.outputs.needs_remediation == 'true'
+        shell: bash
+        run: |
+          changed_files=$(git diff --name-only)
+          if [[ "${changed_files}" != "${LOCKFILE}" ]]; then
+            echo "::error::Expected only ${LOCKFILE} to change, got:"
+            printf '%s\n' "${changed_files}"
+            exit 1
+          fi
+          git diff --check
+
+      - name: Install frontend dependencies
+        if: steps.audit.outputs.needs_remediation == 'true'
+        working-directory: ${{ env.FRONTEND_DIRECTORY }}
+        run: npm ci --ignore-scripts --no-audit
+
+      - name: Validate the fix
+        if: steps.audit.outputs.needs_remediation == 'true'
+        working-directory: ${{ env.PACKAGE_DIRECTORY }}
+        run: |
+          npm ci --ignore-scripts --no-audit
+          npm audit --audit-level=high
+          npm run lint
+          npm test
+          npm run build
+
+      - name: Create remediation artifact
+        if: steps.audit.outputs.needs_remediation == 'true'
+        shell: bash
+        run: |
+          artifact_directory="${RUNNER_TEMP}/npm-audit-remediation"
+          mkdir -p "${artifact_directory}"
+          git diff --binary -- "${LOCKFILE}" > 
"${artifact_directory}/fix.patch"
+          cp "${RUNNER_TEMP}/npm-audit-before.json" 
"${artifact_directory}/audit-before.json"
+          test -s "${artifact_directory}/fix.patch"
+
+      - name: Upload remediation artifact
+        if: steps.audit.outputs.needs_remediation == 'true'
+        uses: actions/upload-artifact@v6
+        with:
+          name: npm-audit-remediation
+          path: ${{ runner.temp }}/npm-audit-remediation
+          retention-days: 1
+
+  publish:
+    needs: prepare
+    if: >-
+      needs.prepare.outputs.needs_remediation == 'true' &&
+      github.event_name != 'pull_request' &&
+      github.repository == 'apache/zeppelin'
+    runs-on: ubuntu-24.04
+    timeout-minutes: 10
+    permissions:
+      actions: read
+      contents: write
+      pull-requests: write
+    steps:
+      - name: Checkout audited revision
+        uses: actions/checkout@v5
+        with:
+          fetch-depth: 0
+          ref: ${{ needs.prepare.outputs.audited_sha }}
+
+      - name: Download remediation artifact
+        uses: actions/download-artifact@v7
+        with:
+          name: npm-audit-remediation
+          path: ${{ runner.temp }}/npm-audit-remediation
+
+      - name: Create or update remediation pull request
+        shell: bash
+        env:
+          GH_TOKEN: ${{ github.token }}
+        run: |
+          git apply "${RUNNER_TEMP}/npm-audit-remediation/fix.patch"
+
+          changed_files=$(git diff --name-only)
+          if [[ "${changed_files}" != "${LOCKFILE}" ]]; then
+            echo "::error::Artifact changed unexpected files:"
+            printf '%s\n' "${changed_files}"
+            exit 1
+          fi
+
+          desired_blob=$(git hash-object "${LOCKFILE}")
+          # Include closed PRs so a maintainer's decision is not undone every 
day.
+          # The REST head filter includes the repository owner, avoiding a fork
+          # PR with the same predictable branch name.
+          latest_pr=$(gh api --method GET \
+            "repos/${GITHUB_REPOSITORY}/pulls" \
+            -f state=all \
+            -f base=master \
+            -f head="${GITHUB_REPOSITORY_OWNER}:${REMEDIATION_BRANCH}" \
+            -f per_page=1 \
+            --jq '.[0] // empty | {number: .number, state: (.state | 
ascii_upcase)}')
+
+          latest_pr_number=$(jq -r '.number // empty' <<< "${latest_pr}")
+          latest_pr_state=$(jq -r '.state // empty' <<< "${latest_pr}")
+
+          if [[ -n "${latest_pr_number}" ]]; then
+            unexpected_files=$(gh pr view "${latest_pr_number}" \
+              --repo "${GITHUB_REPOSITORY}" \
+              --json files \
+              --jq '.files[].path' | grep -vx "${LOCKFILE}" || true)
+
+            if [[ "${latest_pr_state}" == "OPEN" && -n "${unexpected_files}" 
]]; then
+              echo "::error::Existing remediation PR contains unexpected 
files:"
+              printf '%s\n' "${unexpected_files}"
+              exit 1
+            fi
+
+            git fetch --no-tags origin \
+              
"refs/pull/${latest_pr_number}/head:refs/remotes/origin/npm-audit-pr-head"
+            previous_blob=$(git rev-parse \
+              "refs/remotes/origin/npm-audit-pr-head:${LOCKFILE}" 2>/dev/null 
|| true)
+
+            if [[ -z "${unexpected_files}" && "${desired_blob}" == 
"${previous_blob}" ]]; then
+              echo "PR #${latest_pr_number} (${latest_pr_state}) already 
contains this lockfile."
+              echo "Suppressing an identical replacement PR."
+              exit 0
+            fi
+
+            if [[ "${latest_pr_state}" == "OPEN" ]]; then
+              open_pr_number="${latest_pr_number}"
+            fi
+          fi
+
+          remote_sha=$(git ls-remote --heads origin \
+            "refs/heads/${REMEDIATION_BRANCH}" | cut -f1)
+
+          git config user.name "github-actions[bot]"
+          git config user.email 
"41898282+github-actions[bot]@users.noreply.github.com"
+          git switch -C "${REMEDIATION_BRANCH}"
+          git add "${LOCKFILE}"
+          git commit -m "${PR_TITLE}"
+
+          if [[ -n "${remote_sha}" ]]; then
+            git push \
+              
--force-with-lease="refs/heads/${REMEDIATION_BRANCH}:${remote_sha}" \
+              origin "HEAD:refs/heads/${REMEDIATION_BRANCH}"
+          else
+            git push origin "HEAD:refs/heads/${REMEDIATION_BRANCH}"
+          fi
+
+          if [[ -n "${open_pr_number:-}" ]]; then
+            echo "Updated existing PR #${open_pr_number}."
+            exit 0
+          fi
+
+          pr_body="${RUNNER_TEMP}/npm-audit-pr-body.md"
+          {
+            echo '### What is this PR for?'
+            echo
+            echo 'Refresh the zeppelin-react lockfile with compatible updates 
suggested by `npm audit fix`.'
+            echo 'The daily audit remediation workflow generated this change 
after the required high-severity audit began failing.'
+            echo
+            echo '### What type of PR is it?'
+            echo
+            echo 'Hot Fix'
+            echo
+            echo '### What is the Jira issue?'
+            echo
+            echo 'N/A - automated dependency maintenance.'
+            echo
+            echo '### How should this be tested?'
+            echo
+            echo '- `npm ci --ignore-scripts --no-audit` in 
`zeppelin-web-angular`'
+            echo '- `npm ci --ignore-scripts --no-audit` in 
`zeppelin-web-angular/projects/zeppelin-react`'
+            echo '- `npm audit --audit-level=high`'
+            echo '- `npm run lint`'
+            echo '- `npm test`'
+            echo '- `npm run build`'
+            echo
+            echo 'All commands passed before this PR was created.'
+          } > "${pr_body}"
+
+          gh pr create \
+            --repo "${GITHUB_REPOSITORY}" \
+            --base master \
+            --head "${REMEDIATION_BRANCH}" \
+            --title "${PR_TITLE}" \
+            --body-file "${pr_body}" \
+            --draft

Reply via email to