Always-mint session cookie, rename _bs_verified -> _bs_session #37
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: CI | |
| # Per-PR gate: two jobs. `test-fast` runs the pytest suite minus the | |
| # @browser layer; `test-browser` runs only the @browser tests on its | |
| # own runner so the Chromium binary can be cached independently of | |
| # everything else. The 60-second soak smoke rides along in test-fast. | |
| # | |
| # Manual-only jobs (workflow_dispatch via the GitHub Actions "Run | |
| # workflow" button): `nightly` runs the 8h soak; `fuzz-nightly` | |
| # runs 30 min each of the cookie + robots LibFuzzer harnesses. | |
| # Neither fires on a schedule — runner-minute cost outweighs the | |
| # signal at this project's cadence; kick them off before a release | |
| # or after a parser change. | |
| on: | |
| # Skip pushes/PRs that only touch docs — repeated `make docs` | |
| # iterations or markdown tweaks shouldn't burn a runner. CI still | |
| # fires the moment a code/test/config file is in the diff | |
| # alongside the docs change. workflow_dispatch is always | |
| # available for manual override. | |
| pull_request: | |
| branches: [ main ] | |
| paths-ignore: | |
| - 'docs/**' | |
| - 'docs-src/**' | |
| - 'site-src/**' | |
| - '**/*.md' | |
| - 'LICENSE' | |
| push: | |
| branches: [ main ] | |
| paths-ignore: | |
| - 'docs/**' | |
| - 'docs-src/**' | |
| - 'site-src/**' | |
| - '**/*.md' | |
| - 'LICENSE' | |
| # Manual trigger only — used to fire the 8h soak and the fuzz | |
| # campaigns (each can spend hours of runner time, so we don't run | |
| # them on a cron). Also lets you re-run the whole CI on a docs-only | |
| # commit when you're done iterating and want one final check. Hit | |
| # the "Run workflow" button in GitHub Actions to invoke. | |
| workflow_dispatch: | |
| env: | |
| # Shared env so provision + tests use the same paths. | |
| BS_BASE: "https://localhost" | |
| jobs: | |
| # -------- Per-PR gate: fast tests + short soak smoke. -------- | |
| test-fast: | |
| name: pytest (not browser) + short soak | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| # Live-provider tests need the secrets below. Without them the | |
| # tests SKIP (provision.sh installs the publicly-documented | |
| # always-pass keys for Turnstile / hCaptcha / reCAPTCHA v2, which | |
| # is enough for the most critical integration paths to run). Real | |
| # v3 / Friendly / GeeTest exercise only the plumbing branches | |
| # unless the OK-branch env vars below are set. | |
| env: | |
| BS_RECAPTCHA_V3_TOKEN: ${{ secrets.BS_RECAPTCHA_V3_TOKEN }} | |
| BS_FRIENDLY_SOLUTION: ${{ secrets.BS_FRIENDLY_SOLUTION }} | |
| BS_GEETEST_TOKEN: ${{ secrets.BS_GEETEST_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Provision | |
| run: sudo tests/setup/provision.sh | |
| - name: Start Apache | |
| run: | | |
| sudo systemctl restart apache2 | |
| sleep 2 | |
| curl -sk --max-time 5 -o /dev/null https://localhost/ \ | |
| || (sudo journalctl -u apache2 --no-pager -n 50 && exit 1) | |
| - name: Run fast tests (pytest, not browser, not slow) | |
| run: tests/run --parallel --mark "not browser" --verbose | |
| - name: Soak smoke (60s at 25 rps) | |
| # The soak is a pytest test marked @slow + @serial. Running | |
| # it via tests/run with --slow --match soak keeps it in the | |
| # same framework + reports pipeline as every other test. | |
| run: tests/run --slow --match soak --verbose | |
| - name: Upload reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: reports-fast | |
| path: tests/reports/ | |
| retention-days: 14 | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apache-logs-fast | |
| path: | | |
| /var/log/apache2/botshield-dev-error.log | |
| /var/log/apache2/error.log | |
| retention-days: 7 | |
| # -------- Per-PR gate: browser tests in an isolated job so the | |
| # Chromium install + cache is independent of the fast lane. -------- | |
| test-browser: | |
| name: pytest (browser) — headless Chromium | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| # Cache the Chromium binary across runs. provision.sh will | |
| # `playwright install chromium` but it becomes a no-op when | |
| # the cache hit populates ~/.cache/ms-playwright. | |
| - name: Restore Playwright cache | |
| id: pw-cache | |
| uses: actions/cache@v4 | |
| with: | |
| path: ~/.cache/ms-playwright | |
| key: playwright-${{ runner.os }}-v1-${{ hashFiles('tests/requirements-test.txt') }} | |
| - name: Provision | |
| run: sudo tests/setup/provision.sh | |
| - name: Start Apache | |
| run: | | |
| sudo systemctl restart apache2 | |
| sleep 2 | |
| - name: Run browser tests (chromium) | |
| run: tests/run --parallel --mark "browser" --verbose | |
| - name: Upload reports | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: reports-browser | |
| path: | | |
| tests/reports/ | |
| tests/test-results/ | |
| retention-days: 14 | |
| - name: Upload logs on failure | |
| if: failure() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apache-logs-browser | |
| path: | | |
| /var/log/apache2/botshield-dev-error.log | |
| /var/log/apache2/error.log | |
| retention-days: 7 | |
| # -------- Per-PR gate: docs/ is in sync with sources. -------- | |
| # GitHub Pages publishes from main:/docs, so docs/ has to be | |
| # committed and rebuilt whenever docs-src/ or site-src/ changes. | |
| # This job rebuilds the site and fails if the working tree shows | |
| # any diff vs the committed docs/ — i.e., the author forgot to | |
| # rebuild before pushing. | |
| docs-fresh: | |
| name: docs/ is up to date with sources | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 5 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Set up Python | |
| uses: actions/setup-python@v5 | |
| with: | |
| python-version: "3.x" | |
| - name: Install docs build deps | |
| run: pip install -r tools/requirements-docs.txt | |
| - name: Rebuild docs/ | |
| run: make docs | |
| - name: Fail if rebuild produced changes | |
| run: | | |
| if ! git diff --quiet docs/; then | |
| echo "::error::docs/ is out of sync with docs-src/ or site-src/." | |
| echo "Run 'make docs' locally and commit the result before pushing." | |
| git --no-pager diff --stat docs/ | |
| exit 1 | |
| fi | |
| # -------- Manual: 8h soak. -------- | |
| # Available via the "Run workflow" button in GitHub Actions; not | |
| # wired to any automatic trigger. 8 h × every-day burns runner | |
| # minutes faster than the value warrants for a small project, so | |
| # the cron was retired — kick it off manually before a release. | |
| nightly: | |
| name: 8h soak (manual) | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 540 # 9h: 8h soak + provision overhead | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Provision | |
| run: sudo tests/setup/provision.sh | |
| - name: Start Apache | |
| run: | | |
| sudo systemctl restart apache2 | |
| sleep 2 | |
| - name: Full soak (8h at 50 rps) | |
| env: | |
| BS_SOAK_DURATION_SEC: "28800" # 8h | |
| BS_SOAK_RPS: "50" | |
| BS_SOAK_REPORT: /tmp/bs_soak_nightly.report | |
| run: tests/run --slow --match soak --verbose | |
| - name: Upload soak report | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: soak-nightly-report | |
| path: | | |
| /tmp/bs_soak_nightly.report | |
| tests/reports/ | |
| retention-days: 30 | |
| - name: Upload Apache logs | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: apache-logs-nightly | |
| path: | | |
| /var/log/apache2/botshield-dev-error.log | |
| /var/log/apache2/error.log | |
| retention-days: 14 | |
| # -------- Manual: LibFuzzer campaigns. -------- | |
| # Two harnesses run sequentially within one job: fuzz_cookie | |
| # (HMAC + GCM cookie parser via _fuzz_stubs.h) and fuzz_robots | |
| # (robots.c parser, APR-only, no httpd dependency). 30 minutes | |
| # each is enough to surface meaningful coverage on a mature | |
| # corpus; longer campaigns belong on a dedicated runner. On a | |
| # finding, the harness writes crash-/leak-/slow-unit-/timeout- | |
| # <hash> reproducers next to itself in tests/fuzz/, which the | |
| # artifact upload picks up. | |
| # | |
| # Manual-only — kick off via "Run workflow" before merging a | |
| # parser change or before a release. | |
| fuzz-nightly: | |
| name: fuzz (cookie + robots, 30m each, manual) | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| timeout-minutes: 90 | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - name: Install clang + libfuzzer | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y clang libclang-rt-dev pkg-config \ | |
| libapr1-dev libcrypto++-dev libssl-dev | |
| - name: Build harnesses | |
| run: | | |
| make fuzz | |
| make fuzz-robots | |
| - name: Fuzz cookie parser (30 min) | |
| run: tests/fuzz/run.sh --target cookie 1800 | |
| - name: Fuzz robots parser (30 min) | |
| run: tests/fuzz/run.sh --target robots 1800 | |
| - name: Upload corpus + reproducers | |
| if: always() | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: fuzz-nightly-artifacts | |
| path: | | |
| tests/fuzz/corpus/ | |
| tests/fuzz/corpus-robots/ | |
| tests/fuzz/crash-* | |
| tests/fuzz/leak-* | |
| tests/fuzz/timeout-* | |
| tests/fuzz/slow-unit-* | |
| retention-days: 30 | |
| if-no-files-found: ignore |