name: Security Scan on: push: branches: [main] paths: - 'src/**/*.php' - 'src/**/*.sh' - '.github/workflows/security-scan.yml' pull_request: paths: - 'src/**/*.php' - 'src/**/*.sh' - '.github/workflows/security-scan.yml' workflow_dispatch: schedule: - cron: '0 6 * * 1' - cron: '0 6 * * 4' permissions: contents: read security-events: write jobs: semgrep: name: Semgrep Security Scan runs-on: ubuntu-latest container: image: semgrep/semgrep env: SEMGREP_CONFIGS: >- --config p/php --config p/security-audit --config p/command-injection --config p/sql-injection --config p/xss steps: - name: Checkout uses: actions/checkout@v7 with: # Full history is required so --baseline-commit can diff against it. fetch-depth: 0 - name: Resolve baseline commit id: base shell: bash run: | # Diff-aware scanning: only findings NEW relative to the baseline block # the build. The pre-existing backlog stays visible in the Security tab # (full scan on schedule) but does not fail push/PR runs. ref="" case "${{ github.event_name }}" in pull_request) ref="${{ github.event.pull_request.base.sha }}" ;; push) ref="${{ github.event.before }}" ;; esac # Ignore an empty / all-zero / unreachable baseline (e.g. first push). if [ -z "$ref" ] || [ "$ref" = "0000000000000000000000000000000000000000" ] \ || ! git cat-file -e "$ref^{commit}" 2>/dev/null; then ref="" fi echo "ref=$ref" >> "$GITHUB_OUTPUT" - name: Run Semgrep (diff — blocks only NEW findings) if: steps.base.outputs.ref != '' run: | semgrep scan $SEMGREP_CONFIGS \ --baseline-commit "${{ steps.base.outputs.ref }}" \ --sarif --output semgrep-results.sarif \ --error --severity ERROR \ src/ - name: Run Semgrep (full — informational, surfaces the backlog) if: steps.base.outputs.ref == '' continue-on-error: true run: | semgrep scan $SEMGREP_CONFIGS \ --sarif --output semgrep-results.sarif \ --severity ERROR \ src/ - name: Upload SARIF if: always() uses: github/codeql-action/upload-sarif@v4 with: sarif_file: semgrep-results.sarif category: semgrep