Skip to content

Commit a22cd35

Browse files
committed
feat: add GitHub Actions for Mend CLI image, code and dependency scan
1 parent ece1505 commit a22cd35

6 files changed

Lines changed: 529 additions & 0 deletions

File tree

mend-image/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Mend Image Scan Action
2+
3+
## Usage
4+
5+
```yaml
6+
- uses: vespa-engine/gh-actions/mend-image@main
7+
with:
8+
image-name: "docker.io/vespaengine/vespa:latest"
9+
mend-api-key: ${{ secrets.MEND_API_KEY }}
10+
mend-user: ${{ secrets.MEND_USER }}
11+
```

mend-image/action.yml

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
name: Image Scan with Mend CLI
2+
description: Run Mend container image scanning on your images
3+
4+
inputs:
5+
image-name:
6+
description: "Container image to scan (e.g., alpine:latest, myregistry.io/app:v1.0)"
7+
required: true
8+
9+
mend-app-name:
10+
description: "Mend Application Name"
11+
required: true
12+
13+
mend-project-name:
14+
description: "Mend Project Name"
15+
required: true
16+
17+
mend-api-key:
18+
description: "Mend API Key (pass from secrets)"
19+
required: true
20+
21+
mend-user:
22+
description: "Mend User Email (pass from secrets)"
23+
required: true
24+
25+
mend-url:
26+
description: "Mend Server URL"
27+
required: false
28+
default: "https://saas-eu.mend.io"
29+
30+
local-pull:
31+
description: "Pull from local Docker only (true/false)"
32+
required: false
33+
default: "false"
34+
35+
fail-on-policy-violation:
36+
description: "Fail build on policy violations (true/false)"
37+
required: false
38+
default: "true"
39+
40+
log-level:
41+
description: "Log level (INFO, DEBUG, WARNING, ERROR)"
42+
required: false
43+
default: "INFO"
44+
45+
runs:
46+
using: "composite"
47+
steps:
48+
- name: Install Mend CLI
49+
shell: bash
50+
env:
51+
ARCH: ${{ contains(fromJson('["X86", "X64"]'), runner.arch) && 'amd64' || 'arm64' }}
52+
run: |
53+
CLI_URL="https://downloads.mend.io/cli/linux_${ARCH}/mend"
54+
echo "📥 Downloading Mend CLI from ${CLI_URL}"
55+
curl -fsSL "${CLI_URL}" -o /tmp/mend
56+
sudo mv /tmp/mend /usr/local/bin/mend
57+
sudo chmod +x /usr/local/bin/mend
58+
mend version
59+
60+
- name: Run Mend Image Scan
61+
shell: bash
62+
id: scan
63+
env:
64+
# Authentication variables
65+
MEND_USER_KEY: ${{ inputs.mend-api-key }}
66+
MEND_EMAIL: ${{ inputs.mend-user }}
67+
MEND_URL: ${{ inputs.mend-url }}
68+
69+
# Scope
70+
MEND_APP: ${{ inputs.mend-app-name }}
71+
MEND_PROJ: ${{ inputs.mend-project-name }}
72+
73+
MEND_LOG_LEVEL: ${{ inputs.log-level }}
74+
run: |
75+
echo "🐳 Starting Mend Image Scan..."
76+
echo "Image: ${{ inputs.image-name }}"
77+
echo "Application: ${MEND_APP}"
78+
echo "Project: ${MEND_PROJ}"
79+
80+
mend_args=(
81+
"--non-interactive"
82+
"--scope" "${MEND_APP}//${MEND_PROJ}"
83+
)
84+
85+
# Add local-pull flag if enabled
86+
if [[ "${{ inputs.local-pull }}" == "true" ]]; then
87+
mend_args+=("--local-pull")
88+
fi
89+
90+
# Add fail-policy flag if enabled
91+
if [[ "${{ inputs.fail-on-policy-violation }}" == "true" ]]; then
92+
mend_args+=("--fail-policy")
93+
fi
94+
95+
set +e
96+
mend image "${{ inputs.image-name }}" "${mend_args[@]}"
97+
exit_code=$?
98+
99+
# Save exit code for next step
100+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
101+
echo "MEND_APP=${MEND_APP}" >> $GITHUB_ENV
102+
echo "MEND_PROJ=${MEND_PROJ}" >> $GITHUB_ENV
103+
104+
- name: Process Scan Results
105+
shell: bash
106+
env:
107+
SCAN_EXIT_CODE: ${{ steps.scan.outputs.mend-exit }}
108+
run: |
109+
# Generate GitHub Actions Job Summary
110+
echo "## 🐳 Mend Image Scan Results" >> $GITHUB_STEP_SUMMARY
111+
echo "" >> $GITHUB_STEP_SUMMARY
112+
echo "**Image:** ${{ inputs.image-name }}" >> $GITHUB_STEP_SUMMARY
113+
echo "**Application:** ${MEND_APP}" >> $GITHUB_STEP_SUMMARY
114+
echo "**Project:** ${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
115+
echo "" >> $GITHUB_STEP_SUMMARY
116+
117+
case $SCAN_EXIT_CODE in
118+
0)
119+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
120+
echo "Image scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
121+
;;
122+
1)
123+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
124+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
125+
;;
126+
2)
127+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
128+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
129+
;;
130+
9)
131+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
132+
echo "Image scan results contain vulnerabilities that contravene the defined policy." >> $GITHUB_STEP_SUMMARY
133+
echo "" >> $GITHUB_STEP_SUMMARY
134+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
135+
;;
136+
*)
137+
echo "### ❌ Error" >> $GITHUB_STEP_SUMMARY
138+
echo "Image scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
139+
echo "" >> $GITHUB_STEP_SUMMARY
140+
echo "Check the Mend CLI documentation for exit code details." >> $GITHUB_STEP_SUMMARY
141+
;;
142+
esac
143+
144+
exit $SCAN_EXIT_CODE

mend-sast/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Mend Code Scan Action
2+
3+
## Usage
4+
5+
```yaml
6+
- uses: vespa-engine/gh-actions/mend-sast-scan@main
7+
with:
8+
mend-app-name: "vespa-engine"
9+
mend-project-name: "MyProject" # Optional
10+
mend-api-key: ${{ secrets.MEND_API_KEY }}
11+
mend-email: ${{ secrets.MEND_EMAIL }}
12+
```

mend-sast/action.yml

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
name: Code Scan with Mend CLI
2+
description: Run Mend SAST security scanning on your codebase
3+
4+
inputs:
5+
mend-app-name:
6+
description: "Mend Application Name"
7+
required: true
8+
9+
mend-project-name:
10+
description: "Mend Project Name"
11+
required: true
12+
13+
mend-api-key:
14+
description: "Mend API Key (pass from secrets)"
15+
required: true
16+
17+
mend-user:
18+
description: "Mend User Email (pass from secrets)"
19+
required: true
20+
21+
scan-name:
22+
description: "Name of the scan"
23+
required: false
24+
default: ""
25+
26+
update:
27+
description: "Update results to Mend (true/false)"
28+
required: false
29+
default: "true"
30+
31+
target-directory:
32+
description: "Target directory to scan, relative to GitHub workspace"
33+
required: false
34+
default: ""
35+
36+
log-level:
37+
description: "Log level (INFO, DEBUG, WARNING, ERROR)"
38+
required: false
39+
default: "INFO"
40+
41+
secrets-detection:
42+
description: "Enable secrets detection (true/false)"
43+
required: false
44+
default: "true"
45+
46+
incremental-scan:
47+
description: "Enable incremental scan (true/false)"
48+
required: false
49+
default: ${{ github.event_name == 'pull_request' }}
50+
51+
github-upload:
52+
description: "Upload results to GitHub Code Scanning Alerts (true/false)"
53+
required: false
54+
default: "true"
55+
56+
enabled-engines:
57+
description: |
58+
**Recommended:** Comma-separated list of SAST engine IDs (e.g., '101,108' for Java and JavaScript). Omit for auto-recognition.
59+
Useful ones: Ruby(5), C/C++(12), Go(18), Java(101), Python(104), JavaScript(108)
60+
[See documentation for complete list](https://docs.mend.io/platform/latest/configure-the-mend-cli-for-sast#ConfiguretheMendCLIforSAST-Mend-CLI-SAST-supported-languages-and-engine-IDsMendCLISAST-supportedlanguagesandengineIDs)
61+
required: false
62+
default: ""
63+
64+
runs:
65+
using: "composite"
66+
steps:
67+
- name: Install Mend CLI
68+
shell: bash
69+
env:
70+
ARCH: ${{ contains(fromJson('["X86", "X64"]'), runner.arch) && 'amd64' || 'arm64' }}
71+
run: |
72+
CLI_URL="https://downloads.mend.io/cli/linux_${ARCH}/mend"
73+
echo "📥 Downloading Mend CLI from ${CLI_URL}"
74+
curl -fsSL "${CLI_URL}" -o /tmp/mend
75+
sudo mv /tmp/mend /usr/local/bin/mend
76+
sudo chmod +x /usr/local/bin/mend
77+
mend version
78+
79+
- name: Run Mend Code Scan
80+
shell: bash
81+
id: scan
82+
env:
83+
# Authentication variables
84+
MEND_USER_KEY: ${{ inputs.mend-api-key }}
85+
MEND_EMAIL: ${{ inputs.mend-user }}
86+
MEND_URL: https://saas-eu.mend.io
87+
88+
# Scope
89+
MEND_APP: ${{ inputs.mend-app-name }}
90+
MEND_PROJ: ${{ inputs.mend-project-name }}
91+
92+
# Scan configuration
93+
MEND_SAST_TARGET_DIRECTORY: ${{ github.workspace }}/${{ inputs.target-directory }}
94+
MEND_LOG_LEVEL: ${{ inputs.log-level }}
95+
MEND_SAST_ENGINES: ${{ inputs.enabled-engines }}
96+
97+
MEND_SAST_TIMEOUT_TOTAL: "3600"
98+
MEND_SAST_SCAN_RETRIES: "3"
99+
100+
run: |
101+
echo "🔍 Starting Mend Code Scan..."
102+
echo "Application: ${MEND_APP}"
103+
echo "Project: ${MEND_PROJ}"
104+
echo "Target Directory: ${MEND_SAST_TARGET_DIRECTORY}"
105+
106+
mend_args=(
107+
"--non-interactive"
108+
# Mend Organization in scope is assumed from API key
109+
"--scope" "${MEND_APP}//${MEND_PROJ}"
110+
)
111+
112+
if [[ -n "${{ inputs.scan-name }}" ]]; then
113+
mend_args+=("--name" "${{ inputs.scan-name }}")
114+
fi
115+
116+
if [[ "${{ inputs.update }}" != "true" ]]; then
117+
mend_args+=("--no-logs")
118+
fi
119+
120+
if [[ "${{ inputs.secrets-detection }}" == "true" ]]; then
121+
mend_args+=("--secrets-detection")
122+
fi
123+
124+
if [[ "${{ inputs.incremental-scan }}" == "true" ]]; then
125+
mend_args+=("--inc")
126+
fi
127+
128+
if [[ "${{ inputs.github-upload }}" == "true" ]]; then
129+
mend_args+=(
130+
"--report"
131+
# Format extension is appended by CLI
132+
"--filename" "mend"
133+
"--formats" "sarif"
134+
)
135+
fi
136+
137+
set +e
138+
mend sast "${mend_args[@]}"
139+
exit_code=$?
140+
141+
# Save environment variables for next step
142+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
143+
echo "MEND_APP=${MEND_APP}" >> $GITHUB_ENV
144+
echo "MEND_PROJ=${MEND_PROJ}" >> $GITHUB_ENV
145+
echo "MEND_SAST_TARGET_DIRECTORY=${MEND_SAST_TARGET_DIRECTORY}" >> $GITHUB_ENV
146+
echo "cli-args=${mend_args[*]}" >> $GITHUB_OUTPUT
147+
148+
- name: Upload SARIF Report
149+
if: ${{ inputs.github-upload == 'true' && steps.scan.outputs.mend-exit != '' }}
150+
uses: github/codeql-action/upload-sarif@v4
151+
with:
152+
sarif_file: 'mend.sarif'
153+
category: 'Mend SAST'
154+
155+
- name: Process Scan Results
156+
shell: bash
157+
env:
158+
CLI_ARGS: "`${{ steps.scan.outputs.cli-args }}`"
159+
SCAN_EXIT_CODE: "${{ steps.scan.outputs.mend-exit }}"
160+
run: |
161+
echo "## 🔍 Mend SAST Scan Results" >> $GITHUB_STEP_SUMMARY
162+
echo "" >> $GITHUB_STEP_SUMMARY
163+
echo "**Application:** ${MEND_APP}" >> $GITHUB_STEP_SUMMARY
164+
echo "**Project:** ${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
165+
echo "**Source Path:** ${MEND_SAST_TARGET_DIRECTORY}" >> $GITHUB_STEP_SUMMARY
166+
echo "**Run Arguments:** ${CLI_ARGS}" >> $GITHUB_STEP_SUMMARY
167+
echo "" >> $GITHUB_STEP_SUMMARY
168+
169+
case $SCAN_EXIT_CODE in
170+
0)
171+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
172+
echo "Scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
173+
;;
174+
1)
175+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
176+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
177+
;;
178+
2)
179+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
180+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
181+
;;
182+
4)
183+
echo "### ❌ Unsupported Language" >> $GITHUB_STEP_SUMMARY
184+
echo "Unable to detect a supported language within the project based on the file extensions provided." >> $GITHUB_STEP_SUMMARY
185+
;;
186+
7)
187+
echo "### ❌ Permission Error" >> $GITHUB_STEP_SUMMARY
188+
echo "Could not create a cache subdirectory. Check that the Mend CLI permissions include 'create'." >> $GITHUB_STEP_SUMMARY
189+
;;
190+
9)
191+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
192+
echo "Results contain too many vulnerabilities, which contravenes the defined policy." >> $GITHUB_STEP_SUMMARY
193+
echo "" >> $GITHUB_STEP_SUMMARY
194+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
195+
;;
196+
10)
197+
echo "### ❌ Scanning Engine Failure" >> $GITHUB_STEP_SUMMARY
198+
echo "A scanning engine stalled or failed." >> $GITHUB_STEP_SUMMARY
199+
;;
200+
*)
201+
echo "### ❌ Unknown Error" >> $GITHUB_STEP_SUMMARY
202+
echo "Scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
203+
;;
204+
esac
205+
206+
exit $SCAN_EXIT_CODE

mend-sca/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Mend Dependency Scan Action
2+
3+
## Usage
4+
5+
```yaml
6+
- uses: vespa-engine/gh-actions/mend-sca-scan@main
7+
with:
8+
mend-app-name: "vespa-engine"
9+
mend-project-name: "MyProject" # Optional
10+
mend-api-key: ${{ secrets.MEND_API_KEY }}
11+
mend-email: ${{ secrets.MEND_EMAIL }}
12+
```

0 commit comments

Comments
 (0)