name: Security Scan on: push: branches: - main pull_request: branches: - main workflow_dispatch: jobs: scan: runs-on: ubuntu-latest permissions: contents: write security-events: write actions: read env: SECURITY_SNYK_TOKEN: ${{ secrets.SECURITY_SNYK_TOKEN }} FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Initialize CodeQL if: env.ACT != 'true' continue-on-error: true uses: github/codeql-action/init@v4 with: languages: javascript-typescript, actions build-mode: none queries: security-extended,security-and-quality - name: Perform CodeQL Analysis if: env.ACT != 'true' continue-on-error: true uses: github/codeql-action/analyze@v4 with: upload: true output: sarif-results - name: Secret Detection id: gitleaks uses: gitleaks/gitleaks-action@ff98106e4c7b2bc287b24eaf42907196329070c7 continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install Project Dependencies if: env.SECURITY_SNYK_TOKEN != '' continue-on-error: true env: SECURITY_PACKAGE: ${{ vars.SECURITY_PACKAGE || secrets.SECURITY_PACKAGE || env.SECURITY_PACKAGE || '' }} run: | echo "Preparing dependency lock files for security scanning..." if [ -z "$SECURITY_PACKAGE" ]; then echo "SECURITY_PACKAGE is empty, installing in root..." npm install --package-lock-only --legacy-peer-deps else echo "SECURITY_PACKAGE is set to: $SECURITY_PACKAGE" # Split by comma and install IFS=',' read -ra PACKAGES <<< "$SECURITY_PACKAGE" for pkg in "${PACKAGES[@]}"; do if [ -d "$pkg" ]; then echo "Installing in "$pkg"..." npm install --prefix "$pkg" --package-lock-only --legacy-peer-deps else echo "Warning: Directory $pkg not found, skipping." fi done fi - name: Dependency Scan id: snyk if: env.SECURITY_SNYK_TOKEN != '' continue-on-error: true run: | npm install -g snyk snyk auth ${{ secrets.SECURITY_SNYK_TOKEN }} snyk test --all-projects --json-file-output=snyk_result.json > snyk_result.txt || true env: SECURITY_SNYK_TOKEN: ${{ secrets.SECURITY_SNYK_TOKEN }} - name: Check for Dockerfile id: check_docker run: | if [ -f "Dockerfile" ]; then echo "exists=true" >> $GITHUB_OUTPUT else echo "exists=false" >> $GITHUB_OUTPUT fi - name: Container Security Scan (Trivy) if: steps.check_docker.outputs.exists == 'true' continue-on-error: true uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.20.0 with: scan-type: config scan-ref: . format: json output: trivy_result.json severity: CRITICAL,HIGH - name: Generate Security Report if: always() env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | # Gitleaks typically produces results.sarif if configured or by default in some versions # We'll ensure it exists for our reporter node .github/scripts/security.cjs # Also append to step summary for immediate visibility in GHA UI cat README.md >> $GITHUB_STEP_SUMMARY echo -e "\n---\n" >> $GITHUB_STEP_SUMMARY cat README_CN.md >> $GITHUB_STEP_SUMMARY - name: Upload Gitleaks Results to GitHub Security uses: github/codeql-action/upload-sarif@v4 if: always() with: sarif_file: results.sarif category: gitleaks - name: Push to Audit Branch if: always() && github.event_name != 'pull_request' run: | mkdir audit_temp cp README.md audit_temp/ cp README_CN.md audit_temp/ [ -f "snyk_result.txt" ] && cp snyk_result.txt audit_temp/ [ -f "snyk_result.json" ] && cp snyk_result.json audit_temp/ # Collect all SARIF files with descriptive names [ -f "results.sarif" ] && cp results.sarif audit_temp/Gitleaks_results.sarif if [ -d "sarif-results" ]; then for f in sarif-results/*.sarif; do [ -f "$f" ] && cp "$f" "audit_temp/CodeQL_$(basename "$f")" done fi cd audit_temp git init git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git checkout --orphan security-audit git add . git commit -m "chore: archive security report and raw data [skip ci]" git remote add origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }} git push -f origin security-audit