Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 86 additions & 5 deletions .github/workflows/test-trivy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ on:
pull_request:
workflow_dispatch: # Manual trigger for testing

permissions:
contents: read
security-events: write

jobs:
hadolint:
name: Dockerfile Lint
Expand All @@ -22,8 +26,23 @@ jobs:
output-file: hadolint.sarif
no-fail: true

- name: Add Hadolint results to Job Summary
run: |
echo "## 🐳 Hadolint Dockerfile Analysis" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

# Parse SARIF and extract readable results
ISSUES=$(jq -r '.runs[0].results[] | "- **\(.ruleId)** (line \(.locations[0].physicalLocation.region.startLine)): \(.message.text)"' hadolint.sarif 2>/dev/null)

if [ -n "$ISSUES" ]; then
echo "### Issues Found" >> $GITHUB_STEP_SUMMARY
echo "$ISSUES" >> $GITHUB_STEP_SUMMARY
else
echo "✅ **No issues found** - Dockerfile follows best practices" >> $GITHUB_STEP_SUMMARY
fi

- name: Upload Hadolint SARIF to Security tab
uses: github/codeql-action/upload-sarif@v3
uses: github/codeql-action/upload-sarif@v4
if: github.event.repository.visibility == 'public'
with:
sarif_file: hadolint.sarif
Expand All @@ -39,16 +58,54 @@ jobs:
- name: Build Docker image locally
run: docker build -t trivy-test:${{ github.sha }} .

- name: Run Trivy (table output - logs)
- name: Run Trivy (table output for summary)
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: 'trivy-test:${{ github.sha }}'
format: 'table'
output: 'trivy-table.txt'
exit-code: '0' # Non-blocking for visibility
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH,MEDIUM'
trivyignores: ${{ hashFiles('.trivyignore') != '' && '.trivyignore' || '' }}

- name: Add Trivy results to Job Summary
run: |
echo "## 🐳 Container Image Vulnerabilities" >> $GITHUB_STEP_SUMMARY
echo "Image: \`trivy-test:${{ github.sha }}\`" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat trivy-table.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

# Filesystem scan - table output for Job Summary
# NOTE: Must run BEFORE any SARIF scans to avoid TRIVY_FORMAT env var pollution
- name: Run Trivy filesystem scan (table output)
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: 'fs'
scan-ref: '.'
format: 'table'
output: 'trivy-fs-table.txt'
exit-code: '0'
ignore-unfixed: true
vuln-type: 'library'
severity: 'CRITICAL,HIGH,MEDIUM'
trivyignores: ${{ hashFiles('.trivyignore') != '' && '.trivyignore' || '' }}

- name: Add Trivy filesystem results to Job Summary
run: |
echo "" >> $GITHUB_STEP_SUMMARY
echo "## 📦 Source Dependency Vulnerabilities" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
cat trivy-fs-table.txt >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY

# SARIF scans run last to prevent TRIVY_FORMAT env var from polluting table scans
# NOTE: Image SARIF paths (e.g., /app/package.json) don't map to repo files,
# so alerts won't appear in GitHub Security tab. Kept for demonstration only.
# For actual Security tab alerts, use the filesystem scan below.
- name: Run Trivy (SARIF output - Security tab)
uses: aquasecurity/trivy-action@0.28.0
if: github.event.repository.visibility == 'public'
Expand All @@ -60,13 +117,36 @@ jobs:
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL,HIGH,MEDIUM'
trivyignores: ${{ hashFiles('.trivyignore') != '' && '.trivyignore' || '' }}

- name: Upload Trivy SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
- name: Upload Trivy image SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: github.event.repository.visibility == 'public'
with:
sarif_file: 'trivy-results.sarif'
category: trivy
category: trivy-image

# Filesystem scan - SARIF for Security tab (paths map to repo files)
- name: Run Trivy filesystem scan (SARIF output)
uses: aquasecurity/trivy-action@0.28.0
if: github.event.repository.visibility == 'public'
with:
scan-type: 'fs'
scan-ref: '.'
format: 'sarif'
output: 'trivy-fs-results.sarif'
exit-code: '0'
ignore-unfixed: true
vuln-type: 'library'
severity: 'CRITICAL,HIGH,MEDIUM'
trivyignores: ${{ hashFiles('.trivyignore') != '' && '.trivyignore' || '' }}

- name: Upload Trivy filesystem SARIF to GitHub Security tab
uses: github/codeql-action/upload-sarif@v4
if: github.event.repository.visibility == 'public'
with:
sarif_file: 'trivy-fs-results.sarif'
category: trivy-fs

test-blocking-mode:
name: Test Blocking Mode (CRITICAL only)
Expand All @@ -88,3 +168,4 @@ jobs:
ignore-unfixed: true
vuln-type: 'os,library'
severity: 'CRITICAL'
trivyignores: ${{ hashFiles('.trivyignore') != '' && '.trivyignore' || '' }}
15 changes: 9 additions & 6 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
# Build stage
FROM node:20-alpine AS builder
# Build stage - intentionally bad for Hadolint testing
FROM node:latest AS builder

Check warning

Code scanning / Hadolint

Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag Warning

Using latest is prone to errors if the image will ever update. Pin the version explicitly to a release tag

RUN cd /app || mkdir /app

Check warning

Code scanning / Hadolint

Use WORKDIR to switch to a directory Warning

Use WORKDIR to switch to a directory
WORKDIR /app

COPY package*.json ./
ADD package*.json ./

Check failure

Code scanning / Hadolint

Use COPY instead of ADD for files and folders Error

Use COPY instead of ADD for files and folders
RUN npm ci

COPY tsconfig.json ./
COPY src ./src
ADD tsconfig.json ./

Check failure

Code scanning / Hadolint

Use COPY instead of ADD for files and folders Error

Use COPY instead of ADD for files and folders
ADD src ./src

Check failure

Code scanning / Hadolint

Use COPY instead of ADD for files and folders Error

Use COPY instead of ADD for files and folders

RUN npm run build

# Production stage
FROM node:20-alpine

RUN apk add curl wget

Check warning

Code scanning / Hadolint

Pin versions in apk add. Instead of apk add <package> use apk add <package>=<version> Warning

Pin versions in apk add. Instead of apk add <package> use apk add <package>=<version>

Check notice

Code scanning / Hadolint

Use the --no-cache switch to avoid the need to use --update and remove /var/cache/apk/* when done installing packages Note

Use the --no-cache switch to avoid the need to use --update and remove /var/cache/apk/* when done installing packages

WORKDIR /app

# Create non-root user for security
Expand All @@ -29,4 +32,4 @@

EXPOSE 3000

CMD ["node", "dist/index.js"]
CMD node dist/index.js

Check warning

Code scanning / Hadolint

Use arguments JSON notation for CMD and ENTRYPOINT arguments Warning

Use arguments JSON notation for CMD and ENTRYPOINT arguments
Loading