Skip to content

Commit 7c2002f

Browse files
shortstackedidirouhab
authored andcommitted
ci: Cat 1072 security nightly cleanup (#17983)
1 parent 6f240ab commit 7c2002f

File tree

2 files changed

+175
-91
lines changed

2 files changed

+175
-91
lines changed

.github/workflows/security-trivy-scan-callable.yml

Lines changed: 174 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -4,123 +4,206 @@ on:
44
workflow_dispatch:
55
inputs:
66
image_ref:
7-
description: Full image reference to scan e.g ghcr.io/n8n-io/n8n:latest
7+
description: 'Full image reference to scan e.g. ghcr.io/n8n-io/n8n:latest'
88
required: true
99
default: 'ghcr.io/n8n-io/n8n:latest'
1010
workflow_call:
1111
inputs:
1212
image_ref:
1313
type: string
14-
description: Full image reference to scan e.g ghcr.io/n8n-io/n8n:latest
14+
description: 'Full image reference to scan e.g. ghcr.io/n8n-io/n8n:latest'
1515
required: true
1616
secrets:
17-
SLACK_WEBHOOK_URL:
17+
SLACK_BOT_TOKEN:
1818
required: true
1919

20+
permissions:
21+
contents: read
22+
23+
env:
24+
SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
25+
SLACK_CHANNEL_ID: C042WDXPTEZ #mission-security
26+
2027
jobs:
2128
security_scan:
29+
name: Security - Scan Docker Image With Trivy
2230
runs-on: ubuntu-latest
2331
steps:
24-
- name: Checkout code
25-
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
26-
27-
- name: Pull Docker image with retry
28-
run: |
29-
for i in {1..4}; do
30-
docker pull "${{ inputs.image_ref }}" && break
31-
[ $i -lt 4 ] && echo "Retry $i failed, waiting..." && sleep 15
32-
done
33-
3432
- name: Run Trivy vulnerability scanner
35-
uses: aquasecurity/trivy-action@dc5a429b52fcf669ce959baa2c2dd26090d2a6c4 # v0.30.0
33+
uses: aquasecurity/trivy-action@dc5a429b52fcf669ce959baa2c2dd26090d2a6c4 # v0.32.0
34+
id: trivy_scan
3635
with:
3736
image-ref: ${{ inputs.image_ref }}
3837
format: 'json'
3938
output: 'trivy-results.json'
40-
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
41-
vuln-type: 'os,library'
39+
severity: 'CRITICAL,HIGH'
4240
ignore-unfixed: false
4341
exit-code: '0'
4442

45-
- name: Process vulnerability results
46-
id: process_vulns
43+
- name: Calculate vulnerability counts
44+
id: process_results
4745
run: |
48-
if [ -f trivy-results.json ]; then
49-
CRITICAL_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length' trivy-results.json)
50-
HIGH_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length' trivy-results.json)
51-
MEDIUM_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' trivy-results.json)
52-
LOW_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW")] | length' trivy-results.json)
53-
54-
TOTAL_VULNS=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT))
55-
56-
echo "critical_count=${CRITICAL_COUNT}" >> $GITHUB_OUTPUT
57-
echo "high_count=${HIGH_COUNT}" >> $GITHUB_OUTPUT
58-
echo "medium_count=${MEDIUM_COUNT}" >> $GITHUB_OUTPUT
59-
echo "low_count=${LOW_COUNT}" >> $GITHUB_OUTPUT
60-
echo "total_count=${TOTAL_VULNS}" >> $GITHUB_OUTPUT
61-
62-
if [ $TOTAL_VULNS -gt 0 ]; then
63-
echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
64-
65-
# Extract top vulnerabilities for display (limit to 10 for readability)
66-
TOP_VULNS=$(jq -r '
67-
.Results[]?
68-
| .Vulnerabilities[]?
69-
| select(.Severity == "CRITICAL" or .Severity == "HIGH" or .Severity == "MEDIUM" or .Severity == "LOW")
70-
| "• \(.VulnerabilityID): \(.Title // "No title") (\(.Severity))"
71-
' trivy-results.json | head -10)
72-
73-
echo "top_vulnerabilities<<EOF" >> $GITHUB_OUTPUT
74-
echo "$TOP_VULNS" >> $GITHUB_OUTPUT
75-
echo "EOF" >> $GITHUB_OUTPUT
76-
else
77-
echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT
78-
fi
79-
else
80-
echo "Trivy results file not found."
46+
if [ ! -s trivy-results.json ] || [ $(jq '.Results | length' trivy-results.json) -eq 0 ]; then
47+
echo "No high-severity vulnerabilities found."
8148
echo "vulnerabilities_found=false" >> $GITHUB_OUTPUT
49+
exit 0
8250
fi
8351
84-
- name: Notify Slack - Vulnerabilities Found
85-
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
86-
if: steps.process_vulns.outputs.vulnerabilities_found == 'true'
87-
with:
88-
status: 'warning'
89-
channel: '#mission-security'
90-
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
91-
message: |
92-
:warning: *Trivy Scan: Vulnerabilities Detected*
93-
94-
*Repository:* `${{ github.repository }}`
95-
*Image Ref:* `${{ inputs.image_ref }}`
96-
97-
*Vulnerability Summary:*
98-
• *Critical:* ${{ steps.process_vulns.outputs.critical_count }}
99-
• *High:* ${{ steps.process_vulns.outputs.high_count }}
100-
• *Medium:* ${{ steps.process_vulns.outputs.medium_count }}
101-
• *Low:* ${{ steps.process_vulns.outputs.low_count }}
102-
• *Total:* ${{ steps.process_vulns.outputs.total_count }}
103-
104-
*Top Vulnerabilities (showing first 10):*
105-
```
106-
${{ steps.process_vulns.outputs.top_vulnerabilities }}
107-
```
108-
109-
:point_right: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Full Scan Results>
110-
111-
- name: Notify Slack - No Vulnerabilities
112-
uses: act10ns/slack@44541246747a30eb3102d87f7a4cc5471b0ffb7d # v2.1.0
113-
if: steps.process_vulns.outputs.vulnerabilities_found == 'false'
114-
with:
115-
status: 'success'
116-
channel: '#mission-security'
117-
webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
118-
message: |
119-
:white_check_mark: *Trivy Scan: All Clear*
52+
# Calculate counts by severity
53+
CRITICAL_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "CRITICAL")] | length)' trivy-results.json)
54+
HIGH_COUNT=$(jq '([.Results[]?.Vulnerabilities[]? | select(.Severity == "HIGH")] | length)' trivy-results.json)
55+
TOTAL_VULNS=$((CRITICAL_COUNT + HIGH_COUNT))
12056
121-
*Repository:* `${{ github.repository }}`
122-
*Image Ref:* `${{ inputs.image_ref }}`
57+
# Get unique CVE count
58+
UNIQUE_CVES=$(jq -r '[.Results[]?.Vulnerabilities[]?.VulnerabilityID] | unique | length' trivy-results.json)
12359
124-
No vulnerabilities detected in the Docker image scan.
60+
# Get affected packages count
61+
AFFECTED_PACKAGES=$(jq -r '[.Results[]?.Vulnerabilities[]? | .PkgName] | unique | length' trivy-results.json)
12562
126-
:point_right: <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Scan Results>
63+
echo "vulnerabilities_found=$( [ $TOTAL_VULNS -gt 0 ] && echo 'true' || echo 'false' )" >> $GITHUB_OUTPUT
64+
echo "total_count=$TOTAL_VULNS" >> $GITHUB_OUTPUT
65+
echo "critical_count=$CRITICAL_COUNT" >> $GITHUB_OUTPUT
66+
echo "high_count=$HIGH_COUNT" >> $GITHUB_OUTPUT
67+
echo "unique_cves=$UNIQUE_CVES" >> $GITHUB_OUTPUT
68+
echo "affected_packages=$AFFECTED_PACKAGES" >> $GITHUB_OUTPUT
69+
70+
- name: Generate GitHub Job Summary
71+
if: always()
72+
run: |
73+
{
74+
echo "# 🛡️ Trivy Security Scan Results"
75+
echo ""
76+
echo "**Image:** \`${{ inputs.image_ref }}\`"
77+
echo "**Scan Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
78+
echo ""
79+
} >> $GITHUB_STEP_SUMMARY
80+
81+
if [ "${{ steps.process_results.outputs.vulnerabilities_found }}" == "false" ]; then
82+
echo "✅ **No critical or high severity vulnerabilities found!**" >> $GITHUB_STEP_SUMMARY
83+
else
84+
{
85+
echo "## 📊 Summary"
86+
echo "| Metric | Count |"
87+
echo "|--------|-------|"
88+
echo "| 🔴 Critical Vulnerabilities | ${{ steps.process_results.outputs.critical_count }} |"
89+
echo "| 🟠 High Vulnerabilities | ${{ steps.process_results.outputs.high_count }} |"
90+
echo "| 📋 Unique CVEs | ${{ steps.process_results.outputs.unique_cves }} |"
91+
echo "| 📦 Affected Packages | ${{ steps.process_results.outputs.affected_packages }} |"
92+
echo ""
93+
echo "## 🚨 Top Vulnerabilities"
94+
echo ""
95+
} >> $GITHUB_STEP_SUMMARY
96+
97+
# Generate detailed vulnerability table
98+
jq -r --arg image_ref "${{ inputs.image_ref }}" '
99+
# Collect all vulnerabilities
100+
[.Results[] | select(.Vulnerabilities != null) | .Vulnerabilities[]] |
101+
# Group by CVE ID to avoid duplicates
102+
group_by(.VulnerabilityID) |
103+
map({
104+
cve: .[0].VulnerabilityID,
105+
severity: .[0].Severity,
106+
cvss: (.[0].CVSS.nvd.V3Score // "N/A"),
107+
cvss_sort: (.[0].CVSS.nvd.V3Score // 0),
108+
packages: [.[] | "\(.PkgName)@\(.InstalledVersion)"] | unique | join(", "),
109+
fixed: (.[0].FixedVersion // "No fix available"),
110+
description: (.[0].Description // "No description available") | split("\n")[0] | .[0:150]
111+
}) |
112+
# Sort by severity (CRITICAL first) and CVSS score
113+
sort_by((.severity == "HIGH" | if . then 1 else 0 end), -.cvss_sort) |
114+
# Take top 15
115+
.[:15] |
116+
# Generate markdown table
117+
"| CVE | Severity | CVSS | Package(s) | Fix Version | Description |",
118+
"|-----|----------|------|------------|-------------|-------------|",
119+
(.[] | "| [\(.cve)](https://nvd.nist.gov/vuln/detail/\(.cve)) | \(.severity) | \(.cvss) | `\(.packages)` | `\(.fixed)` | \(.description) |")
120+
' trivy-results.json >> $GITHUB_STEP_SUMMARY
121+
122+
{
123+
echo ""
124+
echo "---"
125+
echo "🔍 **View detailed logs above for full analysis**"
126+
} >> $GITHUB_STEP_SUMMARY
127+
fi
128+
129+
- name: Generate Slack Blocks JSON
130+
if: steps.process_results.outputs.vulnerabilities_found == 'true'
131+
id: generate_blocks
132+
run: |
133+
BLOCKS_JSON=$(jq -c --arg image_ref "${{ inputs.image_ref }}" \
134+
--arg repo_url "${{ github.server_url }}/${{ github.repository }}" \
135+
--arg repo_name "${{ github.repository }}" \
136+
--arg run_url "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" \
137+
--arg critical_count "${{ steps.process_results.outputs.critical_count }}" \
138+
--arg high_count "${{ steps.process_results.outputs.high_count }}" \
139+
--arg unique_cves "${{ steps.process_results.outputs.unique_cves }}" \
140+
'
141+
# Function to create a vulnerability block with emoji indicators
142+
def vuln_block: {
143+
"type": "section",
144+
"text": {
145+
"type": "mrkdwn",
146+
"text": "\(if .Severity == "CRITICAL" then ":red_circle:" else ":large_orange_circle:" end) *<https://nvd.nist.gov/vuln/detail/\(.VulnerabilityID)|\(.VulnerabilityID)>* (CVSS: `\(.CVSS.nvd.V3Score // "N/A")`)\n*Package:* `\(.PkgName)@\(.InstalledVersion)` → `\(.FixedVersion // "No fix available")`"
147+
}
148+
};
149+
150+
# Main structure
151+
[
152+
{
153+
"type": "header",
154+
"text": { "type": "plain_text", "text": ":warning: Trivy Scan: Vulnerabilities Detected" }
155+
},
156+
{
157+
"type": "section",
158+
"fields": [
159+
{ "type": "mrkdwn", "text": "*Repository:*\n<\($repo_url)|\($repo_name)>" },
160+
{ "type": "mrkdwn", "text": "*Image:*\n`\($image_ref)`" },
161+
{ "type": "mrkdwn", "text": "*Critical:*\n:red_circle: \($critical_count)" },
162+
{ "type": "mrkdwn", "text": "*High:*\n:large_orange_circle: \($high_count)" }
163+
]
164+
},
165+
{
166+
"type": "context",
167+
"elements": [
168+
{ "type": "mrkdwn", "text": ":shield: \($unique_cves) unique CVEs affecting packages" }
169+
]
170+
},
171+
{ "type": "divider" }
172+
] +
173+
(
174+
# Group vulnerabilities by CVE to avoid duplicates in notification
175+
[.Results[] | select(.Vulnerabilities != null) | .Vulnerabilities[]] |
176+
group_by(.VulnerabilityID) |
177+
map(.[0]) |
178+
sort_by((.Severity == "HIGH" | if . then 1 else 0 end), -((.CVSS.nvd.V3Score // 0) | tonumber? // 0)) |
179+
.[:8] |
180+
map(. | vuln_block)
181+
) +
182+
[
183+
{ "type": "divider" },
184+
{
185+
"type": "actions",
186+
"elements": [
187+
{
188+
"type": "button",
189+
"text": { "type": "plain_text", "text": ":github: View Full Report" },
190+
"style": "primary",
191+
"url": $run_url
192+
}
193+
]
194+
}
195+
]
196+
' trivy-results.json)
197+
198+
echo "slack_blocks=$BLOCKS_JSON" >> $GITHUB_OUTPUT
199+
200+
- name: Send Slack Notification
201+
if: steps.process_results.outputs.vulnerabilities_found == 'true'
202+
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
203+
with:
204+
method: chat.postMessage
205+
token: ${{ secrets.SLACK_BOT_TOKEN }}
206+
payload: |
207+
channel: ${{ env.SLACK_CHANNEL_ID }}
208+
text: "🚨 Trivy Scan: ${{ steps.process_results.outputs.critical_count }} Critical, ${{ steps.process_results.outputs.high_count }} High vulnerabilities found in ${{ inputs.image_ref }}"
209+
blocks: ${{ steps.generate_blocks.outputs.slack_blocks }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,4 @@ compiled_app_output
3535
trivy_report*
3636
compiled
3737
packages/cli/src/modules/my-feature
38+
.secrets

0 commit comments

Comments
 (0)