Docs / CI

GitHub Actions integration

Run governed Agentic scenarios on every pull request. The action dispatches your chosen scenarios, polls until they finish, and fails the build on regressions.

1

Add secrets to your repo

In your GitHub repository settings → Secrets and variables → Actions, add:

  • ASSERTIVE_PROJECT_ID — your Assertive project UUID
  • ASSERTIVE_CI_HMAC_SECRET — a shared random string (also saved in your Assertive backend)
  • ASSERTIVE_SCENARIO_IDS — comma-separated scenario UUIDs (a repo variable is fine)

Generate the HMAC secret with openssl rand -hex 32 and paste the same value in both places.

2

Drop in the workflow

Save this as .github/workflows/assertive.yml:

yaml
name: Assertive QA
on:
  pull_request:
  workflow_dispatch:

jobs:
  qa:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Assertive scenarios
        uses: vancoregroup/assertive-action@v1
        with:
          endpoint: https://assertive.tech
          project_id: ${{ secrets.ASSERTIVE_PROJECT_ID }}
          scenario_ids: ${{ vars.ASSERTIVE_SCENARIO_IDS }}
          hmac_secret: ${{ secrets.ASSERTIVE_CI_HMAC_SECRET }}
          poll_timeout_seconds: "900"
3

Dispatch manually with curl

The endpoint is HMAC-signed. Reject responses that come back with 401 — the signature or secret is off.

bash
BODY='{"project_id":"<uuid>","scenario_ids":["<uuid>"],"triggered_by":"manual"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$ASSERTIVE_CI_HMAC_SECRET" | awk '{print $2}')

curl -X POST https://assertive.tech/api/public/hooks/ci-dispatch \
  -H "Content-Type: application/json" \
  -H "x-assertive-signature: $SIG" \
  --data "$BODY"
4

Poll for status

The dispatch response returns { run_refs: [...] }. Poll each ref:

bash
curl https://assertive.tech/api/public/runs/RUN-000042/status
# → { "run_ref": "RUN-000042", "status": "running", ... }

Terminal statuses: passed, failed, cancelled.

5

Run in your own perimeter (on-prem)

For staging URLs behind a VPN, or when credentials must never leave your network, run the open-source Assertive Runner daemon. It pulls scenarios from Assertive, executes them locally with Stagehand against a real browser, and posts back only sanitized results.{{secret.KEY}} placeholders are resolved from a local env file — the cloud never sees the values.

Create a runner-scoped API key under Settings → API keys → Runner, then set mode: local:

yaml
name: Assertive QA (on-prem)
on:
  pull_request:
  workflow_dispatch:

jobs:
  qa:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run Assertive scenarios on this runner
        uses: vancoregroup/assertive-action@v2
        with:
          mode: local
          endpoint: https://assertive.tech
          project_id: ${{ secrets.ASSERTIVE_PROJECT_ID }}
          runner_key: ${{ secrets.ASSERTIVE_RUNNER_KEY }}
          llm_key: ${{ secrets.LLM_API_KEY }}
          # scenario_ids omitted = run all scenarios in the project

Or run the daemon directly on a host inside your network:

bash
# .assertive.env — keep on the runner host, never commit
ASSERTIVE_API_KEY=ast_...        # runner-scoped key (Settings → API keys → Runner)
ASSERTIVE_ENDPOINT=https://assertive.tech
ASSERTIVE_PROJECT_ID=<uuid>
LLM_API_KEY=...                  # your own key; prompts stay on this host
ASSERTIVE_INTERNAL_BASE=https://staging.internal.example.com  # optional grounding
ASSERTIVE_CONCURRENCY=4          # parallel workers (1 = serial, max 8, clamped by project cap)
6

Generate scenarios on-prem (self-hostable)

The daemon can also generate scenarios entirely on your host, for internal/staging sites the cloud cannot reach. It crawls the target with a local browser, builds a domain brief and scenario prompts using the same core as the cloud, calls your OpenAI-compatible LLM, and uploads only the synthesized scenario definitions for governance — no DOM, no credentials, no page content leaves the host.

bash
# .assertive.env — on-prem generation
ASSERTIVE_API_KEY=ast_...        # runner-scoped key
ASSERTIVE_ENDPOINT=https://assertive.tech
ASSERTIVE_PROJECT_ID=<uuid>
LLM_API_KEY=...                  # your own OpenAI-compatible key
LLM_BASE_URL=http://localhost:11434/v1   # optional: local Ollama / vLLM
LLM_MODEL=gpt-4o-mini                   # optional: model id
ASSERTIVE_GENERATE_URL=https://staging.internal.example.com   # target to scan
ASSERTIVE_GENERATE_COUNT=6              # scenarios to synthesize
ASSERTIVE_GENERATE_RISK=balanced        # balanced | high | broad

Then run assertive-runner generate. The generated scenarios are synced to the project and appear in Test Staging, pinned to the current Assertive version so future regenerations skip them unless you explicitly unpin.

7

Runner health and heartbeats

While it is running, the daemon sends a heartbeat every 30 seconds so you can see which on-prem runners are online and what they are executing, live under Settings → Security → On-prem runners. Only operational metadata leaves your host: instance id, hostname, version, platform, status, and the run reference the cloud already issued — never page content, credentials or secrets.

bash
# Optional heartbeat tuning
ASSERTIVE_RUNNER_LABEL="London build box"   # friendly name in the health view
ASSERTIVE_HEARTBEAT_SECONDS=30              # 5-600, default 30
ASSERTIVE_DISABLE_HEARTBEAT=1               # opt out entirely (no liveness view)

A runner shows as online while beats arrive, stale after a couple of missed beats, and offline beyond that. Shutting the daemon down sends a final beat so it is marked offline immediately.

8

Signed releases and auto-update

Every Runner release is published with an Ed25519-signed manifest at /api/public/runner/releases. The signing public key is compiled into the daemon, and each artifact carries a SHA-256 recorded inside the signed payload — so a tampered download fails even if the CDN or transport is compromised. Downgrades are refused and the binary swap is atomic, with automatic rollback if it is interrupted.

bash
# Check the signed release channel (verifies signature + checksum)
assertive-runner update

# Install the verified update
assertive-runner update --apply

# Opt in to automatic updates on startup
ASSERTIVE_AUTO_UPDATE=1 assertive-runner run

# Air-gapped hosts: never phone home
ASSERTIVE_SKIP_UPDATE_CHECK=1 assertive-runner run

Auto-apply is opt-in: with ASSERTIVE_AUTO_UPDATE=1 the daemon installs verified updates on startup; otherwise it only tells you one exists. Set ASSERTIVE_SKIP_UPDATE_CHECK=1 for fully air-gapped hosts. Nothing self-modifies inside your perimeter unless you ask for it.

9

Security boundaries: what leaves your network

The daemon is a test executor, not a collector. Every outbound payload passes through the sanitizer, which redacts credential-shaped values, tokens and common PII patterns before anything is posted. This is the exact list.

Sent to Assertive

  • Run outcome per scenario: passed / failed / cancelled, plus the run reference the cloud already issued.
  • Timing and cost telemetry: duration, runner seconds, token counts, estimated cost.
  • Per-step telemetry: the step index, the sanitized step text as authored in Assertive, its status and duration.
  • Sanitized log lines and error messages, with credential-shaped values, tokens and PII patterns redacted.
  • Heartbeat metadata: instance id, hostname or label, daemon version, platform, status, runs completed.
  • Optional anonymized page structure (headings, link hrefs, form field names, button labels) — only when you set an internal base for grounding.
  • Optional synthesized scenario definitions — only when you run the generate command.

Never leaves your host

  • Secret values: {{secret.KEY}} placeholders resolve from your local env file in memory and are never transmitted or logged.
  • Persona passwords, MFA seeds and any browser-stored session, cookie or token.
  • Page HTML, DOM snapshots, screenshots, traces, HAR files and network bodies — captured locally only, if at all.
  • Your LLM API key and the prompts sent to it; the model call goes from your host to the endpoint you configure.
  • Internal hostnames and URLs beyond the base you explicitly configure for grounding.
  • Anything not on the list above — the daemon has no ambient monitoring or analytics collector.

Outbound endpoints are limited to /api/runner/scenarios, /api/runner/results, /api/runner/heartbeat, /api/runner/grounding (only when you set an internal base), /api/runner/scenarios/sync (only for on-prem generation) and the signed release manifest. Your own LLM endpoint is the only other destination, and you choose it. Every call is recorded in Runner activity so you can audit exactly what was delivered.

With parallel execution each worker runs in its own browser instance, so sessions, cookies and resolved secrets are never shared between concurrent scenarios.

Need a public share link for the run?

Toggle any run public and share /r/RUN-XXXXXX. See Pricing for parallel execution and SSO.