From f53aa409b519233f185a804e64d1ad9258b698ea Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Tue, 13 Oct 2026 11:31:39 -0400 Subject: [PATCH 1/5] ci: add Mend scan workflow for releases --- .github/actions/mend-sast/action.yml | 172 +++++++++++++++++++ .github/actions/mend-sca/action.yml | 211 ++++++++++++++++++++++++ .github/actions/mend-setup/action.yml | 36 ++++ .github/workflows/mend-release-scan.yml | 77 +++++++++ 4 files changed, 496 insertions(+) create mode 100644 .github/actions/mend-sast/action.yml create mode 100644 .github/actions/mend-sca/action.yml create mode 100644 .github/actions/mend-setup/action.yml create mode 100644 .github/workflows/mend-release-scan.yml diff --git a/.github/actions/mend-sast/action.yml b/.github/actions/mend-sast/action.yml new file mode 100644 index 0000000000..5ff5a0317d --- /dev/null +++ b/.github/actions/mend-sast/action.yml @@ -0,0 +1,172 @@ +name: 'Mend SAST Scan' +description: 'SAST scan packages for release' +inputs: + scan_dir: + description: 'Directory to scan' + required: false + default: 'packages/' + MEND_SAST_REPORT_NAME: + description: 'Filename for the output of the scan' + required: false + default: 'Unified_JS_SAST_Scan_Report' + MEND_EMAIL: + required: true + MEND_USER_KEY: + required: true + MEND_SERVER_URL: + required: true + +runs: + using: 'composite' + steps: + - name: Mend SAST scan + env: + MEND_EMAIL: ${{ inputs.MEND_EMAIL }} + MEND_USER_KEY: ${{ inputs.MEND_USER_KEY }} + MEND_URL: ${{ inputs.MEND_SERVER_URL }} + MEND_SAST_PATH_EXCLUSIONS: '/node_modules/,/dist/,/coverage/,/out-tsc/,/temp/' + shell: bash + run: mend code --dir ${{ inputs.scan_dir }} --scope 'Mobile//unified-sdk-javascript' --retries 2 --non-interactive --report --filename ${{ inputs.MEND_SAST_REPORT_NAME }} --formats json --name ${{ inputs.MEND_SAST_REPORT_NAME }}_$(date +%s) > ${{ inputs.MEND_SAST_REPORT_NAME }}.txt + + - name: Extract Scan Results + shell: bash + run: | + export MEND_SAST_TOTAL_VULNERABILITIES_COUNT=$(jq '.[0].stats.totalVulnerabilities' ${{ inputs.MEND_SAST_REPORT_NAME }}.json) + export MEND_SAST_CRITICAL_COUNT=$(jq '.[0].stats.critical' ${{ inputs.MEND_SAST_REPORT_NAME }}.json) + export MEND_SAST_HIGH_COUNT=$(jq '.[0].stats.high' ${{ inputs.MEND_SAST_REPORT_NAME }}.json) + export MEND_SAST_MEDIUM_COUNT=$(jq '.[0].stats.medium' ${{ inputs.MEND_SAST_REPORT_NAME }}.json) + export MEND_SAST_LOW_COUNT=$(jq '.[0].stats.low' ${{ inputs.MEND_SAST_REPORT_NAME }}.json) + export MEND_SAST_SCAN_URL=$(grep -Eo '(http|https)://saas\.whitesourcesoftware\.com[^ ]+' ${{ inputs.MEND_SAST_REPORT_NAME }}.txt | head -n 1) + echo "MEND_SAST_TOTAL_VULNERABILITIES_COUNT=$MEND_SAST_TOTAL_VULNERABILITIES_COUNT" >> $GITHUB_ENV + echo "MEND_SAST_CRITICAL_COUNT=$MEND_SAST_CRITICAL_COUNT" >> $GITHUB_ENV + echo "MEND_SAST_HIGH_COUNT=$MEND_SAST_HIGH_COUNT" >> $GITHUB_ENV + echo "MEND_SAST_MEDIUM_COUNT=$MEND_SAST_MEDIUM_COUNT" >> $GITHUB_ENV + echo "MEND_SAST_LOW_COUNT=$MEND_SAST_LOW_COUNT" >> $GITHUB_ENV + echo "MEND_SAST_SCAN_URL=$MEND_SAST_SCAN_URL" >> $GITHUB_ENV + + - name: Show the SAST Report in Step Summary + shell: bash + run: | + # Determine badge color based on severity + if [ "$MEND_SAST_CRITICAL_COUNT" -gt 0 ]; then + BADGE_COLOR="red" + BADGE_TEXT="${MEND_SAST_CRITICAL_COUNT}_critical" + elif [ "$MEND_SAST_HIGH_COUNT" -gt 0 ]; then + BADGE_COLOR="orange" + BADGE_TEXT="${MEND_SAST_HIGH_COUNT}_high" + elif [ "$MEND_SAST_MEDIUM_COUNT" -gt 0 ]; then + BADGE_COLOR="yellow" + BADGE_TEXT="${MEND_SAST_MEDIUM_COUNT}_medium" + elif [ "$MEND_SAST_LOW_COUNT" -gt 0 ]; then + BADGE_COLOR="blue" + BADGE_TEXT="${MEND_SAST_LOW_COUNT}_low" + else + BADGE_COLOR="brightgreen" + BADGE_TEXT="clean" + fi + + BADGE_URL="https://img.shields.io/badge/SAST-${BADGE_TEXT}-${BADGE_COLOR}" + + echo "## Mend SAST Report ![SAST Badge](${BADGE_URL})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add a table summarizing results + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| 🔴 Critical | $MEND_SAST_CRITICAL_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🟠 High | $MEND_SAST_HIGH_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🟡 Medium | $MEND_SAST_MEDIUM_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🔵 Low | $MEND_SAST_LOW_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add Mend report link if available + if [ -n "$MEND_SAST_SCAN_URL" ]; then + echo "**🔗 [View Full Mend SAST Report]($MEND_SAST_SCAN_URL)**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + # Add expandable section with raw scan output + echo "
Expand for details" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat ${{ inputs.MEND_SAST_REPORT_NAME }}.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + # Step 1: Decide Check Conclusion + # - name: Determine Mend SAST Check Conclusion + # id: sast_conclusion + # shell: bash + # run: | + # if [ "$MEND_SAST_CRITICAL_COUNT" -gt 0 ] || [ "$MEND_SAST_HIGH_COUNT" -gt 0 ]; then + # echo "conclusion=failure" >> $GITHUB_OUTPUT + # else + # echo "conclusion=success" >> $GITHUB_OUTPUT + # fi + + # Step 2: Publish Mend SAST Check + # - name: Publish Mend SAST Check + # uses: actions/github-script@v9 + # env: + # REPORT_NAME: ${{ inputs.MEND_SAST_REPORT_NAME }} + # with: + # script: | + # const fs = require('fs'); + # const body = fs.readFileSync(process.env.REPORT_NAME + '.txt', 'utf8'); + + # await github.rest.checks.create({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # name: "Mend SAST Report", + # head_sha: '${{ inputs.head_sha || github.event.pull_request.head.sha || github.sha }}', + # status: "completed", + # conclusion: "${{ steps.sast_conclusion.outputs.conclusion }}", + # output: { + # title: "Mend SAST Report", + # summary: `Critical: ${process.env.MEND_SAST_CRITICAL_COUNT}, High: ${process.env.MEND_SAST_HIGH_COUNT}`, + # text: "```\n" + body + "\n```" + # } + # }); + + - name: Upload SAST scan report + uses: actions/upload-artifact@v7 + with: + name: mend-sast-report + path: | + ${{ inputs.MEND_SAST_REPORT_NAME }}.txt + ${{ inputs.MEND_SAST_REPORT_NAME }}.json + + # - name: Send slack notification + # uses: 8398a7/action-slack@v3 + # if: always() + # with: + # status: custom + # fields: all + # custom_payload: | + # { + # "text": "*Mend SAST Scan Results*", + # "attachments": [ + # { + # "color": "${{ (env.MEND_SAST_CRITICAL_COUNT == '0' && env.MEND_SAST_HIGH_COUNT == '0') && 'good' || 'danger' }}", + # "fields": [ + # { + # "title": "SAST scan", + # "value": "Total: ${{ env.MEND_SAST_TOTAL_VULNERABILITIES_COUNT }} | Critical: ${{ env.MEND_SAST_CRITICAL_COUNT }} | High: ${{ env.MEND_SAST_HIGH_COUNT }} | Medium: ${{ env.MEND_SAST_MEDIUM_COUNT }} | Low: ${{ env.MEND_SAST_LOW_COUNT }}\n<${{ env.MEND_SAST_SCAN_URL }}|View full SAST report>", + # "short": false + # } + # ] + # } + # ] + # } + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + # Fail the job on Critical/High findings + - name: Fail on Critical/High severity vulnerabilities + shell: bash + run: | + if [ "$MEND_SAST_CRITICAL_COUNT" -gt 0 ] || [ "$MEND_SAST_HIGH_COUNT" -gt 0 ]; then + echo "::error::Mend SAST scan found ${MEND_SAST_CRITICAL_COUNT} critical and ${MEND_SAST_HIGH_COUNT} high severity vulnerabilities. See the Mend SAST Report step summary for details." + exit 1 + fi + echo "No critical or high severity vulnerabilities found." diff --git a/.github/actions/mend-sca/action.yml b/.github/actions/mend-sca/action.yml new file mode 100644 index 0000000000..e17bc1a3db --- /dev/null +++ b/.github/actions/mend-sca/action.yml @@ -0,0 +1,211 @@ +name: 'Mend SCA Scan' +description: 'SCA scan packages for release' +inputs: + scan_dir: + description: 'Directory to scan' + required: false + default: 'packages/' + MEND_SCA_REPORT_NAME: + description: 'Filename for the output of the scan' + required: false + default: 'Unified_JS_SCA_Scan_Report' + MEND_EMAIL: + required: true + MEND_USER_KEY: + required: true + MEND_SERVER_URL: + required: true + +runs: + using: 'composite' + steps: + - uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Setup pnpm config + run: pnpm config set store-dir '.pnpm-store' --global + shell: bash + + - uses: actions/setup-node@v6 + with: + node-version-file: '.node-version' + cache: 'pnpm' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + shell: bash + + # Run Mend scan and tee output to both console and file + - name: Mend SCA scan + env: + MEND_EMAIL: ${{ inputs.MEND_EMAIL }} + MEND_USER_KEY: ${{ inputs.MEND_USER_KEY }} + MEND_URL: ${{ inputs.MEND_SERVER_URL }} + shell: bash + run: mend dep --dir ${{ inputs.scan_dir }} --non-interactive --update --scope 'Mobile//unified-sdk-javascript' 2>&1 | tee ${{ inputs.MEND_SCA_REPORT_NAME }}.txt + + - name: Extract Scan Results + shell: bash + run: | + # Extract scan results with more robust parsing + MEND_SCA_SCAN_URL=$(grep -Eo '(http|https)://saas\.whitesourcesoftware\.com[^ ]+' ${{ inputs.MEND_SCA_REPORT_NAME }}.txt | head -n 1) + MEND_SCA_SCAN_SUMMARY=$(grep -E "Detected.*vulnerabilities" ${{ inputs.MEND_SCA_REPORT_NAME }}.txt || echo 'No vulnerabilities detected') + + # Initialize counts with default value of 0 + MEND_SCA_CRITICAL_COUNT=0 + MEND_SCA_HIGH_COUNT=0 + MEND_SCA_MEDIUM_COUNT=0 + MEND_SCA_LOW_COUNT=0 + + # Extract vulnerability counts with error handling + if grep -q "Critical" ${{ inputs.MEND_SCA_REPORT_NAME }}.txt; then + MEND_SCA_CRITICAL_COUNT=$(grep -Eo '[0-9]+ Critical' ${{ inputs.MEND_SCA_REPORT_NAME }}.txt | awk '{print $1}') + fi + if grep -q "High" ${{ inputs.MEND_SCA_REPORT_NAME }}.txt; then + MEND_SCA_HIGH_COUNT=$(grep -Eo '[0-9]+ High' ${{ inputs.MEND_SCA_REPORT_NAME }}.txt | awk '{print $1}') + fi + if grep -q "Medium" ${{ inputs.MEND_SCA_REPORT_NAME }}.txt; then + MEND_SCA_MEDIUM_COUNT=$(grep -Eo '[0-9]+ Medium' ${{ inputs.MEND_SCA_REPORT_NAME }}.txt | awk '{print $1}') + fi + if grep -q "Low" ${{ inputs.MEND_SCA_REPORT_NAME }}.txt; then + MEND_SCA_LOW_COUNT=$(grep -Eo '[0-9]+ Low' ${{ inputs.MEND_SCA_REPORT_NAME }}.txt | awk '{print $1}') + fi + + # Set environment variables with error checking + if [ ! -z "$MEND_SCA_SCAN_URL" ]; then + echo "MEND_SCA_SCAN_URL=$MEND_SCA_SCAN_URL" >> $GITHUB_ENV + fi + echo "MEND_SCA_SCAN_SUMMARY=$MEND_SCA_SCAN_SUMMARY" >> $GITHUB_ENV + echo "MEND_SCA_CRITICAL_COUNT=$MEND_SCA_CRITICAL_COUNT" >> $GITHUB_ENV + echo "MEND_SCA_HIGH_COUNT=$MEND_SCA_HIGH_COUNT" >> $GITHUB_ENV + echo "MEND_SCA_MEDIUM_COUNT=$MEND_SCA_MEDIUM_COUNT" >> $GITHUB_ENV + echo "MEND_SCA_LOW_COUNT=$MEND_SCA_LOW_COUNT" >> $GITHUB_ENV + + - name: Show the SCA Report in Step Summary + shell: bash + run: | + # Pick badge color based on severity counts + if [ "$MEND_SCA_CRITICAL_COUNT" -gt 0 ]; then + BADGE_COLOR="red" + BADGE_TEXT="${MEND_SCA_CRITICAL_COUNT}_critical" + elif [ "$MEND_SCA_HIGH_COUNT" -gt 0 ]; then + BADGE_COLOR="orange" + BADGE_TEXT="${MEND_SCA_HIGH_COUNT}_high" + elif [ "$MEND_SCA_MEDIUM_COUNT" -gt 0 ]; then + BADGE_COLOR="yellow" + BADGE_TEXT="${MEND_SCA_MEDIUM_COUNT}_medium" + elif [ "$MEND_SCA_LOW_COUNT" -gt 0 ]; then + BADGE_COLOR="blue" + BADGE_TEXT="${MEND_SCA_LOW_COUNT}_low" + else + BADGE_COLOR="brightgreen" + BADGE_TEXT="clean" + fi + + BADGE_URL="https://img.shields.io/badge/SCA-${BADGE_TEXT}-${BADGE_COLOR}" + + echo "## Mend SCA Report ![SCA Badge](${BADGE_URL})" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Show counts in a table + echo "| Severity | Count |" >> $GITHUB_STEP_SUMMARY + echo "|----------|-------|" >> $GITHUB_STEP_SUMMARY + echo "| 🔴 Critical | $MEND_SCA_CRITICAL_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🟠 High | $MEND_SCA_HIGH_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🟡 Medium | $MEND_SCA_MEDIUM_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "| 🔵 Low | $MEND_SCA_LOW_COUNT |" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add Mend report link if available + if [ -n "$MEND_SCA_SCAN_URL" ]; then + echo "**🔗 [View Full Mend SCA Report]($MEND_SCA_SCAN_URL)**" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + # Expandable section for full output + echo "
Expand for details" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + cat ${{ inputs.MEND_SCA_REPORT_NAME }}.txt >> $GITHUB_STEP_SUMMARY + echo '```' >> $GITHUB_STEP_SUMMARY + echo "
" >> $GITHUB_STEP_SUMMARY + + # # Step 1: Decide Check Conclusion + # - name: Determine Mend SCA Check Conclusion + # id: sca_conclusion + # shell: bash + # run: | + # if [ "$MEND_SCA_CRITICAL_COUNT" -gt 0 ] || [ "$MEND_SCA_HIGH_COUNT" -gt 0 ]; then + # echo "conclusion=failure" >> $GITHUB_OUTPUT + # else + # echo "conclusion=success" >> $GITHUB_OUTPUT + # fi + + # # Step 2: Publish Mend SCA Check + # - name: Publish Mend SCA Check + # uses: actions/github-script@v9 + # env: + # REPORT_NAME: ${{ inputs.MEND_SCA_REPORT_NAME }} + # with: + # script: | + # const fs = require('fs'); + # const body = fs.readFileSync(process.env.REPORT_NAME + '.txt', 'utf8'); + + # await github.rest.checks.create({ + # owner: context.repo.owner, + # repo: context.repo.repo, + # name: "Mend SCA Report", + # head_sha: '${{ inputs.head_sha || github.event.pull_request.head.sha || github.sha }}', + # status: "completed", + # conclusion: "${{ steps.sca_conclusion.outputs.conclusion }}", + # output: { + # title: "Mend SCA Report", + # summary: `Critical: ${process.env.MEND_SCA_CRITICAL_COUNT}, High: ${process.env.MEND_SCA_HIGH_COUNT}`, + # text: "```\n" + body + "\n```" + # } + # }); + + - name: Upload SCA scan report + uses: actions/upload-artifact@v7 + with: + name: mend-sca-report + path: ${{ inputs.MEND_SCA_REPORT_NAME }}.txt + + # - name: Send slack notification + # uses: 8398a7/action-slack@v3 + # if: always() + # with: + # status: custom + # fields: all + # custom_payload: | + # { + # "text": "*Mend SCA Scan Results*", + # "attachments": [ + # { + # "color": "${{ (env.MEND_SCA_CRITICAL_COUNT == '0' && env.MEND_SCA_HIGH_COUNT == '0') && 'good' || 'danger' }}", + # "fields": [ + # { + # "title": "SCA scan", + # "value": "${{ env.MEND_SCA_SCAN_SUMMARY }}\n<${{ env.MEND_SCA_SCAN_URL }}|View full SCA report>", + # "short": false + # } + # ] + # } + # ] + # } + # env: + # SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }} + + # Fail the job (not just the informational Check above) so that + # ci.yaml's `needs:` gating and branch protection actually block on + # Critical/High findings instead of showing green. + - name: Fail on Critical/High severity vulnerabilities + shell: bash + run: | + if [ "$MEND_SCA_CRITICAL_COUNT" -gt 0 ] || [ "$MEND_SCA_HIGH_COUNT" -gt 0 ]; then + echo "::error::Mend SCA scan found ${MEND_SCA_CRITICAL_COUNT} critical and ${MEND_SCA_HIGH_COUNT} high severity vulnerabilities. See the Mend SCA Report step summary for details." + exit 1 + fi + echo "No critical or high severity vulnerabilities found." diff --git a/.github/actions/mend-setup/action.yml b/.github/actions/mend-setup/action.yml new file mode 100644 index 0000000000..73592940ac --- /dev/null +++ b/.github/actions/mend-setup/action.yml @@ -0,0 +1,36 @@ +name: 'Mend Scan Setup' +description: 'Setup a Mend scan' + +runs: + using: 'composite' + steps: + # Fetch just the checksum first (a few bytes) so the cache key reflects the + # CLI version Mend is currently serving. + - name: Fetch Mend CLI checksum + shell: bash + run: curl -fsSL https://downloads.mend.io/cli/linux_amd64/mend.sha256 -o mend.sha256 + + - name: Restore Cached Mend CLI + id: mend-cache + uses: actions/cache@v6 + env: + mend-cache-name: mend-cli + with: + path: ${{ github.workspace }}/bin/mend + key: ${{ runner.os }}-${{ env.mend-cache-name }}-${{ hashFiles('mend.sha256') }} + + - if: ${{ steps.mend-cache.outputs.cache-hit != 'true' }} + name: Download Mend CLI executable (cache miss...) + shell: bash + run: | + mkdir -p ${{ github.workspace }}/bin + curl https://downloads.mend.io/cli/linux_amd64/mend -o ${{ github.workspace }}/bin/mend + chmod +x ${{ github.workspace }}/bin/mend + + - name: Add Mend CLI to Path + shell: bash + run: echo "${{ github.workspace }}/bin" >> $GITHUB_PATH + + - name: Test Mend CLI + shell: bash + run: mend version diff --git a/.github/workflows/mend-release-scan.yml b/.github/workflows/mend-release-scan.yml new file mode 100644 index 0000000000..32b15c27eb --- /dev/null +++ b/.github/workflows/mend-release-scan.yml @@ -0,0 +1,77 @@ +# Copyright (c) 2026 Ping Identity Corporation. All rights reserved. +# +# This software may be modified and distributed under the terms +# of the MIT license. See the LICENSE file for details. +# + +name: Mend Scans for Release +on: + workflow_dispatch: + inputs: + scan_dir: + description: 'Directory to scan' + required: false + default: 'packages/' + type: string + sast_report_name: + description: 'Mend SAST scan report name' + required: false + default: 'Unified_JS_SAST_Scan_Report' + type: string + sca_report_name: + description: 'Mend SCA scan report name' + required: false + default: 'Unified_JS_SCA_Scan_Report' + type: string +env: + MEND_EMAIL: ${{ secrets.MEND_EMAIL }} + MEND_USER_KEY: ${{ secrets.MEND_USER_KEY }} + MEND_SERVER_URL: ${{ vars.MEND_SERVER_URL }} + GH_TOKEN: ${{ secrets.GH_TOKEN }} + +jobs: + mend-sast-scan: + runs-on: ubuntu-latest + steps: + - name: Clone the repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + token: ${{ env.GH_TOKEN }} + + - name: Setup Mend + uses: ./.github/actions/mend-setup + + - name: Mend SAST scan + uses: ./.github/actions/mend-sast + timeout-minutes: 10 + with: + scan_dir: ${{ inputs.scan_dir }} + MEND_SAST_REPORT_NAME: ${{ inputs.sast_report_name }} + MEND_EMAIL: ${{ env.MEND_EMAIL }} + MEND_USER_KEY: ${{ env.MEND_USER_KEY }} + MEND_SERVER_URL: ${{ env.MEND_SERVER_URL }} + + mend-sca-scan: + runs-on: ubuntu-latest + needs: mend-sast-scan + if: always() + steps: + - name: Clone the repository + uses: actions/checkout@v7 + with: + fetch-depth: 0 + token: ${{ env.GH_TOKEN }} + + - name: Setup Mend + uses: ./.github/actions/mend-setup + + - name: Mend SCA scan + uses: ./.github/actions/mend-sca + timeout-minutes: 10 + with: + scan_dir: ${{ inputs.scan_dir }} + MEND_SCA_REPORT_NAME: ${{ inputs.sca_report_name }} + MEND_EMAIL: ${{ env.MEND_EMAIL }} + MEND_USER_KEY: ${{ env.MEND_USER_KEY }} + MEND_SERVER_URL: ${{ env.MEND_SERVER_URL }} From ec5892f74bba943bd927c847fb9434f4aa65ef9e Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:26:32 -0400 Subject: [PATCH 2/5] ci: fix lefthook order of operations --- lefthook.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lefthook.yml b/lefthook.yml index 88bc8d5168..651e3a4986 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,17 +1,23 @@ pre-commit: + piped: true commands: nx-sync: + priority: 1 run: pnpm nx sync copyright-sync: + priority: 2 run: node tools/copyright/sync-header-years.mjs --fix stage_fixed: true nx-check: + priority: 3 run: pnpm nx affected -t typecheck lint build api-report --tui=false stage_fixed: true format: + priority: 4 run: pnpm nx format:write stage_fixed: true syncpack: + priority: 5 glob: >- {package.json, pnpm-workspace.yaml, @@ -23,6 +29,7 @@ pre-commit: scratchpad/package.json} run: pnpm syncpack:lint interface-mapping: + priority: 6 glob: >- {tools/interface-mapping-validator/**/*.ts, interface_mapping.md, From 4d57472046b6df81afc015d746bde8d235dbc8e4 Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:36:46 -0400 Subject: [PATCH 3/5] ci: fix playwright install --- .github/actions/setup/action.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9d83145428..90562c02e5 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -49,8 +49,14 @@ runs: ${{ runner.os }}-playwright- - name: Install Playwright - run: pnpm exec playwright install shell: bash + # Playwright fails to install Chromium on Ubuntu 26.04 x64 because its built-in OS-to-platform mapping + # table does not yet recognize the new operating system version, triggering an ERROR: Playwright does + # not support chromium on ubuntu26.04-x64 message. + # https://github.com/microsoft/playwright/issues/40117 + run: | + export PLAYWRIGHT_HOST_PLATFORM_OVERRIDE="ubuntu24.04-x64" + pnpm exec playwright install - name: Set Nx SHAs uses: nrwl/nx-set-shas@v4 From 2faf06fd55c40571ea8bfee3fb3483616ae5e260 Mon Sep 17 00:00:00 2001 From: AJ Ancheta <7781450+ancheetah@users.noreply.github.com> Date: Wed, 21 Oct 2026 10:33:19 -0400 Subject: [PATCH 4/5] ci(mend): add full scan option and cli integrity check --- .github/actions/mend-sast/action.yml | 15 ++++++++++++++- .github/actions/mend-sca/action.yml | 18 ++++++++++++++++-- .github/actions/mend-setup/action.yml | 9 ++++++++- .github/workflows/mend-release-scan.yml | 8 ++++++++ 4 files changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/actions/mend-sast/action.yml b/.github/actions/mend-sast/action.yml index 5ff5a0317d..8238c5e032 100644 --- a/.github/actions/mend-sast/action.yml +++ b/.github/actions/mend-sast/action.yml @@ -5,6 +5,10 @@ inputs: description: 'Directory to scan' required: false default: 'packages/' + full_scan: + description: 'Scan full repo if true' + required: false + default: 'false' MEND_SAST_REPORT_NAME: description: 'Filename for the output of the scan' required: false @@ -21,12 +25,21 @@ runs: steps: - name: Mend SAST scan env: + SCAN_DIR: ${{ inputs.scan_dir }} + FULL_SCAN: ${{ inputs.full_scan }} + REPORT_NAME: ${{ inputs.MEND_SAST_REPORT_NAME }} MEND_EMAIL: ${{ inputs.MEND_EMAIL }} MEND_USER_KEY: ${{ inputs.MEND_USER_KEY }} MEND_URL: ${{ inputs.MEND_SERVER_URL }} MEND_SAST_PATH_EXCLUSIONS: '/node_modules/,/dist/,/coverage/,/out-tsc/,/temp/' shell: bash - run: mend code --dir ${{ inputs.scan_dir }} --scope 'Mobile//unified-sdk-javascript' --retries 2 --non-interactive --report --filename ${{ inputs.MEND_SAST_REPORT_NAME }} --formats json --name ${{ inputs.MEND_SAST_REPORT_NAME }}_$(date +%s) > ${{ inputs.MEND_SAST_REPORT_NAME }}.txt + run: | + args=(--scope 'Mobile//unified-sdk-javascript' --retries 2 --non-interactive --report --filename "$REPORT_NAME" --formats json --name "${REPORT_NAME}_$(date +%s)") + # Omit `--dir` on a full scan so Mend scans the whole repo. + if [ "$FULL_SCAN" != "true" ]; then + args+=(--dir "$SCAN_DIR") + fi + mend code "${args[@]}" > "$REPORT_NAME.txt" - name: Extract Scan Results shell: bash diff --git a/.github/actions/mend-sca/action.yml b/.github/actions/mend-sca/action.yml index e17bc1a3db..529350f80b 100644 --- a/.github/actions/mend-sca/action.yml +++ b/.github/actions/mend-sca/action.yml @@ -5,6 +5,10 @@ inputs: description: 'Directory to scan' required: false default: 'packages/' + full_scan: + description: 'Scan full repo if true' + required: false + default: 'false' MEND_SCA_REPORT_NAME: description: 'Filename for the output of the scan' required: false @@ -37,14 +41,24 @@ runs: run: pnpm install --frozen-lockfile shell: bash - # Run Mend scan and tee output to both console and file - name: Mend SCA scan env: + SCAN_DIR: ${{ inputs.scan_dir }} + FULL_SCAN: ${{ inputs.full_scan }} + REPORT_NAME: ${{ inputs.MEND_SCA_REPORT_NAME }} MEND_EMAIL: ${{ inputs.MEND_EMAIL }} MEND_USER_KEY: ${{ inputs.MEND_USER_KEY }} MEND_URL: ${{ inputs.MEND_SERVER_URL }} shell: bash - run: mend dep --dir ${{ inputs.scan_dir }} --non-interactive --update --scope 'Mobile//unified-sdk-javascript' 2>&1 | tee ${{ inputs.MEND_SCA_REPORT_NAME }}.txt + # Mend SCA scan does not generate a report so tee output to both console and file + # --update flag syncs the results to the Mend portal and overwrites any pre-existing vulnerabilities + run: | + args=(--non-interactive --update --scope 'Mobile//unified-sdk-javascript') + # Omit `--dir` on a full scan so Mend scans the whole repo. + if [ "$FULL_SCAN" != "true" ]; then + args+=(--dir "$SCAN_DIR") + fi + mend dep "${args[@]}" 2>&1 | tee "$REPORT_NAME.txt" - name: Extract Scan Results shell: bash diff --git a/.github/actions/mend-setup/action.yml b/.github/actions/mend-setup/action.yml index 73592940ac..cc331d9277 100644 --- a/.github/actions/mend-setup/action.yml +++ b/.github/actions/mend-setup/action.yml @@ -22,9 +22,16 @@ runs: - if: ${{ steps.mend-cache.outputs.cache-hit != 'true' }} name: Download Mend CLI executable (cache miss...) shell: bash + # Download Mend CLI if it passes integrity check run: | mkdir -p ${{ github.workspace }}/bin - curl https://downloads.mend.io/cli/linux_amd64/mend -o ${{ github.workspace }}/bin/mend + curl -fsSL https://downloads.mend.io/cli/linux_amd64/mend -o ${{ github.workspace }}/bin/mend + expected="$(awk '{print $1}' mend.sha256)" + actual="$(sha256sum "${{ github.workspace }}/bin/mend" | awk '{print $1}')" + if [ "${actual}" != "${expected}" ]; then + echo "::error::Mend CLI checksum mismatch: expected ${expected}, got ${actual}" + exit 1 + fi chmod +x ${{ github.workspace }}/bin/mend - name: Add Mend CLI to Path diff --git a/.github/workflows/mend-release-scan.yml b/.github/workflows/mend-release-scan.yml index 32b15c27eb..d7cb94edf3 100644 --- a/.github/workflows/mend-release-scan.yml +++ b/.github/workflows/mend-release-scan.yml @@ -13,6 +13,11 @@ on: required: false default: 'packages/' type: string + full_scan: + description: 'Scan full repo if true' + required: false + type: boolean + default: false sast_report_name: description: 'Mend SAST scan report name' required: false @@ -47,6 +52,7 @@ jobs: timeout-minutes: 10 with: scan_dir: ${{ inputs.scan_dir }} + full_scan: ${{ inputs.full_scan }} MEND_SAST_REPORT_NAME: ${{ inputs.sast_report_name }} MEND_EMAIL: ${{ env.MEND_EMAIL }} MEND_USER_KEY: ${{ env.MEND_USER_KEY }} @@ -54,6 +60,7 @@ jobs: mend-sca-scan: runs-on: ubuntu-latest + # Force the Mend SCA scan to always run. It is run sequentially after the SAST scan instead of in parallel. needs: mend-sast-scan if: always() steps: @@ -71,6 +78,7 @@ jobs: timeout-minutes: 10 with: scan_dir: ${{ inputs.scan_dir }} + full_scan: ${{ inputs.full_scan }} MEND_SCA_REPORT_NAME: ${{ inputs.sca_report_name }} MEND_EMAIL: ${{ env.MEND_EMAIL }} MEND_USER_KEY: ${{ env.MEND_USER_KEY }} From 5cbd7ccef5e70a3f716c5ed094d753b67f09af9c Mon Sep 17 00:00:00 2001 From: "nx-cloud[bot]" <71083854+nx-cloud[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:50:23 +0000 Subject: [PATCH 5/5] ci(mend): add full scan option and cli integrity check [Self-Healing CI Rerun]