ci: Refine Trivy scans, centralizes security config (#26384)

This commit is contained in:
Declan Carroll 2026-03-02 07:43:29 +00:00 committed by GitHub
parent 0b84e1079d
commit 241ad231a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 49 additions and 18 deletions

View file

@ -599,10 +599,10 @@ npm audit signatures n8n@VERSION
VEX documents which CVEs actually affect n8n vs false positives from scanners.
- **File:** `vex.openvex.json` (repo root)
- **File:** `security/vex.openvex.json`
- **Format:** OpenVEX (broad scanner compatibility - Trivy, Docker Scout, etc.)
- **Attached to:** GitHub Release, Docker image attestations
- **Used by:** Trivy scans (via `.github/trivy.yaml`)
- **Used by:** Trivy scans (via `security/trivy.yaml`)
**VEX Status Types:**
| Status | Meaning |
@ -620,7 +620,7 @@ cosign verify-attestation --type openvex \
ghcr.io/n8n-io/n8n:VERSION
```
**Adding a CVE statement to vex.openvex.json:**
**Adding a CVE statement to security/vex.openvex.json:**
```json
{
"statements": [

View file

@ -330,7 +330,7 @@ jobs:
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
# VEX Attestation - Documents which CVEs affect us (vex.openvex.json)
# VEX Attestation - Documents which CVEs affect us (security/vex.openvex.json)
vex-attestation:
name: VEX Attestation
needs: [determine-build-context, build-and-push-docker, create_multi_arch_manifest, provenance-n8n, provenance-runners, provenance-runners-distroless]
@ -349,7 +349,7 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Install Cosign
uses: sigstore/cosign-installer@d7543c93d881b35a8faa02e8e3605f69b7a1ce62 # v3.10.0
uses: sigstore/cosign-installer@7e8b541eb2e61bf99390e1afd4be13a184e9ebc5 # v3.10.1
- name: Login to GHCR
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
@ -363,7 +363,7 @@ jobs:
run: |
cosign attest --yes \
--type openvex \
--predicate vex.openvex.json \
--predicate security/vex.openvex.json \
${{ needs.create_multi_arch_manifest.outputs.n8n_image }}@${{ needs.create_multi_arch_manifest.outputs.n8n_digest }}
- name: Attest VEX to runners image
@ -371,7 +371,7 @@ jobs:
run: |
cosign attest --yes \
--type openvex \
--predicate vex.openvex.json \
--predicate security/vex.openvex.json \
${{ needs.create_multi_arch_manifest.outputs.runners_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_digest }}
- name: Attest VEX to runners-distroless image
@ -379,7 +379,7 @@ jobs:
run: |
cosign attest --yes \
--type openvex \
--predicate vex.openvex.json \
--predicate security/vex.openvex.json \
${{ needs.create_multi_arch_manifest.outputs.runners_distroless_image }}@${{ needs.create_multi_arch_manifest.outputs.runners_distroless_digest }}
security-scan:

View file

@ -68,11 +68,11 @@ jobs:
# Upload SBOM and VEX files to the existing release
gh release upload "${{ inputs.release_tag_ref }}" \
sbom-source.cdx.json \
vex.openvex.json \
security/vex.openvex.json \
--clobber
COMPONENT_COUNT=$(jq '.components | length' sbom-source.cdx.json 2>/dev/null || echo "unknown")
VEX_STATEMENTS=$(jq '.statements | length' vex.openvex.json 2>/dev/null || echo "0")
VEX_STATEMENTS=$(jq '.statements | length' security/vex.openvex.json 2>/dev/null || echo "0")
echo "SBOM and VEX attached to release"
echo " - SBOM: $COMPONENT_COUNT components"
echo " - VEX: $VEX_STATEMENTS CVE statements"

View file

@ -34,8 +34,9 @@ jobs:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
sparse-checkout: |
vex.openvex.json
.github/trivy.yaml
security/vex.openvex.json
security/trivy.yaml
security/trivy-ignore-policy.rego
sparse-checkout-cone-mode: false
- name: Pull Docker image with retry
@ -46,7 +47,7 @@ jobs:
done
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@dc5a429b52fcf669ce959baa2c2dd26090d2a6c4 # v0.32.0
uses: aquasecurity/trivy-action@e368e328979b113139d6f9068e03accaed98a518 # v0.34.1
id: trivy_scan
with:
image-ref: ${{ inputs.image_ref }}
@ -55,7 +56,7 @@ jobs:
severity: 'CRITICAL,HIGH,MEDIUM,LOW'
ignore-unfixed: false
exit-code: '0'
trivy-config: '.github/trivy.yaml'
trivy-config: 'security/trivy.yaml'
- name: Calculate vulnerability counts
id: process_results

View file

@ -14,6 +14,15 @@ const scriptDir = path.dirname(new URL(import.meta.url).pathname);
const isInScriptsDir = path.basename(scriptDir) === 'scripts';
const rootDir = isInScriptsDir ? path.join(scriptDir, '..') : scriptDir;
const assertPathWithinRoot = (envVar, defaultRelPath) => {
const resolved = path.resolve(process.env[envVar] || path.join(rootDir, defaultRelPath));
if (!resolved.startsWith(rootDir + path.sep) && resolved !== rootDir) {
echo(chalk.red(`Error: ${envVar} must resolve within the repository root`));
process.exit(1);
}
return resolved;
};
// #region ===== Configuration =====
const config = {
imageBaseName: process.env.IMAGE_BASE_NAME || 'n8nio/n8n',
@ -27,7 +36,8 @@ const config = {
scanners: process.env.TRIVY_SCANNERS || 'vuln',
quiet: process.env.TRIVY_QUIET === 'true',
rootDir: rootDir,
vexFile: process.env.TRIVY_VEX || path.join(rootDir, 'vex.openvex.json'),
vexFile: assertPathWithinRoot('TRIVY_VEX', 'security/vex.openvex.json'),
ignorePolicyFile: assertPathWithinRoot('TRIVY_IGNORE_POLICY', 'security/trivy-ignore-policy.rego'),
};
config.fullImageName = `${config.imageBaseName}:${config.imageTag}`;
@ -54,6 +64,7 @@ const printSummary = (status, time, message) => {
echo(chalk.gray(` • Severity Levels: ${config.severity}`));
echo(chalk.gray(` • Scanners: ${config.scanners}`));
echo(chalk.gray(` • VEX file: ${config.vexFile}`));
echo(chalk.gray(` • Ignore policy: ${config.ignorePolicyFile}`));
if (config.ignoreUnfixed) echo(chalk.gray(` • Ignored unfixed: yes`));
echo(chalk.blue.bold('========================'));
};
@ -94,6 +105,8 @@ const printSummary = (status, time, message) => {
'/var/run/docker.sock:/var/run/docker.sock',
'-v',
`${config.vexFile}:/vex.openvex.json:ro`,
'-v',
`${config.ignorePolicyFile}:/trivy-ignore-policy.rego:ro`,
config.trivyImage,
'image',
'--severity',
@ -107,6 +120,8 @@ const printSummary = (status, time, message) => {
'--no-progress',
'--vex',
'/vex.openvex.json',
'--ignore-policy',
'/trivy-ignore-policy.rego',
];
if (config.ignoreUnfixed) trivyArgs.push('--ignore-unfixed');

View file

@ -0,0 +1,14 @@
# Trivy ignore policy for n8n security scans.
# n8n's own published CVEs/GHSAs are intentionally excluded from internal
# scan results. Vulnerabilities in the n8n package should be visible to
# anyone running an older version — they indicate an upgrade is required.
# VEX (vex.openvex.json) covers third-party dependency false positives only.
package trivy
import future.keywords.if
default ignore := false
ignore if {
input.PkgName == "n8n"
}

View file

@ -2,4 +2,5 @@
# See: https://trivy.dev/latest/docs/references/configuration/config-file/
vulnerability:
vex:
- vex.openvex.json
- security/vex.openvex.json
ignore-policy: security/trivy-ignore-policy.rego

View file

@ -3,8 +3,8 @@
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://github.com/n8n-io/n8n/vex",
"author": "n8n Security Team <security@n8n.io>",
"timestamp": "2026-02-13T00:00:00Z",
"version": 3,
"timestamp": "2026-03-01T00:00:00Z",
"version": 5,
"statements": [
{
"vulnerability": {