mirror of
https://github.com/n8n-io/n8n
synced 2026-04-21 15:47:20 +00:00
ci: Refine Trivy scans, centralizes security config (#26384)
This commit is contained in:
parent
0b84e1079d
commit
241ad231a4
8 changed files with 49 additions and 18 deletions
6
.github/WORKFLOWS.md
vendored
6
.github/WORKFLOWS.md
vendored
|
|
@ -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": [
|
||||
|
|
|
|||
10
.github/workflows/docker-build-push.yml
vendored
10
.github/workflows/docker-build-push.yml
vendored
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
14
security/trivy-ignore-policy.rego
Normal file
14
security/trivy-ignore-policy.rego
Normal 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"
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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": {
|
||||
Loading…
Reference in a new issue