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 }} 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@dcedce43c6f43de0b836d1fe38946645c9c638dc continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: format: sarif report_path: results.sarif - name: Install Project Dependencies if: env.SECURITY_SNYK_TOKEN != '' env: SECURITY_PACKAGE: ${{ vars.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 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 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 run: | VERSION="0.56.1" echo "Installing Trivy $VERSION..." curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin "v$VERSION" trivy config . --format json --output trivy_result.json --severity CRITICAL,HIGH || true - name: Generate Security Report 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@v3 if: always() with: sarif_file: results.sarif category: gitleaks - name: Push to Audit Branch if: 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 --local user.email "action@github.com" git config --local user.name "GitHub Action" 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