This repository was archived by the owner on Aug 7, 2026. It is now read-only.
chore(deps): Update npm-frontend-prod #1182
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Security Scanning Workflow | |
| # Runs SAST, dependency scanning, container scanning, and DAST on PRs | |
| # All tools are FREE for public repositories | |
| # | |
| # Policy: Vulnerabilities rated MEDIUM or higher block merges to main | |
| # Configure branch protection to require "Security Status" check | |
| # | |
| # Can be called from other workflows (e.g., create-beta.yml) via workflow_call | |
| name: "10 Security" | |
| on: | |
| pull_request: | |
| branches: [main] | |
| # Note: No paths filter here - we always run to report status | |
| # Individual jobs use dorny/paths-filter to skip when not needed | |
| push: | |
| branches: [main] | |
| schedule: | |
| # Weekly scan on Monday at midnight UTC to catch new CVEs | |
| - cron: "0 0 * * 1" | |
| workflow_dispatch: | |
| inputs: | |
| head_sha: | |
| description: 'Commit SHA to post status to (for PR association)' | |
| required: false | |
| type: string | |
| # Allow other workflows to call this one | |
| workflow_call: | |
| inputs: | |
| severity: | |
| description: 'Minimum severity to fail on (high or medium)' | |
| required: false | |
| default: 'medium' | |
| type: string | |
| skip_dast: | |
| description: 'Skip DAST scan for faster execution' | |
| required: false | |
| default: false | |
| type: boolean | |
| skip_codeql: | |
| description: 'Skip CodeQL scan (for beta releases where code was already scanned on merge)' | |
| required: false | |
| default: false | |
| type: boolean | |
| concurrency: | |
| group: security-${{ github.ref }} | |
| cancel-in-progress: true | |
| # Note: When called as a reusable workflow (workflow_call), permissions | |
| # are inherited from the calling workflow. When run standalone, the | |
| # default token permissions apply (usually read access to most resources). | |
| # Individual jobs specify additional write permissions where needed. | |
| jobs: | |
| # ============================================================================ | |
| # Detect which files changed - skip most scans if no security-relevant files | |
| # Note: CodeQL always runs (required by Code Scanning ruleset) | |
| # ============================================================================ | |
| changes: | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| outputs: | |
| security_relevant: ${{ steps.result.outputs.security_relevant }} | |
| steps: | |
| - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| # For workflow_dispatch and workflow_call, always run scans (explicit request) | |
| # For push/pull_request, use paths-filter to skip if no security-relevant files changed | |
| - name: Check if explicit trigger | |
| id: explicit | |
| run: | | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ] || \ | |
| [ "${{ github.event_name }}" = "workflow_call" ] || \ | |
| [ "${{ github.event_name }}" = "schedule" ]; then | |
| echo "is_explicit=true" >> $GITHUB_OUTPUT | |
| else | |
| echo "is_explicit=false" >> $GITHUB_OUTPUT | |
| fi | |
| - uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3 | |
| id: filter | |
| if: steps.explicit.outputs.is_explicit == 'false' | |
| with: | |
| filters: | | |
| security_relevant: | |
| - '**.py' | |
| - '**.ts' | |
| - '**.tsx' | |
| - '**.js' | |
| - '**/package*.json' | |
| - 'requirements/**' | |
| - 'Dockerfile' | |
| - 'docker-compose.yml' | |
| # Note: .github/** removed - workflow YAML isn't scanned by CodeQL | |
| # and triggers unnecessary runs on every workflow change | |
| - name: Determine if scans should run | |
| id: result | |
| run: | | |
| if [ "${{ steps.explicit.outputs.is_explicit }}" = "true" ]; then | |
| echo "security_relevant=true" >> $GITHUB_OUTPUT | |
| echo "Explicit trigger - running all scans" | |
| else | |
| echo "security_relevant=${{ steps.filter.outputs.security_relevant }}" >> $GITHUB_OUTPUT | |
| echo "Paths filter result: ${{ steps.filter.outputs.security_relevant }}" | |
| fi | |
| # ============================================================================ | |
| # CodeQL - Static Application Security Testing (SAST) | |
| # Scans for SQL injection, XSS, command injection, path traversal, etc. | |
| # Results appear in GitHub Security tab | |
| # ============================================================================ | |
| codeql: | |
| name: CodeQL (${{ matrix.language }}) | |
| # Skip for beta releases (code was already scanned on merge to main) | |
| if: ${{ !inputs.skip_codeql }} | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| timeout-minutes: 15 | |
| permissions: | |
| security-events: write | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| language: [javascript-typescript, python] | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4 | |
| with: | |
| languages: ${{ matrix.language }} | |
| # Use extended query suite for more comprehensive analysis | |
| queries: security-extended | |
| # Use custom config to exclude false positives | |
| config-file: ./.github/codeql/codeql-config.yml | |
| - name: Autobuild | |
| uses: github/codeql-action/autobuild@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4 | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4 | |
| with: | |
| category: "/language:${{ matrix.language }}" | |
| # ============================================================================ | |
| # Secret Scanning with Gitleaks | |
| # Detects hardcoded secrets, API keys, passwords in code | |
| # Uses the free CLI instead of the paid GitHub Action | |
| # ============================================================================ | |
| secret-scan: | |
| name: Secret Scan | |
| needs: changes | |
| if: needs.changes.outputs.security_relevant == 'true' | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| with: | |
| fetch-depth: 0 # Full history for scanning all commits | |
| - name: Install Gitleaks | |
| run: | | |
| curl -sSfL https://github.com/gitleaks/gitleaks/releases/download/v8.21.2/gitleaks_8.21.2_linux_x64.tar.gz | tar xz | |
| sudo mv gitleaks /usr/local/bin/ | |
| - name: Run Gitleaks | |
| run: gitleaks detect --source . --verbose --redact | |
| # ============================================================================ | |
| # Dependency Scanning | |
| # Checks npm and pip packages for known vulnerabilities | |
| # ============================================================================ | |
| dependency-scan: | |
| name: Dependency Scan | |
| needs: changes | |
| if: needs.changes.outputs.security_relevant == 'true' | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6 | |
| with: | |
| node-version: "20" | |
| - name: Setup Python | |
| uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Install frontend dependencies | |
| working-directory: frontend | |
| run: npm ci | |
| - name: Run npm audit | |
| working-directory: frontend | |
| # --audit-level controls minimum severity to fail on | |
| # moderate = medium+, high = high+ | |
| run: | | |
| LEVEL="${{ inputs.severity || 'medium' }}" | |
| if [ "$LEVEL" = "high" ]; then | |
| npm audit --audit-level=high | |
| else | |
| npm audit --audit-level=moderate | |
| fi | |
| - name: Install pip-audit | |
| run: pip install pip-audit | |
| - name: Run pip-audit | |
| # --strict fails on any vulnerability, --desc shows descriptions | |
| run: pip-audit --strict --desc -r requirements/base.txt | |
| # ============================================================================ | |
| # Container Scanning with Trivy | |
| # Scans Docker image for OS and application vulnerabilities | |
| # ============================================================================ | |
| container-scan: | |
| name: Container Scan | |
| needs: changes | |
| if: needs.changes.outputs.security_relevant == 'true' | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| timeout-minutes: 15 | |
| permissions: | |
| security-events: write | |
| contents: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build Docker image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 | |
| with: | |
| context: . | |
| push: false | |
| load: true | |
| tags: eclosion:scan | |
| cache-from: type=gha | |
| cache-to: type=gha,mode=max | |
| - name: Determine severity threshold | |
| id: severity | |
| run: | | |
| LEVEL="${{ inputs.severity || 'medium' }}" | |
| if [ "$LEVEL" = "high" ]; then | |
| echo "trivy_severity=HIGH,CRITICAL" >> $GITHUB_OUTPUT | |
| else | |
| echo "trivy_severity=MEDIUM,HIGH,CRITICAL" >> $GITHUB_OUTPUT | |
| fi | |
| - name: Run Trivy vulnerability scanner | |
| uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 | |
| with: | |
| image-ref: eclosion:scan | |
| format: "table" | |
| exit-code: "1" | |
| severity: ${{ steps.severity.outputs.trivy_severity }} | |
| ignore-unfixed: true | |
| trivy-config: config/trivy.yaml | |
| - name: Run Trivy and upload SARIF | |
| uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # 0.33.1 | |
| if: always() | |
| with: | |
| image-ref: eclosion:scan | |
| format: "sarif" | |
| output: "trivy-results.sarif" | |
| severity: ${{ steps.severity.outputs.trivy_severity }} | |
| trivy-config: config/trivy.yaml | |
| - name: Upload Trivy SARIF to GitHub Security tab | |
| uses: github/codeql-action/upload-sarif@cdefb33c0f6224e58673d9004f47f7cb3e328b89 # v4 | |
| # Skip SARIF upload when called via workflow_call (e.g., from release pipelines) | |
| # to avoid creating duplicate configurations in GitHub Code Scanning. | |
| # The table output above still shows pass/fail for the release gate. | |
| if: always() && github.event_name != 'workflow_call' | |
| with: | |
| sarif_file: "trivy-results.sarif" | |
| category: "trivy-container-scan" | |
| # ============================================================================ | |
| # DAST - Dynamic Application Security Testing with OWASP ZAP | |
| # Runs penetration testing against the running application | |
| # Only runs on non-fork PRs to prevent abuse | |
| # ============================================================================ | |
| dast: | |
| name: DAST (OWASP ZAP) | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| timeout-minutes: 20 | |
| needs: [changes, container-scan] | |
| # Only run DAST on: | |
| # - Push events (trusted) | |
| # - PRs from the same repo (not forks) to prevent abuse | |
| # - Scheduled runs | |
| # - Manual runs (not workflow_call with skip_dast=true) | |
| if: | | |
| needs.changes.outputs.security_relevant == 'true' && | |
| inputs.skip_dast != true && ( | |
| github.event_name == 'push' || | |
| github.event_name == 'schedule' || | |
| github.event_name == 'workflow_dispatch' || | |
| github.event_name == 'workflow_call' || | |
| (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) | |
| ) | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6 | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 | |
| - name: Build Docker image | |
| uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6 | |
| with: | |
| context: . | |
| push: false | |
| load: true | |
| tags: eclosion:test | |
| cache-from: type=gha | |
| - name: Start application | |
| run: | | |
| echo "Starting container..." | |
| # Note: We don't set FLASK_DEBUG=1 because that changes app behavior | |
| # Instead, ZAP will set X-Forwarded-Proto: https to bypass HTTPS redirect | |
| # Note: We don't set INSTANCE_SECRET so ZAP can access all endpoints | |
| docker run -d --name eclosion -p 5001:5001 \ | |
| eclosion:test | |
| echo "Container started with ID: $(docker ps -q -f name=eclosion)" | |
| - name: Wait for application to be healthy | |
| run: | | |
| echo "=== Container status ===" | |
| docker ps -a | |
| echo "" | |
| echo "=== Waiting for application to start ===" | |
| for i in {1..30}; do | |
| echo "Attempt $i/30..." | |
| # Check if container is still running | |
| if ! docker ps -q -f name=eclosion | grep -q .; then | |
| echo "ERROR: Container is not running!" | |
| echo "" | |
| echo "=== Container logs ===" | |
| docker logs eclosion 2>&1 || echo "No logs available" | |
| echo "" | |
| echo "=== Container inspect ===" | |
| docker inspect eclosion --format='{{.State.Status}} - {{.State.Error}}' || true | |
| exit 1 | |
| fi | |
| # Try the health check (with X-Forwarded-Proto to bypass HTTPS redirect) | |
| if curl -sf -H "X-Forwarded-Proto: https" http://localhost:5001/health; then | |
| echo "" | |
| echo "Application is healthy!" | |
| exit 0 | |
| fi | |
| echo " Health check failed, waiting 2s..." | |
| sleep 2 | |
| done | |
| echo "" | |
| echo "=== TIMEOUT: Application failed to become healthy ===" | |
| echo "" | |
| echo "=== Container logs ===" | |
| docker logs eclosion 2>&1 | |
| echo "" | |
| echo "=== Container inspect ===" | |
| docker inspect eclosion | |
| echo "" | |
| echo "=== Listening ports ===" | |
| docker exec eclosion ss -tlnp 2>/dev/null || docker exec eclosion netstat -tlnp 2>/dev/null || echo "Unable to check ports" | |
| echo "" | |
| echo "=== Process list ===" | |
| docker exec eclosion ps aux 2>/dev/null || echo "ps not available in container" | |
| exit 1 | |
| - name: Run OWASP ZAP Baseline Scan | |
| uses: zaproxy/action-baseline@de8ad967d3548d44ef623df22cf95c3b0baf8b25 # v0.15.0 | |
| env: | |
| # Set X-Forwarded-Proto to bypass HTTPS redirect in the app | |
| # The app redirects HTTP to HTTPS in production unless this header is set | |
| ZAP_AUTH_HEADER: "X-Forwarded-Proto" | |
| ZAP_AUTH_HEADER_VALUE: "https" | |
| with: | |
| target: "http://localhost:5001" | |
| rules_file_name: ".zap/rules.tsv" | |
| fail_action: true | |
| # -a: Include alpha rules | |
| # -j: Use AJAX spider (disabled for performance) | |
| cmd_options: "-a" | |
| - name: Stop application | |
| if: always() | |
| run: docker stop eclosion || true | |
| # ============================================================================ | |
| # Security Status - Aggregates all security job results | |
| # Configure branch protection to require this check | |
| # ============================================================================ | |
| security-status: | |
| name: Security Status | |
| runs-on: ${{ vars.RUNNER_PROVIDER == 'namespace' && 'ubuntu-2204-x64-4x16' || 'ubuntu-latest' }} | |
| needs: [changes, codeql, secret-scan, dependency-scan, container-scan, dast] | |
| if: always() | |
| steps: | |
| - name: Check security scan results | |
| id: check | |
| run: | | |
| echo "=== Security Scan Results ===" | |
| echo "Security-relevant files changed: ${{ needs.changes.outputs.security_relevant }}" | |
| echo "CodeQL: ${{ needs.codeql.result }}" | |
| echo "Secret Scan: ${{ needs.secret-scan.result }}" | |
| echo "Dependency Scan: ${{ needs.dependency-scan.result }}" | |
| echo "Container Scan: ${{ needs.container-scan.result }}" | |
| echo "DAST: ${{ needs.dast.result }}" | |
| echo "" | |
| # CodeQL can be skipped for beta releases (already scanned on merge) | |
| if [ "${{ needs.codeql.result }}" = "failure" ] || [ "${{ needs.codeql.result }}" = "cancelled" ]; then | |
| echo "::error::CodeQL scan failed" | |
| echo "result=failure" >> $GITHUB_OUTPUT | |
| echo "description=CodeQL scan failed" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # If no security-relevant files changed, other scans were skipped - that's OK | |
| if [ "${{ needs.changes.outputs.security_relevant }}" != "true" ]; then | |
| if [ "${{ needs.codeql.result }}" = "skipped" ]; then | |
| echo "✅ All scans skipped (beta release or no security-relevant files)" | |
| echo "result=success" >> $GITHUB_OUTPUT | |
| echo "description=Scans skipped (beta release)" >> $GITHUB_OUTPUT | |
| else | |
| echo "✅ CodeQL passed, other scans skipped (no security-relevant files)" | |
| echo "result=success" >> $GITHUB_OUTPUT | |
| echo "description=CodeQL passed, no code changes to scan" >> $GITHUB_OUTPUT | |
| fi | |
| exit 0 | |
| fi | |
| # Check other scans (DAST is allowed to be skipped for fork PRs) | |
| results="${{ needs.secret-scan.result }} ${{ needs.dependency-scan.result }} ${{ needs.container-scan.result }}" | |
| dast_result="${{ needs.dast.result }}" | |
| if echo "$results" | grep -qE '(failure|cancelled)'; then | |
| echo "::error::One or more security scans failed" | |
| echo "result=failure" >> $GITHUB_OUTPUT | |
| echo "description=One or more security scans failed" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| # DAST failure should also fail, but skipped is OK | |
| if [ "$dast_result" = "failure" ] || [ "$dast_result" = "cancelled" ]; then | |
| echo "::error::DAST scan failed or was cancelled" | |
| echo "result=failure" >> $GITHUB_OUTPUT | |
| echo "description=DAST scan failed or was cancelled" >> $GITHUB_OUTPUT | |
| exit 1 | |
| fi | |
| echo "✅ All security scans passed" | |
| echo "result=success" >> $GITHUB_OUTPUT | |
| echo "description=All security scans passed" >> $GITHUB_OUTPUT | |
| # For workflow_dispatch, post status to the commit so it appears on PRs | |
| - name: Post commit status | |
| if: github.event_name == 'workflow_dispatch' && inputs.head_sha && always() | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh api repos/${{ github.repository }}/statuses/${{ inputs.head_sha }} \ | |
| -f state="${{ steps.check.outputs.result || 'success' }}" \ | |
| -f context="Security Status" \ | |
| -f description="${{ steps.check.outputs.description || 'Security scans passed' }}" | |
| echo "Posted Security Status to commit ${{ inputs.head_sha }}" |