diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index ae6ec091..377152f2 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -42,10 +42,67 @@ jobs: type=sha,prefix=sha- type=raw,value=latest,enable={{is_default_branch}} + # Test the same minimal HTTP-only Docker application; no TLS setup is added. + - name: Build fresh Docker image for security testing + run: docker build --pull -t markdown-viewer:security-audit . + + - name: Smoke-test HTTP application and Nginx configuration + run: | + docker run -d --rm --name mdv-security-test -p 127.0.0.1:18080:80 markdown-viewer:security-audit + trap 'docker stop mdv-security-test >/dev/null 2>&1 || true' EXIT + docker exec mdv-security-test nginx -t + for i in $(seq 1 30); do + if curl -fsS --max-time 3 -o /dev/null http://127.0.0.1:18080/; then break; fi + sleep 1 + done + curl -fsS --max-time 10 -o /dev/null http://127.0.0.1:18080/ + curl -fsS --max-time 10 -o /dev/null http://127.0.0.1:18080/styles.css + curl -fsS --max-time 10 -o /dev/null http://127.0.0.1:18080/script.js + curl -fsSI --max-time 10 http://127.0.0.1:18080/ | grep -qi '^content-security-policy:' + ! curl -fsSI --max-time 10 http://127.0.0.1:18080/ | grep -qi 'upgrade-insecure-requests' + + - name: Prepare Docker vulnerability report directory + run: mkdir -p docker-artifacts + + - name: Scan all known OS and library CVEs (machine-readable artifact) + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: markdown-viewer:security-audit + format: json + output: docker-artifacts/trivy-vulnerabilities.json + scanners: vuln + vuln-type: os,library + severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL + exit-code: '0' + + - name: Fail on fixable critical or high vulnerabilities + uses: aquasecurity/trivy-action@v0.36.0 + with: + image-ref: markdown-viewer:security-audit + format: table + scanners: vuln + vuln-type: os,library + severity: CRITICAL,HIGH + ignore-unfixed: true + exit-code: '1' + + - name: Upload complete vulnerability scan report + if: always() + uses: actions/upload-artifact@v4 + with: + name: docker-security-report + path: docker-artifacts/trivy-vulnerabilities.json + if-no-files-found: ignore + - name: Build and push Docker image uses: docker/build-push-action@v5 with: context: . + pull: true + # Registry-published images carry provenance and an SBOM attestation. + # Local Docker image-store scans do not inherit registry attestations. + provenance: mode=max + sbom: true push: ${{ github.event_name != 'pull_request' }} tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index bc7c50b4..af415415 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,9 @@ # Use nginx as the base image for serving static files -FROM nginx:alpine +FROM nginx:1.31.8-alpine3.24-slim + +# Omit optional Alpine/Nginx modules to reduce third-party package exposure. +# Apply security updates from the supported Alpine stable release. +RUN apk upgrade --no-cache # PERF-019: Only copy necessary web files (exclude .git, desktop-app, wiki, etc.) COPY index.html /usr/share/nginx/html/