-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnode-security-dependency-scan.yml
More file actions
192 lines (172 loc) · 7.03 KB
/
Copy pathnode-security-dependency-scan.yml
File metadata and controls
192 lines (172 loc) · 7.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# Reporting-only workflow: it surfaces dependency vulnerabilities as
# annotations and a job summary, but never fails the build. Findings are
# meant to be reviewed, not to block PRs.
#
# npm-audit and owasp-dependency-check run as separate jobs so the slow
# OWASP/NVD pass does not hold up the fast npm audit feedback.
name: Node.js Security Vulnerability Scanning
on:
workflow_call:
inputs:
project_name:
description: "Project name for OWASP Dependency Check"
required: false
default: "node-project"
type: string
node_version:
description: "Node.js version to use"
required: false
default: "18"
type: string
fail_on_cvss:
description: "CVSS threshold passed to OWASP Dependency Check. Reporting only - it does not fail the build."
required: false
default: "7"
type: string
enable_retired:
description: "Enable retired CVE checks"
required: false
default: true
type: boolean
audit_level:
description: "Threshold passed to `npm audit`. Reporting only - it does not fail the build."
required: false
default: "moderate"
type: string
run_owasp:
description: "Run the OWASP Dependency Check job. It is slow (NVD download); npm audit alone covers most npm cases."
required: false
default: true
type: boolean
continue_on_error:
description: "Deprecated and ignored. This workflow no longer fails the build, so there is nothing to continue past. Kept so existing callers stay valid."
required: false
default: false
type: boolean
permissions:
contents: read
jobs:
npm-audit:
name: npm audit
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node_version }}
cache: "npm"
- name: Install dependencies
run: npm ci
- name: Run npm audit
run: npm audit --audit-level=${{ inputs.audit_level }}
continue-on-error: true
- name: Report vulnerabilities
if: always()
run: |
# Counts come from .metadata.vulnerabilities, which is the only
# part of the JSON that is actually broken down by severity.
# (--audit-level affects npm's exit code, not its --json output,
# so filtering on it here would report every finding as "high".)
AUDIT_JSON=$(npm audit --json 2>/dev/null || true)
if [ -z "$AUDIT_JSON" ]; then
echo "::warning title=npm audit::Could not run npm audit - skipping vulnerability report"
exit 0
fi
get() { echo "$AUDIT_JSON" | jq -r ".metadata.vulnerabilities.$1 // 0"; }
CRITICAL=$(get critical); HIGH=$(get high); MODERATE=$(get moderate)
LOW=$(get low); INFO=$(get info); TOTAL=$(get total)
{
echo "## Dependency vulnerability report"
echo ""
echo "| Severity | Count |"
echo "| --- | --- |"
echo "| Critical | $CRITICAL |"
echo "| High | $HIGH |"
echo "| Moderate | $MODERATE |"
echo "| Low | $LOW |"
echo "| Info | $INFO |"
echo "| **Total** | **$TOTAL** |"
} >> "$GITHUB_STEP_SUMMARY"
if [ "$TOTAL" -eq 0 ]; then
echo "✅ No vulnerabilities detected"
exit 0
fi
echo "Vulnerability details:"
npm audit || true
# Advisory-level detail, so the annotation names the offending package.
echo "$AUDIT_JSON" \
| jq -r '.vulnerabilities | to_entries[]
| select(.value.severity == "critical" or .value.severity == "high")
| "::warning title=Vulnerable dependency (\(.value.severity))::\(.key) \(.value.range // "")"' \
|| true
if [ "$CRITICAL" -gt 0 ] || [ "$HIGH" -gt 0 ]; then
echo "::warning title=Dependency scan::$CRITICAL critical and $HIGH high severity vulnerabilities found. This does not block the build - please review and fix where possible."
else
echo "::warning title=Dependency scan::$TOTAL vulnerabilities found ($MODERATE moderate, $LOW low). This does not block the build."
fi
# Reporting only - always succeed.
exit 0
# Verifies packages against npm's signed registry attestations, which
# catches tampering/typosquat republishing that a CVE feed cannot.
- name: Verify package provenance
if: always()
continue-on-error: true
run: |
OUT=$(npm audit signatures 2>&1) || true
echo "$OUT"
if echo "$OUT" | grep -qiE "invalid|missing.*signature"; then
echo "::warning title=Package provenance::npm audit signatures reported invalid or missing signatures - see log."
fi
owasp-dependency-check:
name: OWASP Dependency Check
runs-on: ubuntu-latest
if: ${{ inputs.run_owasp }}
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v4
# Pinned to a SHA rather than @main. This is a third-party Docker
# action running inside our security pipeline; a mutable ref there is
# exactly the supply-chain exposure this workflow exists to catch.
#
# Note: the action invokes dependency-check with --noupdate, so the CVE
# database is whatever was baked into its image - it does not fetch the
# current NVD feed at runtime. Treat its output as a secondary signal;
# npm audit is the authoritative source for npm packages.
- name: Run OWASP Dependency Check
uses: dependency-check/Dependency-Check_Action@75ba02d6183445fe0761d26e836bde58b1560600 # 1.1.0
continue-on-error: true
with:
project: ${{ inputs.project_name }}
path: "."
format: "HTML"
out: "reports"
args: >
--failOnCVSS ${{ inputs.fail_on_cvss }}
--enableRetired ${{ inputs.enable_retired }}
- name: Upload OWASP report
uses: actions/upload-artifact@v4
if: always()
with:
name: owasp-dependency-check-report
path: reports/
retention-days: 30
- name: Report OWASP result
if: always()
run: |
if [ -d reports ] && [ -n "$(ls -A reports 2>/dev/null)" ]; then
echo "::warning title=OWASP Dependency Check::Report generated - download the 'owasp-dependency-check-report' artifact to review findings."
{
echo "## OWASP Dependency Check"
echo ""
echo "Report available as the \`owasp-dependency-check-report\` artifact."
} >> "$GITHUB_STEP_SUMMARY"
else
echo "::warning title=OWASP Dependency Check::No report was produced - the scan may not have completed."
fi
exit 0