fix: Docker sandbox agent execution + role template rendering + --model propagation for non-Claude CLIs #890
Workflow file for this run
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
| name: static-analysis (extended) | |
| # Additional static-analysis surface that complements the existing | |
| # ruff + mypy + bandit + CodeQL lane in ci.yml. Catches dead code | |
| # (vulture), outdated idioms (refurb), hot-path antipatterns | |
| # (perflint), pattern-based Python rules (Semgrep CE), and | |
| # IaC/filesystem CVEs (Trivy) that single-tool lanes miss. | |
| # | |
| # Each job runs in parallel and uploads SARIF to GitHub Code | |
| # Scanning so the Security tab is the single source of truth. | |
| # | |
| # Ref: .sdd/backlog/open/2026-05-19-feat-ci-semgrep-trivy-pipaudit.md | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - "src/**" | |
| - "tests/**" | |
| - "scripts/**" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| - "Dockerfile" | |
| - "docker/**" | |
| - "deploy/**" | |
| - "examples/**/Dockerfile*" | |
| - "examples/**/docker-compose*.yml" | |
| - ".github/workflows/static-analysis-extended.yml" | |
| - ".semgrep/**" | |
| pull_request: | |
| paths: | |
| - "src/**" | |
| - "tests/**" | |
| - "scripts/**" | |
| - "pyproject.toml" | |
| - "uv.lock" | |
| - "Dockerfile" | |
| - "docker/**" | |
| - "deploy/**" | |
| - "examples/**/Dockerfile*" | |
| - "examples/**/docker-compose*.yml" | |
| - ".github/workflows/static-analysis-extended.yml" | |
| - ".semgrep/**" | |
| schedule: | |
| # Weekly safety net so dormant findings still surface even when | |
| # only docs change. Sunday 05:23 UTC keeps it clear of the | |
| # zizmor / trufflehog / scorecard slots. | |
| - cron: "23 5 * * 0" | |
| workflow_dispatch: | |
| concurrency: | |
| group: static-analysis-extended-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| semgrep: | |
| # Semgrep CE registry rules (p/python + p/security-audit). | |
| # Project-specific rules already run in ci.yml semgrep job; this | |
| # lane covers the broader community ruleset and uses the git | |
| # baseline so only NEW findings fail PRs. | |
| name: Semgrep (CE rules) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| # Full history required so --baseline-commit can resolve the | |
| # merge-base on pull_request runs. | |
| fetch-depth: 0 | |
| persist-credentials: false | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install Semgrep (isolated env) | |
| # Semgrep pins click<8.2 + opentelemetry-sdk<1.38, which collide | |
| # with project floors. Install via `uv tool` so its transitive | |
| # pins never touch the project resolver. Mirrors the install | |
| # pattern used by the existing ci.yml semgrep job. | |
| run: uv tool install semgrep | |
| - name: Determine baseline ref | |
| id: baseline | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| if [ "${EVENT_NAME}" = "pull_request" ] && [ -n "${BASE_SHA}" ]; then | |
| echo "ref=${BASE_SHA}" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "ref=" >> "$GITHUB_OUTPUT" | |
| fi | |
| - name: Run Semgrep (SARIF, baseline-aware) | |
| env: | |
| BASELINE_REF: ${{ steps.baseline.outputs.ref }} | |
| run: | | |
| set -euo pipefail | |
| extra_args=() | |
| if [ -n "${BASELINE_REF}" ]; then | |
| extra_args+=("--baseline-commit=${BASELINE_REF}") | |
| fi | |
| uv tool run semgrep scan \ | |
| --config p/python \ | |
| --config p/security-audit \ | |
| --severity ERROR \ | |
| --severity WARNING \ | |
| --metrics off \ | |
| --sarif \ | |
| --sarif-output=semgrep.sarif \ | |
| "${extra_args[@]}" \ | |
| src/ | |
| - name: Drop suppressed SARIF results | |
| # Inline `# nosemgrep: <rule-id>` markers populate the SARIF | |
| # `suppressions` array but Code Scanning still ingests the | |
| # result. Strip those locally so the Security tab matches local | |
| # intent. See scripts/sarif_drop_suppressed.py. | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| semgrep.sarif > semgrep.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: semgrep.filtered.sarif | |
| category: semgrep-ce | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: semgrep-sarif | |
| path: semgrep.sarif | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| trivy-fs: | |
| # Filesystem scan for vulnerable OS / language packages + leaked | |
| # secrets across the repo. HIGH/CRITICAL fails the job; lower | |
| # severities surface in Code Scanning but do not block. | |
| name: Trivy (filesystem) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Run Trivy filesystem scan (SARIF) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| format: sarif | |
| output: trivy-fs.sarif | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "0" | |
| - name: Drop suppressed SARIF results | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| trivy-fs.sarif > trivy-fs.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: trivy-fs.filtered.sarif | |
| category: trivy-fs | |
| - name: Run Trivy filesystem scan (failing gate) | |
| # Re-run with exit-code=1 to fail the job on any HIGH/CRITICAL | |
| # finding. We split SARIF upload from the failing gate so | |
| # findings still reach Code Scanning when the gate fires. | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: fs | |
| scan-ref: . | |
| format: table | |
| severity: HIGH,CRITICAL | |
| ignore-unfixed: true | |
| exit-code: "1" | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: trivy-fs-sarif | |
| path: trivy-fs.sarif | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| trivy-iac: | |
| # Infrastructure-as-Code scan: Dockerfile, docker-compose, helm, | |
| # kustomize. Misconfigurations like missing USER directive, | |
| # privileged containers, latest tags, etc. | |
| name: Trivy (IaC) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Run Trivy IaC scan (SARIF) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: config | |
| scan-ref: . | |
| format: sarif | |
| output: trivy-iac.sarif | |
| severity: HIGH,CRITICAL | |
| hide-progress: true | |
| # ClusterFuzzLite harness intentionally runs as root inside the | |
| # OSS-Fuzz base-builder image (framework requirement); it is a | |
| # fuzzing artefact, not deployable infra, so it is excluded | |
| # from IaC misconfig checks. | |
| skip-dirs: ".clusterfuzzlite" | |
| exit-code: "0" | |
| - name: Drop suppressed SARIF results | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| trivy-iac.sarif > trivy-iac.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: trivy-iac.filtered.sarif | |
| category: trivy-iac | |
| - name: Run Trivy IaC scan (failing gate) | |
| uses: aquasecurity/trivy-action@ed142fd0673e97e23eac54620cfb913e5ce36c25 # v0.36.0 | |
| with: | |
| scan-type: config | |
| scan-ref: . | |
| format: table | |
| severity: HIGH,CRITICAL | |
| hide-progress: true | |
| skip-dirs: ".clusterfuzzlite" | |
| exit-code: "1" | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: trivy-iac-sarif | |
| path: trivy-iac.sarif | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| vulture: | |
| # Dead-code detection. Ruff's F401 only catches unused imports | |
| # within a file; vulture spots unused functions/classes/vars | |
| # across the 40-adapter tree. Findings are advisory (job does | |
| # not fail) until the existing dead-code follow-ups land. | |
| name: vulture (dead code) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install vulture (isolated env) | |
| run: uv tool install vulture | |
| - name: Run vulture (capture text output) | |
| # `|| true` keeps the step green so the SARIF upload always | |
| # runs; the workflow is advisory pending the dead-code | |
| # cleanup track. vulture_whitelist.py is the existing | |
| # opt-out file at the repo root. | |
| run: | | |
| uv tool run vulture src/ vulture_whitelist.py \ | |
| --min-confidence 70 \ | |
| > vulture.txt || true | |
| wc -l vulture.txt | |
| - name: Convert vulture output to SARIF | |
| run: | | |
| python scripts/text_to_sarif.py \ | |
| --tool vulture \ | |
| --input vulture.txt \ | |
| --output vulture.sarif | |
| - name: Drop suppressed SARIF results | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| vulture.sarif > vulture.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: vulture.filtered.sarif | |
| category: vulture | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: vulture-sarif | |
| path: | | |
| vulture.sarif | |
| vulture.txt | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| refurb: | |
| # Outdated-idiom detection. refurb flags constructs that have | |
| # cleaner modern Python equivalents (e.g. `list(map(...))` -> | |
| # list comprehension). Advisory. | |
| name: refurb (idioms) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install refurb (isolated env) | |
| run: uv tool install refurb | |
| - name: Run refurb (capture text output) | |
| run: | | |
| uv tool run refurb src/ > refurb.txt || true | |
| wc -l refurb.txt | |
| - name: Convert refurb output to SARIF | |
| run: | | |
| python scripts/text_to_sarif.py \ | |
| --tool refurb \ | |
| --input refurb.txt \ | |
| --output refurb.sarif | |
| - name: Drop suppressed SARIF results | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| refurb.sarif > refurb.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: refurb.filtered.sarif | |
| category: refurb | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: refurb-sarif | |
| path: | | |
| refurb.sarif | |
| refurb.txt | |
| if-no-files-found: warn | |
| retention-days: 14 | |
| perflint: | |
| # Hot-path antipattern detection. perflint flags patterns that | |
| # have measurable cost in tight loops (string concat in loops, | |
| # try/except in hot paths, etc.). Matters for the orchestrator | |
| # tick loop. Advisory. | |
| name: perflint (hot-path antipatterns) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden runner (audit mode) | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 | |
| with: | |
| persist-credentials: false | |
| - name: Set up uv | |
| uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0 | |
| with: | |
| enable-cache: true | |
| - name: Install perflint (isolated env) | |
| # perflint is a pylint plugin; install both so pylint can | |
| # load the perflint checkers. | |
| run: uv tool install perflint --with pylint | |
| - name: Run perflint (capture text output) | |
| run: | | |
| uv tool run --from perflint pylint \ | |
| --load-plugins=perflint \ | |
| --disable=all \ | |
| --enable=W8101,W8102,W8201,W8202,W8203,W8204,W8205,W8206,W8301 \ | |
| --output-format=parseable \ | |
| --score=n \ | |
| --reports=n \ | |
| src/ > perflint.txt || true | |
| wc -l perflint.txt | |
| - name: Convert perflint output to SARIF | |
| run: | | |
| python scripts/text_to_sarif.py \ | |
| --tool perflint \ | |
| --input perflint.txt \ | |
| --output perflint.sarif | |
| - name: Drop suppressed SARIF results | |
| if: always() | |
| run: | | |
| python3 scripts/sarif_drop_suppressed.py \ | |
| perflint.sarif > perflint.filtered.sarif | |
| - name: Upload SARIF to Code Scanning | |
| if: always() | |
| uses: github/codeql-action/upload-sarif@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4 | |
| with: | |
| sarif_file: perflint.filtered.sarif | |
| category: perflint | |
| - name: Upload SARIF as workflow artifact | |
| if: always() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: perflint-sarif | |
| path: | | |
| perflint.sarif | |
| perflint.txt | |
| if-no-files-found: warn | |
| retention-days: 14 |