Skip to content

Always-mint session cookie, rename _bs_verified -> _bs_session #37

Always-mint session cookie, rename _bs_verified -> _bs_session

Always-mint session cookie, rename _bs_verified -> _bs_session #37

Workflow file for this run

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