55 branches : [ main ]
66 pull_request :
77 branches : [ main ]
8+ schedule :
9+ - cron : ' 0 2 * * *'
10+ workflow_dispatch :
811
912permissions :
1013 contents : read
11- actions : read
12- security-events : write
1314
1415concurrency :
1516 group : ${{ github.workflow }}-${{ github.ref }}
2021 runs-on : ubuntu-latest
2122 name : Build and Validate
2223 timeout-minutes : 15
24+ permissions :
25+ contents : read
26+ security-events : write
2327 steps :
2428 - name : Checkout
2529 uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
@@ -58,20 +62,34 @@ jobs:
5862 --report-path gitleaks.sarif
5963
6064 - name : Upload gitleaks SARIF
61- if : always()
65+ if : >
66+ always() &&
67+ (github.event_name != 'pull_request' ||
68+ github.event.pull_request.head.repo.full_name == github.repository)
6269 uses : github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
6370 with :
6471 sarif_file : gitleaks.sarif
6572 category : security-analysis/gitleaks
6673
6774 - name : Build Docker image
68- run : docker build --pull -t dev-template:latest .devcontainer/
75+ run : |
76+ docker build --pull -t "dev-template:${{ github.sha }}" .devcontainer/
77+ docker save "dev-template:${{ github.sha }}" -o /tmp/dev-template.tar
78+
79+ - name : Upload image artifact
80+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
81+ with :
82+ name : docker-image-${{ github.sha }}
83+ path : /tmp/dev-template.tar
84+ retention-days : 1
6985
7086 test :
7187 runs-on : ubuntu-latest
7288 name : Test Devcontainer
7389 needs : build
7490 timeout-minutes : 20
91+ permissions :
92+ contents : read
7593 steps :
7694 - name : Checkout
7795 uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
84102 push : never
85103 runCmd : |
86104 set -eu
87- for cmd in python3 node npm gh opencode curl jq; do
105+ for cmd in python3 node npm gh opencode curl jq claude ; do
88106 if ! command -v "$cmd" >/dev/null 2>&1; then
89107 echo "::error::$cmd is missing"
90108 exit 1
@@ -97,3 +115,98 @@ jobs:
97115 opencode --version
98116 curl --version | head -1
99117 jq --version
118+ claude --version
119+
120+ scan :
121+ runs-on : ubuntu-latest
122+ name : Security and SBOM Analysis
123+ needs : build
124+ timeout-minutes : 20
125+ permissions :
126+ contents : read
127+ security-events : write
128+ steps :
129+ - name : Checkout
130+ uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
131+ with :
132+ persist-credentials : false
133+
134+ - name : Download image artifact
135+ uses : actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
136+ with :
137+ name : docker-image-${{ github.sha }}
138+ path : /tmp
139+
140+ - name : Load Docker image
141+ run : docker load -i /tmp/dev-template.tar
142+
143+ - name : Run Trivy vulnerability scanner (image)
144+ id : trivy-image
145+ continue-on-error : true
146+ uses : aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
147+ with :
148+ image-ref : ' dev-template:${{ github.sha }}'
149+ format : ' sarif'
150+ output : ' trivy-results.sarif'
151+ severity : ' CRITICAL'
152+ ignore-unfixed : true
153+ limit-severities-for-sarif : true
154+ exit-code : ' 1'
155+ cache : false
156+
157+ - name : Upload Trivy image scan results
158+ if : >
159+ always() &&
160+ (github.event_name != 'pull_request' ||
161+ github.event.pull_request.head.repo.full_name == github.repository)
162+ uses : github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
163+ with :
164+ sarif_file : ' trivy-results.sarif'
165+ category : ' security-analysis/trivy-image'
166+
167+ - name : Generate SBOM
168+ if : always()
169+ uses : anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0
170+ with :
171+ image : ' dev-template:${{ github.sha }}'
172+ format : ' spdx-json'
173+ output-file : ' sbom.spdx.json'
174+
175+ - name : Upload SBOM as artifact
176+ if : always()
177+ uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
178+ with :
179+ name : sbom-${{ github.sha }}
180+ path : sbom.spdx.json
181+ retention-days : 30
182+
183+ - name : Run Trivy vulnerability scanner (filesystem)
184+ id : trivy-fs
185+ continue-on-error : true
186+ uses : aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0
187+ with :
188+ scan-type : ' fs'
189+ scan-ref : ' .'
190+ format : ' sarif'
191+ output : ' trivy-fs-results.sarif'
192+ severity : ' CRITICAL'
193+ ignore-unfixed : true
194+ limit-severities-for-sarif : true
195+ exit-code : ' 1'
196+ cache : false
197+
198+ - name : Upload Trivy filesystem scan results
199+ if : >
200+ always() &&
201+ (github.event_name != 'pull_request' ||
202+ github.event.pull_request.head.repo.full_name == github.repository)
203+ uses : github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4
204+ with :
205+ sarif_file : ' trivy-fs-results.sarif'
206+ category : ' security-analysis/trivy-filesystem'
207+
208+ - name : Fail job if any Trivy scan found CRITICAL vulnerabilities
209+ if : steps.trivy-image.outcome == 'failure' || steps.trivy-fs.outcome == 'failure'
210+ run : |
211+ echo "::error::Trivy found CRITICAL vulnerabilities. Review the Security tab for details."
212+ exit 1
0 commit comments