Skip to content

Commit 7fe819f

Browse files
Split each scan into report + gate steps for reliable pipeline gating
Each job (SCA, SAST) now runs the scan twice: 1. Report scan: --json output saved to file, converted to HTML via snyk-to-html, uploaded as artifact. Always succeeds (|| true) so the HTML report is available regardless of findings. 2. Gating scan: plain CLI output (no --json, no piping) so the native exit code determines pass/fail without snyk-to-html interference. SCA gates with --severity-threshold=high --fail-on=all. SAST gates with --severity-threshold=medium (no --fail-on support). Made-with: Cursor
1 parent dce7d54 commit 7fe819f

1 file changed

Lines changed: 98 additions & 101 deletions

File tree

.github/workflows/snyk-sca-sast-demo.yml

Lines changed: 98 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,21 @@
1212
# 2. SAST (Static Application Security Testing) - Scans your first-party
1313
# source code for security issues like SQL injection, XSS, and more.
1414
#
15-
# Both jobs generate a downloadable HTML report and send results to the
16-
# Snyk Dashboard for continuous monitoring.
15+
# Each job runs TWO scans:
16+
# a) A report scan — saves JSON results, converts them to an HTML report
17+
# via "npx snyk-to-html", and uploads the report as a downloadable
18+
# GitHub Actions artifact. This scan always succeeds (|| true) so the
19+
# report is available regardless of findings.
20+
# b) A gating scan — runs the plain Snyk CLI (no --json, no piping) so
21+
# the native exit code determines pass/fail. This is what actually
22+
# blocks the pipeline when vulnerabilities are found.
23+
#
24+
# HOW GATING WORKS:
25+
# --severity-threshold controls which vulnerabilities appear in the REPORT.
26+
# It does NOT control the exit code on its own.
27+
# --fail-on controls when "snyk test" (SCA) actually exits
28+
# non-zero. Valid values: all | upgradable | patchable.
29+
# "snyk code test" (SAST) does not support --fail-on.
1730
#
1831
# PREREQUISITES:
1932
# 1. A Snyk account (free tier works) — https://app.snyk.io
@@ -25,18 +38,21 @@
2538
# Value: <your Snyk API token>
2639
#
2740
# CUSTOMIZATION:
28-
# - --severity-threshold controls which vulnerabilities appear in the REPORT.
41+
# - Adjust --severity-threshold to control what appears in reports.
2942
# Valid values: low | medium | high | critical
30-
# - --fail-on controls when the build actually FAILS (SCA only).
43+
# - Adjust --fail-on (SCA only) to control when the build fails.
3144
# Valid values:
3245
# all — Fail on any vuln that can be upgraded or patched.
3346
# upgradable — Fail only on vulns that can be upgraded.
3447
# patchable — Fail only on vulns that can be patched.
3548
# - Replace the --org value with your own Snyk Organization ID.
3649
# - Replace --project-name values to match your project naming convention.
3750
# - Uncomment the pull_request trigger to gate PRs before merge.
38-
# - To add Container or IaC scanning, see the Snyk Actions docs:
51+
# - To add Container or IaC scanning, see:
3952
# https://github.com/snyk/actions
53+
#
54+
# REFERENCE:
55+
# https://docs.snyk.io/developer-tools/snyk-cli/scan-and-maintain-projects-using-the-cli/failing-of-builds-in-snyk-cli
4056
# ============================================================================
4157

4258
name: Snyk SCA and SAST Security Pipeline
@@ -64,122 +80,112 @@ jobs:
6480
# JOB 1: SCA — Snyk Open Source Scan
6581
# ==========================================================================
6682
# Scans your dependency tree (package.json / package-lock.json) against the
67-
# Snyk vulnerability database. This catches known CVEs in third-party
68-
# packages before they reach production.
83+
# Snyk vulnerability database. Catches known CVEs in third-party packages.
6984
#
70-
# Gating policy:
71-
# --severity-threshold=high → Report only high/critical vulnerabilities.
72-
# --fail-on=all → FAIL the build when any reported vuln has
73-
# an available fix (upgrade or patch).
85+
# This job runs TWO scans:
86+
# 1. Report scan → --json output saved to file, converted to HTML,
87+
# uploaded as an artifact. Always succeeds.
88+
# 2. Gating scan → plain CLI output, uses --fail-on=all to exit non-zero
89+
# when fixable high/critical vulnerabilities exist.
7490
# ==========================================================================
7591
snyk-sca-scan:
7692
name: SCA - Snyk Open Source Scan
7793
runs-on: ubuntu-latest
7894

79-
# "contents: read" — needed to check out your code
80-
# "security-events: write" — needed if you later add SARIF upload to
81-
# GitHub's Security tab (optional enhancement)
8295
permissions:
8396
contents: read
8497
security-events: write
8598

8699
steps:
87-
# -- Checkout the repository so Snyk can access your code and manifests --
88100
- name: Checkout code
89101
uses: actions/checkout@v4
90102

91-
# -- Set up the Node.js runtime (required for npm install) --
92-
# Change the version to match your project's requirements.
103+
# Change node-version to match your project's requirements.
93104
- name: Setup Node.js
94105
uses: actions/setup-node@v4
95106
with:
96107
node-version: '18'
97108

98-
# -- Install dependencies so Snyk can resolve the full dependency tree --
99-
# Snyk needs the node_modules / lock file to accurately identify
100-
# transitive dependencies and their versions.
109+
# Snyk needs node_modules to resolve the full dependency tree.
101110
- name: Install dependencies
102111
run: npm install
103112

104-
# -- Install the Snyk CLI using the official GitHub Action --
105113
- name: Setup Snyk CLI
106114
uses: snyk/actions/setup@master
107115

108-
# -- Authenticate the CLI with your Snyk API token --
109-
# The SNYK_TOKEN secret must be configured in your repository settings.
116+
# SNYK_TOKEN must be set in: Repo > Settings > Secrets > Actions
110117
- name: Authenticate Snyk
111118
run: snyk auth ${{ secrets.SNYK_TOKEN }}
112119
env:
113120
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
114121

115-
# -- Run the SCA scan and save JSON results to a file --
116-
# Flags explained:
117-
# --severity-threshold=high Report only high or critical findings.
118-
# NOTE: This only filters the report output.
119-
# It does NOT cause the CLI to exit non-zero.
120-
# --fail-on=all FAIL the build when any reported vuln
121-
# has an available fix (upgrade or patch).
122-
# This flag controls the actual exit code.
123-
# --json Output results as JSON (saved to file).
124-
# --report Also send results to the Snyk Dashboard.
125-
# --org Your Snyk Organization ID.
126-
# --project-name Name shown in the Snyk Dashboard.
127-
# --target-reference Tags results with the branch/tag name.
128-
#
129-
# JSON is saved to a file (not piped) so we can reliably capture the
130-
# exit code from "snyk test --fail-on=all" and still generate the HTML
131-
# report in a separate step.
132-
- name: Snyk Open Source Test (Gate on High/Critical)
133-
id: sca-scan
134-
continue-on-error: true
122+
# ----------------------------------------------------------------------
123+
# SCAN 1: Generate the HTML report (always succeeds)
124+
# ----------------------------------------------------------------------
125+
# Runs snyk test with --json and saves output to a file. The "|| true"
126+
# ensures this step always passes — its only purpose is to capture
127+
# results for the HTML report and send them to the Snyk Dashboard.
128+
# --report sends results to the Snyk Dashboard for continuous monitoring.
129+
- name: "Report: Run SCA scan and save JSON results"
135130
run: |
136131
snyk test \
137132
--severity-threshold=high \
138-
--fail-on=all \
139133
--json \
140134
--report \
141135
--org=2c2549f7-de55-4c31-aaea-bea685244487 \
142136
--project-name="nodejs-goof-sca" \
143-
--target-reference=${{ github.ref_name }} > snyk-sca-results.json
137+
--target-reference=${{ github.ref_name }} > snyk-sca-results.json || true
144138
env:
145139
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
146140

147-
# -- Generate the HTML report from JSON results --
148-
- name: Generate SCA HTML Report
141+
# Convert the JSON results to a human-readable HTML report using
142+
# the Snyk-to-HTML tool (https://github.com/snyk/snyk-to-html).
143+
- name: "Report: Generate HTML from JSON"
149144
if: always()
150145
run: npx snyk-to-html -i snyk-sca-results.json -o snyk-sca-report.html
151146

152-
# -- Upload the HTML report as a downloadable artifact --
153-
# "if: always()" ensures the report is uploaded whether the scan
154-
# passed or failed. Find it in the Actions run under "Artifacts".
155-
- name: Upload SCA HTML Report
147+
# Upload the HTML report as a downloadable artifact.
148+
# Find it in the Actions run under the "Artifacts" section.
149+
- name: "Report: Upload SCA HTML report"
156150
if: always()
157151
uses: actions/upload-artifact@v4
158152
with:
159153
name: snyk-sca-report
160154
path: snyk-sca-report.html
161155

162-
# -- Enforce the gate: fail the job if vulnerabilities were found --
163-
# --fail-on=all causes "snyk test" to exit non-zero when fixable
164-
# high/critical vulns exist. This step re-asserts that exit code
165-
# after the report has been uploaded.
166-
- name: Evaluate SCA scan result
167-
if: steps.sca-scan.outcome == 'failure'
168-
run: exit 1
156+
# ----------------------------------------------------------------------
157+
# SCAN 2: Gate the pipeline (actually fails the build)
158+
# ----------------------------------------------------------------------
159+
# Runs snyk test with plain CLI output (no --json, no piping) so the
160+
# native exit code is used directly.
161+
#
162+
# --severity-threshold=high → Report only high/critical vulnerabilities.
163+
# --fail-on=all → Exit non-zero when any reported vuln has
164+
# an available fix (upgrade or patch).
165+
# This is what actually gates the pipeline.
166+
#
167+
# Without --fail-on, --severity-threshold only filters the display.
168+
# See: https://docs.snyk.io/developer-tools/snyk-cli/scan-and-maintain-projects-using-the-cli/failing-of-builds-in-snyk-cli
169+
- name: "Gate: SCA scan (fail on high/critical with available fix)"
170+
run: |
171+
snyk test \
172+
--severity-threshold=high \
173+
--fail-on=all
174+
env:
175+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
169176

170177
# ==========================================================================
171178
# JOB 2: SAST — Snyk Code Scan
172179
# ==========================================================================
173180
# Scans your first-party source code for security vulnerabilities using
174181
# Snyk's static analysis engine. Catches issues like SQL injection, XSS,
175-
# path traversal, hardcoded secrets, and more — directly in your code.
176-
#
177-
# Gating policy: FAILS the pipeline on MEDIUM or higher severity issues.
182+
# path traversal, hardcoded secrets, and more.
178183
#
179-
# NOTE: "snyk code test" does NOT support --fail-on, and
180-
# --severity-threshold only filters the report — it does not affect
181-
# the exit code. To enforce a gate, the scan saves JSON to a file and
182-
# a separate step parses it with jq to check for findings.
184+
# This job runs TWO scans:
185+
# 1. Report scan → --json output saved to file, converted to HTML,
186+
# uploaded as an artifact. Always succeeds.
187+
# 2. Gating scan → plain CLI output. "snyk code test" does NOT support
188+
# --fail-on, so the exit code is checked directly.
183189
# ==========================================================================
184190
snyk-code-scan:
185191
name: SAST - Snyk Code Scan
@@ -190,36 +196,25 @@ jobs:
190196
security-events: write
191197

192198
steps:
193-
# -- Checkout the repository --
194199
- name: Checkout code
195200
uses: actions/checkout@v4
196201

197-
# -- Install the Snyk CLI --
198-
# Note: SAST does not require "npm install" — Snyk Code analyzes
199-
# source files directly without needing to build the project.
202+
# SAST does not require "npm install" — Snyk Code analyzes source
203+
# files directly without needing to build the project.
200204
- name: Setup Snyk CLI
201205
uses: snyk/actions/setup@master
202206

203-
# -- Authenticate the CLI --
204207
- name: Authenticate Snyk
205208
run: snyk auth ${{ secrets.SNYK_TOKEN }}
206209
env:
207210
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
208211

209-
# -- Run the SAST scan and save JSON results to a file --
210-
# Flags explained:
211-
# --severity-threshold=medium Report medium, high, and critical findings.
212-
# NOTE: This only filters the report output.
213-
# It does NOT cause the CLI to exit non-zero.
214-
# --json Output results as JSON (saved to file).
215-
# --report Also send results to the Snyk Dashboard.
216-
# --org Your Snyk Organization ID.
217-
# --project-name Name shown in the Snyk Dashboard.
218-
#
219-
# Unlike "snyk test" (SCA), "snyk code test" does NOT support --fail-on,
220-
# so we save JSON to a file and check for findings explicitly in a
221-
# later step to enforce the gate.
222-
- name: Snyk Code Test
212+
# ----------------------------------------------------------------------
213+
# SCAN 1: Generate the HTML report (always succeeds)
214+
# ----------------------------------------------------------------------
215+
# Runs snyk code test with --json and saves output to a file.
216+
# --report sends results to the Snyk Dashboard.
217+
- name: "Report: Run SAST scan and save JSON results"
223218
run: |
224219
snyk code test \
225220
--severity-threshold=medium \
@@ -230,42 +225,44 @@ jobs:
230225
env:
231226
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
232227

233-
# -- Generate the HTML report from JSON results --
234-
- name: Generate SAST HTML Report
228+
- name: "Report: Generate HTML from JSON"
235229
if: always()
236230
run: npx snyk-to-html -i snyk-sast-results.json -o snyk-sast-report.html
237231

238-
# -- Upload the SAST HTML report as a downloadable artifact --
239-
- name: Upload SAST HTML Report
232+
- name: "Report: Upload SAST HTML report"
240233
if: always()
241234
uses: actions/upload-artifact@v4
242235
with:
243236
name: snyk-sast-report
244237
path: snyk-sast-report.html
245238

246-
# -- Enforce the gate: fail if medium+ severity SAST issues exist --
247-
# Since --severity-threshold only filters the report and does not
248-
# affect the exit code, we parse the JSON output with jq to check
249-
# for findings. If any results are present (medium+), the step
250-
# exits non-zero, failing the job.
251-
- name: Evaluate SAST scan result (Gate on Medium+)
239+
# ----------------------------------------------------------------------
240+
# SCAN 2: Gate the pipeline (actually fails the build)
241+
# ----------------------------------------------------------------------
242+
# Runs snyk code test with plain CLI output (no --json, no piping).
243+
#
244+
# --severity-threshold=medium → Report medium, high, and critical issues.
245+
#
246+
# NOTE: "snyk code test" does NOT support --fail-on. The CLI exits
247+
# non-zero when any issues are found at or above the severity threshold.
248+
- name: "Gate: SAST scan (fail on medium+)"
252249
run: |
253-
FINDINGS=$(jq '[.runs[].results[]] | length' snyk-sast-results.json 2>/dev/null || echo "0")
254-
if [ "$FINDINGS" -gt 0 ]; then
255-
echo "::error::Snyk Code found $FINDINGS medium+ severity issue(s). See the HTML report artifact for details."
256-
exit 1
257-
fi
258-
echo "No medium+ severity SAST issues found."
250+
snyk code test \
251+
--severity-threshold=medium
252+
env:
253+
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
259254

260255
# ============================================================================
261256
# VIEWING RESULTS
262257
# ============================================================================
263258
# 1. GitHub Actions UI:
264259
# - Each job shows pass/fail status in the Actions tab.
265260
# - Download the HTML reports from the "Artifacts" section of any run.
261+
# - The gating scan shows plain CLI output in the step logs, making it
262+
# easy to see exactly which vulnerabilities caused the failure.
266263
#
267264
# 2. Snyk Dashboard:
268-
# - The --report flag sends results to your Snyk organization.
265+
# - The --report flag (on the report scan) sends results to your org.
269266
# - View them at: https://app.snyk.io → your org → Projects
270267
# - Look for project names: nodejs-goof-sca and nodejs-goof-sast
271268
#

0 commit comments

Comments
 (0)