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
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
4258name : 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