Skip to content

Commit 4fb0d2d

Browse files
esolitosCopilot
andcommitted
feat: add GitHub Actions for Mend CLI image, code and dependency scan
Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ece1505 commit 4fb0d2d

6 files changed

Lines changed: 530 additions & 0 deletions

File tree

mend-image/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
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-app-name: "my-app-name"
12+
mend-project-name: "my-project-name"
13+
```

mend-image/action.yml

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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 "app-name=${MEND_APP}" >> $GITHUB_OUTPUT
101+
echo "proj-name=${MEND_PROJ}" >> $GITHUB_OUTPUT
102+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
103+
echo "cli-args=${mend_args[*]}" >> $GITHUB_OUTPUT
104+
105+
- name: Process Scan Results
106+
shell: bash
107+
env:
108+
MEND_APP: "${{ steps.scan.outputs.app-name }}"
109+
MEND_PROJ: "${{ steps.scan.outputs.proj-name }}"
110+
CLI_ARGS: "${{ steps.scan.outputs.cli-args }}"
111+
SCAN_EXIT_CODE: "${{ steps.scan.outputs.mend-exit }}"
112+
run: |
113+
printf '## 🐳 Image Scan for %s//%s\n' "${MEND_APP}" "${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
114+
printf '\n\n' >> $GITHUB_STEP_SUMMARY
115+
printf '**Image URI:** `%s`\n' "${{ inputs.image-name }}" >> $GITHUB_STEP_SUMMARY
116+
printf '**Application:** %s\n' "${MEND_APP}" >> $GITHUB_STEP_SUMMARY
117+
printf '**Project:** %s\n' "${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
118+
printf '\n\n' >> $GITHUB_STEP_SUMMARY
119+
120+
case $SCAN_EXIT_CODE in
121+
0)
122+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
123+
echo "Image scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
124+
;;
125+
1)
126+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
127+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
128+
;;
129+
2)
130+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
131+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
132+
;;
133+
9)
134+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
135+
echo "Image scan results contain vulnerabilities that contravene the defined policy." >> $GITHUB_STEP_SUMMARY
136+
echo "" >> $GITHUB_STEP_SUMMARY
137+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
138+
;;
139+
*)
140+
echo "### ❌ Error" >> $GITHUB_STEP_SUMMARY
141+
echo "Image scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
142+
echo "" >> $GITHUB_STEP_SUMMARY
143+
echo "Check the Mend CLI documentation for exit code details." >> $GITHUB_STEP_SUMMARY
144+
;;
145+
esac
146+
147+
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@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-user: ${{ secrets.MEND_USER }}
12+
```

mend-sast/action.yml

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
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+
mend_args=(
102+
"--non-interactive"
103+
# Mend Organization in scope is assumed from API key
104+
"--scope" "${MEND_APP}//${MEND_PROJ}"
105+
)
106+
107+
if [[ -n "${{ inputs.scan-name }}" ]]; then
108+
mend_args+=("--name" "${{ inputs.scan-name }}")
109+
fi
110+
111+
if [[ "${{ inputs.update }}" != "true" ]]; then
112+
mend_args+=("--no-logs")
113+
fi
114+
115+
if [[ "${{ inputs.secrets-detection }}" == "true" ]]; then
116+
mend_args+=("--secrets-detection")
117+
fi
118+
119+
if [[ "${{ inputs.incremental-scan }}" == "true" ]]; then
120+
mend_args+=("--inc")
121+
fi
122+
123+
if [[ "${{ inputs.github-upload }}" == "true" ]]; then
124+
mend_args+=(
125+
"--report"
126+
# Format extension is appended by CLI
127+
"--filename" "mend"
128+
"--formats" "sarif"
129+
)
130+
fi
131+
132+
set +e
133+
mend sast "${mend_args[@]}"
134+
exit_code=$?
135+
136+
# Save exit code for next step
137+
echo "app-name=${MEND_APP}" >> $GITHUB_OUTPUT
138+
echo "proj-name=${MEND_PROJ}" >> $GITHUB_OUTPUT
139+
echo "mend-exit=$exit_code" >> $GITHUB_OUTPUT
140+
echo "cli-args=${mend_args[*]}" >> $GITHUB_OUTPUT
141+
142+
# Export options not exposed via CLI args
143+
echo "engines=${MEND_SAST_ENGINES}" >> $GITHUB_OUTPUT
144+
145+
146+
- name: Upload SARIF Report
147+
if: ${{ inputs.github-upload == 'true' && steps.scan.outputs.mend-exit != '' }}
148+
uses: github/codeql-action/upload-sarif@v4
149+
with:
150+
sarif_file: 'mend.sarif'
151+
category: 'Mend SAST'
152+
153+
- name: Process Scan Results
154+
shell: bash
155+
env:
156+
MEND_APP: "${{ steps.scan.outputs.app-name }}"
157+
MEND_PROJ: "${{ steps.scan.outputs.proj-name }}"
158+
CLI_ARGS: "${{ steps.scan.outputs.cli-args }}"
159+
SCAN_EXIT_CODE: "${{ steps.scan.outputs.mend-exit }}"
160+
161+
ENABLED_ENGINES: "${{ steps.scan.outputs.engines }}"
162+
MEND_ENGINES_DOC_URL: "https://docs.mend.io/platform/latest/configure-the-mend-cli-for-sast#ConfiguretheMendCLIforSAST-Mend-CLI-SAST-supported-languages-and-engine-IDsMendCLISAST-supportedlanguagesandengineIDs"
163+
run: |
164+
printf '## 🔍 SAST Scan for %s//%s\n' "${MEND_APP}" "${MEND_PROJ}" >> $GITHUB_STEP_SUMMARY
165+
printf '\n\n' >> $GITHUB_STEP_SUMMARY
166+
printf '**Run Arguments:** `%s`\n' "${CLI_ARGS}" >> $GITHUB_STEP_SUMMARY
167+
printf '**Enabled [Engine IDs (ref. docs)](%s):** %s\n' "${MEND_ENGINES_DOC_URL}" "${ENABLED_ENGINES}" >> $GITHUB_STEP_SUMMARY
168+
printf '\n\n' >> $GITHUB_STEP_SUMMARY
169+
170+
case "${{ steps.scan.outputs.mend-exit }}" in
171+
0)
172+
echo "### ✅ Success" >> $GITHUB_STEP_SUMMARY
173+
echo "Scan completed successfully with no policy violations." >> $GITHUB_STEP_SUMMARY
174+
;;
175+
1)
176+
echo "### ❌ Invalid Configuration" >> $GITHUB_STEP_SUMMARY
177+
echo "An invalid configuration parameter was passed. Check for typos in the parameters." >> $GITHUB_STEP_SUMMARY
178+
;;
179+
2)
180+
echo "### ❌ Connection Error" >> $GITHUB_STEP_SUMMARY
181+
echo "Unable to access the update or license details from the Mend server URL. Check internet connection." >> $GITHUB_STEP_SUMMARY
182+
;;
183+
4)
184+
echo "### ❌ Unsupported Language" >> $GITHUB_STEP_SUMMARY
185+
echo "Unable to detect a supported language within the project based on the file extensions provided." >> $GITHUB_STEP_SUMMARY
186+
;;
187+
7)
188+
echo "### ❌ Permission Error" >> $GITHUB_STEP_SUMMARY
189+
echo "Could not create a cache subdirectory. Check that the Mend CLI permissions include 'create'." >> $GITHUB_STEP_SUMMARY
190+
;;
191+
9)
192+
echo "### ⚠️ Policy Violation" >> $GITHUB_STEP_SUMMARY
193+
echo "Results contain too many vulnerabilities, which contravenes the defined policy." >> $GITHUB_STEP_SUMMARY
194+
echo "" >> $GITHUB_STEP_SUMMARY
195+
echo "Please review the findings in the Mend platform." >> $GITHUB_STEP_SUMMARY
196+
;;
197+
10)
198+
echo "### ❌ Scanning Engine Failure" >> $GITHUB_STEP_SUMMARY
199+
echo "A scanning engine stalled or failed." >> $GITHUB_STEP_SUMMARY
200+
;;
201+
*)
202+
echo "### ❌ Unknown Error" >> $GITHUB_STEP_SUMMARY
203+
echo "Scan failed with exit code: ${SCAN_EXIT_CODE}" >> $GITHUB_STEP_SUMMARY
204+
;;
205+
esac
206+
207+
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@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-user: ${{ secrets.MEND_EMAIL }}
12+
```

0 commit comments

Comments
 (0)