name: DAST smoke (PR) on: pull_request: branches: ["main", "release/**"] # Runner-cost guard (#8084): the CLI-bundle build alone is 6-11min; a docs-only PR # cannot change DAST behavior, so skip the whole workflow for pure docs/markdown # changes. Any code path in the diff still runs the full smoke. paths-ignore: - "docs/**" - "**/*.md" permissions: contents: read # Superseded runs on the same PR must not stack 25-minute advisory builds # (force-push storms were holding 2-3 runners each). Same group rule as quality.yml. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: dast-smoke: runs-on: ubuntu-latest # ADVISORY while this new gate matures (repo convention: advisory -> blocking). # Flip to blocking (remove continue-on-error) once it's proven stable across a few PRs. continue-on-error: true # Build CLI bundle alone varies 6-11min on GitHub-hosted runners (3 consecutive # timeouts observed on 2026-07-14 with the old 12min cap killing schemathesis # mid-run) — 25min leaves real headroom for the actual DAST steps. timeout-minutes: 25 env: JWT_SECRET: ci-build-secret-with-sufficient-length-for-validation API_KEY_SECRET: ci-api-key-secret-with-sufficient-length-aaaa steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 with: node-version: "24" cache: npm - uses: ./.github/actions/npm-ci-retry - name: Build CLI bundle env: OMNIROUTE_BUILD_BACKEND_ONLY: "1" run: npm run build:cli - name: Start OmniRoute env: PORT: "20128" INJECTION_GUARD_MODE: block run: | node dist/server.js > server.log 2>&1 & echo $! > server.pid for _ in $(seq 1 30); do if curl -sf http://localhost:20128/api/monitoring/health >/dev/null; then echo up; break; fi sleep 2 done - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" - run: pip install schemathesis - name: Schemathesis smoke (high-risk endpoints, blocking) run: | # /api/auth/oidc/* is a BROWSER redirect flow (302 to the IdP, 302 back to # /login?oidc_error=... on every failure), not a REST endpoint: Schemathesis reads # those 302s as "the API accepted a schema-violating request" and the configured-off # 400 as "rejected a schema-compliant request". Documenting the flow in the spec is # still right (operators need it); fuzzing it is not what this smoke is for. schemathesis run docs/openapi.yaml --url http://localhost:20128 \ --include-path-regex '^/v1/(chat/completions|models)$|^/api/(auth|keys)' \ --exclude-path-regex '^/api/auth/oidc/' \ --max-examples 8 --workers 4 --checks all --max-response-time 30 \ --request-timeout 20 --suppress-health-check all --no-color - name: promptfoo injection-guard (blocking) env: OMNIROUTE_URL: http://localhost:20128 OMNIROUTE_API_KEY: not-needed-blocked-before-upstream run: npx --yes promptfoo@latest eval -c promptfooconfig.yaml --no-cache - name: Stop server if: always() run: kill "$(cat server.pid)" || true - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 if: always() with: name: dast-smoke-logs path: server.log retention-days: 7