name: Security Scan (reusable) # Reusable workflow consumed by per-repo security.yml stubs across the # Hellion stack. Runs Semgrep SAST + Trivy filesystem scan sequentially # inside a single job. Either tool failing fails the calling workflow. # # Why one job, not two parallel jobs: # act_runner v0.6.1 has a race condition when two jobs in the same task # share a workspace and chown it in parallel — one container never gets # /var/run/act/ provisioned and silent-fails. Sequential steps avoid it. on: workflow_call: inputs: severity: description: 'Trivy severity threshold (e.g. CRITICAL,HIGH or just CRITICAL)' required: false type: string default: 'CRITICAL,HIGH' semgrep-config: description: 'Semgrep config (default auto detects rules per language)' required: false type: string default: 'auto' semgrep-exclude-rules: description: 'Semgrep rule IDs to exclude, comma-separated (e.g. csharp.lang.security.sqli.csharp-sqli)' required: false type: string default: '' jobs: scan: name: Security Scan runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.x' - name: Install Semgrep run: pip install --no-cache-dir semgrep - name: Install Trivy # Direct install via the official upstream script. The aquasecurity/ # trivy-action wrapper does nested checkouts and auth-juggling that # does not play well with Self-Hosted Gitea Actions, this is more # robust and a smaller surface. # # Version pinned: the install script otherwise hits api.github.com to # resolve "latest", which is unauthenticated and burns through the # self-hosted runner's GitHub rate-limit on each push. Pinning skips # the API call entirely. Renovate-bot keeps the version current: # renovate: datasource=github-releases depName=aquasecurity/trivy run: curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sudo sh -s -- -b /usr/local/bin v0.70.0 - name: Run Semgrep SAST # --config=auto pulls language-appropriate rule packs from semgrep.dev # without requiring an account. --error makes the step fail when ERROR # findings exist. WARNING-level rules still run for visibility but do # not fail the build (they would dominate the noise). # Per-repo rule exclusion via the semgrep-exclude-rules input. env: EXCLUDE_RULES: ${{ inputs.semgrep-exclude-rules }} run: | args="--config=${{ inputs.semgrep-config }} --error --severity=ERROR" if [ -n "$EXCLUDE_RULES" ]; then for rule in $(echo "$EXCLUDE_RULES" | tr ',' ' '); do args="$args --exclude-rule=$rule" done fi echo "Running: semgrep scan $args" semgrep scan $args - name: Run Trivy filesystem scan # if: always() — surface Trivy findings even when Semgrep fails first, # so a single run gives the full combined picture. # Scans dependency manifests (NuGet, npm, package-lock etc.) against # the NVD CVE database. --ignore-unfixed skips findings that have # no patched version available so we focus on actionable items. if: always() run: trivy fs --severity ${{ inputs.severity }} --exit-code 1 --ignore-unfixed .