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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions .github/actions/mend-sast/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
name: 'Mend SAST Scan'
description: 'SAST scan packages for release'
inputs:
scan_dir:
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
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:
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: |
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
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
Comment on lines +47 to +58

@ryanbas21 ryanbas21 Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine - but perhaps brittle, couldn't we do this in an env: block?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that possible? I think you need to use bash to do this. This reads from the report json and stores those values in variables on the gh environment. I thought the env: block was intended for static values or reading from GH defined inputs or secrets or vars.


- 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 "<details><summary>Expand for details</summary>" >> $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 "</details>" >> $GITHUB_STEP_SUMMARY

# Step 1: Decide Check Conclusion

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving these "Check" steps in case we decide to run this as part of PR CI in the future. This tells Github to mark the Mend scan check with a green checkmark (success) or red X (failure) next to the commit hash.

# - 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."
Loading
Loading