Skip to content
Generated from cxg --help. Report problems against cert-x-gen, not this page.

cxg pentest

The AI-driven whitebox pentest pipeline, its auth subcommands, and every flag.

AI-driven whitebox pentest pipeline (guardlink source code → authenticated browser execution)

Reads whitebox/findings.sarif produced by guardlink, ranks threats against an operator goal, and asks your local AI CLI (claude / codex / gemini) to write JavaScript probe templates that read the target’s source to craft code-aware payloads. Those templates execute in N parallel authenticated Chromium contexts, emitting confirmed/refuted/ambiguous findings to a JSON report plus a JSONL audit log of every HTTP request.

Capabilities:

  • Interactive auth capture for SSO/MFA flows (no need to script logins)
  • Chained-auth probes (IDOR cross-user) via --auth-numbers 2+
  • Pre-flight identity inspection — landing-test based, no /me-path required
  • Goal-driven LLM ranking of guardlink hypotheses
  • Validator-guarded code-generation with hard 240s timeout per AI call
  • Retry-with-mutation on AMBIGUOUS triage (max N retries, env-bound skip)
  • Scope enforcement (URL/method allowlist, per-endpoint budget, 5xx hard-kill)
  • Cookie-jar primitives in templates (HttpOnly-aware via Playwright)
  • Out-of-band callbacks in two modes: inject a canary cxg cannot read (--oast), or register one cxg owns and polls (--oast-interactsh) — only the second yields an in-band blind-vuln confirmation
  • Per-profile custom headers (WAF bypass, internal-test headers)
  • Split report: confirmed_findings vs mitigation_verifications vs ambiguous
cxg pentest [OPTIONS] <COMMAND>
Subcommand Summary
install Install the Python orchestrator into ~/.cert-x-gen/pentest/
auth Capture — or, via a subcommand, import/verify — an authenticated session profile
auth-list List saved auth profiles under ~/.cert-x-gen/auth/
scope-init Write an example scope.yaml the operator can edit
run Run the full end-to-end pentest pipeline against a target

The global options apply here too.

Install the Python orchestrator into ~/.cert-x-gen/pentest/

One-time setup. Copies the Python orchestrator bundled with the cxg source tree to ~/.cert-x-gen/pentest/, then verifies the required Python deps (playwright, anthropic) are installed. Must be run before cxg pentest auth or cxg pentest run.

Examples: cxg pentest install cxg pentest install --force # reinstall after pulling new cxg source
cxg pentest install [OPTIONS]
Option Description
--force Reinstall even if ~/.cert-x-gen/pentest/ already exists. Use after updating the cxg source tree or pulling new Python modules

The global options apply here too.

Capture — or, via a subcommand, import/verify — an authenticated session profile

With no subcommand this captures interactively: it spawns a real headed Chromium window. You log in by ANY means — username/password, SSO redirect, MFA prompt, hardware key, magic link, OAuth popup. When the app dashboard is showing, press ENTER in the terminal (or close the browser) and cxg snapshots cookies + localStorage + sessionStorage from every origin the browser touched. The profile is saved at ~/.cert-x-gen/auth/<profile>.json plus metadata at <profile>.meta.json.

A post-capture landing-test verifies the session works: it re-opens a fresh browser context with the saved state, navigates to --target, and checks whether the final URL/page looks like a login or the app dashboard.

Use --auth-numbers N for chained-auth scenarios (e.g. IDOR testing needs two different identities). cxg prompts for a human-readable label for each capture.

For CI, where no human can drive a browser, use the subcommands instead: cxg pentest auth import replays a session captured once and exported as a Playwright storage_state, with no browser; cxg pentest auth verify checks a saved session is still alive (exit 0 alive / non-zero dead) before a run spends any AI budget.

Examples: # Single interactive capture (no subcommand) cxg pentest auth --target https://app.example.com --profile admin
# Two identities for chained-auth (e.g. IDOR victim + attacker) cxg pentest auth --target https://app.example.com --profile pentest --auth-numbers 2
# Scripted login (no browser pop-up) cxg pentest auth --target https://app.example.com --profile bot \ --creds 'user@example.com:hunter2' --login-path /api/auth/login
# Capture a profile that sends a WAF-bypass header on every request cxg pentest auth --target https://app.example.com --profile pentest \ --header "x-test-automation:abc123xyz"
# CI: import a session captured once, then verify it before a run cxg pentest auth import --profile pentest --target https://staging.app \ --storage-state ./pentest.storage.json cxg pentest auth verify --profile pentest
cxg pentest auth [OPTIONS]
Subcommand Summary
import Import a saved Playwright storage_state as a profile (no browser)
verify Check a saved web session is still alive (exit 0 alive / non-zero dead)
Option Description
--target <TARGET> Target URL where login happens (e.g. https://app.example.com). After capture, the landing-test verifier opens this URL with the saved cookies; if it doesn’t bounce to a login page, the session is alive. Required for interactive capture (no subcommand). The import/verify subcommands take their own --target instead.
--profile <PROFILE> Profile name. If --auth-numbers > 1, this is the PREFIX and final profiles are named <profile>-1, <profile>-2, etc. Use a stable name so subsequent cxg pentest run --auth <profile> invocations can reload it. Required for interactive capture (no subcommand).
--auth-numbers <AUTH_NUMBERS> Number of profiles to capture in sequence. Each capture opens its own Chromium window and prompts for a label (e.g. “victim”, “attacker”, “admin”). Required ≥2 for chained-auth probes like IDOR cross-user tests Default: 1.
--creds <CREDS> Inline credentials email:password for scripted login (skips browser pop-up). Only works for password-based auth — SSO/MFA flows MUST use interactive capture
--creds-file <CREDS_FILE> File with profile:email:password[:label] per line for scripted batch capture. Useful for re-auth between scans of password-only test apps
--login-path <LOGIN_PATH> Endpoint path for scripted login POST. Only used with --creds or --creds-file. The captured browser will POST {email, password} as JSON to <target><login-path> Default: /api/auth/login.
--label <LABEL> Human-readable label for the profile (e.g. “admin”, “low-priv-user”). Shown in scan output to make findings readable. Does not affect behavior
--verify-url <VERIFY_URL> Optional URL probed after capture as a secondary identity check. The landing test is the primary signal regardless. Default: empty (landing test only). Pass an empty string to skip the secondary probe entirely
--header <NAME:VALUE> Custom HTTP header NAME:VALUE sent on every outbound request from this profile’s browser context — including login, every scan probe, every health check. Repeatable for multiple headers. Saved with the profile so future cxg pentest run --auth <profile> invocations reuse them automatically. Use case: WAF-bypass tokens granted by infra (e.g. x-test-automation), internal-test headers, custom forwarding hints. SECURITY: header values are stored in plaintext under ~/.cert-x-gen/auth/<profile>.meta.json. Treat the file like a credential. chmod 600 if your machine has multiple users. Delete the profile when the engagement ends. Header values are sent to EVERY origin the browser touches, including IDPs — keep this in mind for SSO flows.
--tier <TIER> Explicit privilege rank for this identity: an integer 0-100 or an alias high/medium/low (= 90/50/10). Overrides the role-based heuristic and is fed to the AI ranker so it selects the right identity per probe (lowest tier for privesc, etc.). Omit to auto-derive from the app’s role. In multi-capture (--auth-numbers > 1) this pre-fills the per-identity prompt
--persona <PERSONA> Semantic role hint (e.g. “billing-analyst”) passed to the AI as extra context — useful when the app’s /me endpoint exposes no clear role field
--cohort <COHORT> Peer-group name (e.g. “analyst-team-a”). Give two SAME-permission, DIFFERENT-user sessions the same cohort so the AI treats them as peers and targets horizontal IDOR / cross-tenant access between them
--tag <NAME=VALUE> Free-form NAME=VALUE context attached to the profile and shown to the AI. Repeatable
--target-type <TARGET_TYPE> Target type to capture auth for. web (default): the existing authenticated-browser capture against an HTTP application. electron: launch the Electron desktop app and capture its session state instead of a browser’s. Tauri is not supported — it exposes no CDP endpoint on macOS or Linux. Default: web.
--app-cmd <APP_CMD> Command that launches the desktop app, e.g. “npm run electron:dev”. Required with --target-type electron unless --app-binary is given. cxg appends --remote-debugging-port and a per-identity --user-data-dir.
--app-binary <APP_BINARY> Path to a built desktop app, e.g. /Applications/Foo.app. Alternative to --app-cmd; the two are mutually exclusive.

The global options apply here too.

Import a saved Playwright storage_state as a profile (no browser)

The CI-replay primitive: a human captures a session once locally (SSO, MFA and all), exports the Playwright storage_state, and every pipeline run injects it here without a display or a human. The written profile is byte-for-byte what an interactive capture produces, so a later cxg pentest run --auth <profile> cannot tell an imported profile from a captured one.

Examples: # From a file on disk cxg pentest auth import --profile pentest --target https://staging.app \ --storage-state ./pentest.storage.json
# From stdin (a CI secret piped straight in, never written to disk in the clear) cat "$SESSION_SECRET" | cxg pentest auth import --profile pentest \ --target https://staging.app --storage-state -
# From the base64 env var, into a restored bundle dir, CI-strict cxg pentest auth import --profile pentest --target https://staging.app \ --auth-dir ./ci-auth --ci
cxg pentest auth import [OPTIONS] --profile <PROFILE> --target <TARGET>
Option Description
--profile <PROFILE> Profile name. A subsequent cxg pentest run --auth <profile> loads it exactly like a captured one
--target <TARGET> Target URL this session authenticates against. Recorded in the profile and used by auth verify / run pre-flight as the landing-test URL
--storage-state <PATH|-> Path to a Playwright storage_state JSON, or - to read it from stdin (a CI secret pipes straight in, never touching disk in plaintext beyond the profile dir). Omit to materialise it from the base64 env var CXG_AUTH_STATE_<NAME> instead (NAME is the profile name uppercased with non-alphanumerics folded to underscore — e.g. profile pentest-alice reads CXG_AUTH_STATE_PENTEST_ALICE)
--label <LABEL> Human-readable label (e.g. ‘admin’), shown in scan output
--tier <TIER> Explicit privilege rank: an integer 0-100 or an alias high/medium/low. Same meaning as on capture
--persona <PERSONA> Semantic role hint passed to the AI as extra context
--cohort <COHORT> Peer-group name for horizontal IDOR / cross-tenant tests
--tag <NAME=VALUE> Free-form NAME=VALUE context attached to the profile. Repeatable
--header <NAME:VALUE> Custom HTTP header NAME:VALUE sent on every request from this profile (e.g. a WAF-bypass token). Repeatable. Stored as a credential; values are never echoed
--auth-dir <AUTH_DIR> Write into this auth directory instead of ~/.cert-x-gen/auth. A pipeline restores a bundle of profiles into one dir and points the run at it with the same flag
--ci Non-interactive CI mode: refuse a world-accessible --auth-dir rather than write a credential a fellow user could read. Also enabled by CXG_CI=1

The global options apply here too.

Check a saved web session is still alive (exit 0 alive / non-zero dead)

An out-of-band liveness gate for a saved session, reusing the same landing-page test scan-time pre-flight uses. A pipeline runs this before spending a whole run, and re-captures (or re-imports) when it fails.

Example: cxg pentest auth verify --profile pentest || echo "session expired, re-import"
cxg pentest auth verify [OPTIONS] --profile <PROFILE>
Option Description
--profile <PROFILE> Profile name to verify
--target <TARGET> Override the profile’s saved target for the landing test
--me-path <ME_PATH> Secondary identity endpoint probed for role/email; the landing test is the source of truth. Default /api/me Default: /api/me.
--auth-dir <AUTH_DIR> Read the profile from this auth directory instead of ~/.cert-x-gen/auth

The global options apply here too.

List saved auth profiles under ~/.cert-x-gen/auth/

Shows profile name, label, target URL, and whether extra_headers are present. Useful before running cxg pentest run --auth <name> to confirm which profile you’ll be scanning as.

cxg pentest auth-list [OPTIONS]

The global options apply here too.

Write an example scope.yaml the operator can edit

scope.yaml controls the safety rails for a scan: method allowlist (default GET/POST/HEAD/OPTIONS — DELETE/PUT/PATCH require --destructive-ok), URL allow/blocklist regexes, per-endpoint and total request budgets, 5xx-streak hard-kill threshold, and the authorization_attestation field that’s recorded in the audit log header.

Example: cxg pentest scope-init -o my-engagement-scope.yaml
cxg pentest scope-init [OPTIONS]
Option Description
-o, --output <OUTPUT> Output file path. Default: scope.yaml in the current directory Default: scope.yaml.

The global options apply here too.

Run the full end-to-end pentest pipeline against a target

Pipeline steps: [1] Load guardlink hypotheses from <codebase>/whitebox/findings.sarif [1b] Inspect captured auth profiles via landing-test (profile_inspect.inspect_profiles_async) [2] LLM-rank hypotheses against --goal + profile coverage; AI generates JS templates that read source via Claude/Codex/Gemini’s own Read/Grep tools [3] Load + statically validate templates (validator.py) [4] Open N parallel authenticated Chromium contexts; inject the cxg JS bridge [5] Run each template; triage findings (CONFIRMED / REFUTED / AMBIGUOUS); mutate-and-retry on payload-fixable AMBIGUOUS, skip environment-bound [6] Write report.json and audit.jsonl to --session-dir

The full set of probes available to templates is documented in pentest/docs/TEMPLATES.md. The runtime intelligence layer (validator, scope, session health, mutation, triage, audit) is in pentest/docs/ARCHITECTURE.md.

Examples: # Single-profile read-only scan with default settings cxg pentest run --codebase ./repo --target http://localhost:8000 \ --auth admin --ai --ai-provider claude
# Two identities for IDOR/chained-auth, AI off → built-in probes only cxg pentest run --codebase ./repo --target http://localhost:8000 \ --auth victim,attacker
# Verify mitigations only (skips unmitigated threats so you can confirm # declared defenses hold at runtime). Useful for well-annotated codebases. cxg pentest run --codebase ./repo --target https://staging.app \ --auth pentest --ai --ai-provider claude \ --mitigation-mode mitigated --max-templates 16
# SSRF testing against a canary cxg OWNS — it registers the interactsh # session and polls it, so a callback becomes a confirmed finding cxg pentest run --codebase ./repo --target https://staging.app \ --auth pentest --ai --ai-provider claude \ --oast-interactsh \ --goal "verify SSRF on /slack/proxy via OAST callback"
# Same probes, but against a canary cxg can only inject into (e.g. a Burp # Collaborator host). Templates build the URL; YOU read the hit in # Collaborator. Findings stay unconfirmed as far as cxg is concerned. cxg pentest run --codebase ./repo --target https://staging.app \ --auth pentest --ai --ai-provider claude \ --oast c4ca4238a0b92.oastify.com \ --goal "test SSRF on /slack/proxy; check Collaborator for callbacks"

Exit codes: 0 → no confirmed findings (clean scan) 1 → no templates available (guardlink output missing or empty) 2 → confirmed findings present 3 → scan was hard-killed (5xx streak, scope violation) OR, under --no-restart, a desktop target died mid-scan and was not relaunched 5 → CI mode (--ci / CXG_CI=1): an auth session was dead/expired at pre-flight, so the run stopped before spending any AI calls rather than silently probing UNAUTHENTICATED

cxg pentest run [OPTIONS] --codebase <CODEBASE> --target <TARGET>

Target

Option Description
--codebase <CODEBASE> Source codebase root (contains whitebox/findings.sarif)
--target <TARGET> Running target app URL (http:// or https://)
--target-type <TARGET_TYPE> Target type to pentest. web (default): the existing authenticated-browser pipeline against an HTTP application. electron: launch N isolated instances of an Electron desktop app, drive their renderers over CDP, and additionally probe IPC channels, renderer configuration, and local data at rest. Tauri is not supported — it exposes no CDP endpoint on macOS or Linux. EXAMPLES: cxg pentest run --target-type electron --app-cmd “npm run electron:dev” \ --codebase ./app-repo --target https://api.example.com --auth desk-1,desk-2 Default: web.
--app-cmd <APP_CMD> Command that launches the desktop app, e.g. “npm run electron:dev”. Required with --target-type electron unless --app-binary is given. cxg appends --remote-debugging-port and a per-identity --user-data-dir.
--app-binary <APP_BINARY> Path to a built desktop app, e.g. /Applications/Foo.app. Alternative to --app-cmd; the two are mutually exclusive.
--host-scan-path <HOST_SCAN_PATH> Additionally scan a real installation directory for data at rest. By default host probes read only the isolated user-data directories cxg created itself. Pass this to opt in to scanning an existing install.

Authentication

Option Description
--auth <AUTH> Comma-separated auth profile names previously captured via cxg pentest auth. Probes that need multiple identities (IDOR cross-user, session-replay-against- victim) require ≥2 profiles. The lowest-privilege identity is auto-selected as the actor for privesc-class probes. Leave empty if you’re using --interactive-auth to capture fresh profiles inline. Default: ``.
--auth-profile <AUTH_PROFILE> Name prefix for --interactive-auth captures Default: pentest.
--interactive-auth <INTERACTIVE_AUTH> Open N headed browser windows for interactive login at scan start, then run the pipeline with the resulting fresh profiles. Each capture prompts you for a per-identity label. Use this when your captured profiles have expired or when you don’t want to manage profile lifecycle separately. Profiles are saved as <--auth-profile>-1, <--auth-profile>-2, etc. Default: 0.
--auth-numbers <AUTH_NUMBERS> Minimum auth contexts the scan requires
--creds-file <CREDS_FILE> File of profile:email:password[:label] lines used to AUTO RE-AUTH a dead session mid-scan (the session-health monitor detects death via the landing test). Without this, a probe that kills its own session — e.g. session_replay’s logout — leaves remaining probes unable to run. Recommended for any scan with --mutation-retries > 0 or scans with SessionReplay / Csrf class templates.
--me-path <ME_PATH> Optional identity endpoint hit during pre-flight inspection to extract role/email/id from a JSON response. Failure here does NOT mark the profile dead — the landing test is the source of truth. Override only when you want role-tier-based probe selection AND the app has a stable /me endpoint. Default /api/me. Set to empty string to skip the secondary probe. Default: /api/me.
--skip-health-check Skip the pre-flight session health check entirely. Use when: • Multi-tenant app where the session lives at a subdomain different from --target • App has no stable /me-style endpoint • You know the session is fresh and want to skip the verification roundtrip Per-template health checks still run during the scan unless you also configure scope.yaml to disable them.
--auth-dir <AUTH_DIR> Read auth profiles from this directory instead of ~/.cert-x-gen/auth. A CI pipeline restores a bundle of profiles (imported via cxg pentest auth import --auth-dir <dir>) into its own directory and points the run at the same directory here, so pre-flight, the engine, and the health monitor all read the restored bundle rather than the operator’s home store.
--ci Non-interactive CI mode: fail loud instead of warning. A dead or expired auth session becomes a HARD FAIL (exit 5) at pre-flight rather than today’s warn-and-continue, so a pipeline never silently probes UNAUTHENTICATED and refutes real findings; and a world-accessible --auth-dir is refused rather than trusted. Without this flag the current warn behaviour is unchanged. --skip-health-check remains the documented escape hatch (it force-marks profiles alive, so the gate cannot trip). Also enabled by the environment variable CXG_CI=1, for pipelines that cannot add the flag to the invocation.

AI Generation

Option Description
--ai Enable AI-driven template generation
--ai-provider <AI_PROVIDER> AI provider. auto prefers the editor bridge when $BUGB_BRIDGE_URL is set, then the first available CLI tool (claude > codex > gemini), falling back to ANTHROPIC_API_KEY / OPENAI_API_KEY HTTP APIs. Otherwise specify explicitly. CLI providers don’t need API keys — they use your existing CLI auth. Options: auto | bridge | claude | codex | gemini | anthropic | openai bridge posts each prompt to $BUGB_BRIDGE_URL (with Authorization: Bearer $BUGB_BRIDGE_TOKEN when set) and reads the completion back — an editor/CI integration point rather than a local CLI. Default: claude.
--goal <GOAL> Natural-language pentest goal. Used as context for both LLM-based hypothesis ranking AND template generation. Be specific about which vuln classes, endpoints, or claims you want tested. Examples: “test for IDOR in records and transactions APIs” “verify each declared @mitigates actually holds at runtime” “find unguarded admin endpoints accessible to non-admin tokens”
--max-templates <MAX_TEMPLATES> Max templates the LLM ranker may select Default: 8.
--mutation-retries <MUTATION_RETRIES> Max AI mutation retries on AMBIGUOUS triage Default: 2.
--generation-timeout <GENERATION_TIMEOUT> Hard wall-clock timeout per AI generation call. The whole process tree is killed if exceeded. Default 240s. Claude CLI typically takes 60-170s per template; Codex 90-220s; Gemini 30-180s. Bump to 600+ for very large codebases the AI has to grep through. Default: 240.
--template-lang <TEMPLATE_LANG> Template language: js (default) or py Default: js.
--template-dir <TEMPLATE_DIR> Reuse JS templates from a previously-generated session directory. Skips the AI generation step entirely. Useful for re-running the same templates against different targets, or after fixing engine bugs that affected template execution. Template directories are at ~/.cert-x-gen/templates/session-<timestamp>/.
--mitigation-mode <MITIGATION_MODE> Hypothesis filter: any|unmitigated|mitigated Default: any.

Execution

Option Description
--scope-file <SCOPE_FILE> Path to scope.yaml (URL/method allowlist, budgets)
--destructive-ok Allow DELETE/PUT/PATCH methods AND paths matching destructive regexes (/wipe, /delete-all, /factory-reset, etc.) in scope.yaml. Default OFF. Required for templates that test destructive-action authz. USE WITH CARE on production targets. The engine emits a warning at startup and every blocked request is still recorded in the audit log.
--headed Show Chromium windows instead of running headless
--template-timeout <TEMPLATE_TIMEOUT> Per-template dispatch ceiling, seconds (backstop; default 900)
--stall-timeout <STALL_TIMEOUT> Idle-time stall timeout, seconds (electron only; default 90)
--no-restart Do NOT relaunch a desktop target that dies mid-scan. Default is to restart it (max 2 per instance, 3 per run), report the crash as a denial_of_service finding, re-probe the suspected IPC channel once and then quarantine it. With this flag a dead target ends the scan with a truncation caveat and exit 3 — the pre-recovery behaviour.
--attestation <ATTESTATION> Free-text written authorization statement recorded in the audit log header. Include engagement ID, operator name, change-management ticket, etc. Example: “Engagement PT-2026-003, operator J. Doe, ticket CHG-1234” Strongly recommended for any non-local scan. The audit log header is the dispute-ready record of “I had authorization to do this scan.”

OAST

Option Description
--oast <HOST> An external out-of-band callback host cxg can INJECT INTO but cannot read back (Burp Collaborator, a canary you host, an interactsh domain you generated yourself). Exposed to templates as cxg.oast.url(label, scheme?) so payloads carry a callback URL — but cxg never learns whether it fired, so nothing here confirms anything: blind probes (SSRF, blind SQLi, blind XXE, blind cmd-injection) still fall back to status-code heuristics and timing, and the AI prompt is instructed to mark them confirmed=false. Reading the callback is the operator’s job, in their own tooling. Want cxg to do the confirming? Use --oast-interactsh instead. Example: --oast c4ca4238a0b923820dcc.oastify.com If the host is an interactsh domain rather than a Collaborator one, run interactsh-client in another terminal first and paste the hostname it prints — that terminal is the only place these callbacks become visible. (With --oast-interactsh there is no second terminal: cxg is the client.)
--oast-interactsh [<SERVER_URL>] Register an interactsh session cxg OWNS and poll it — the mode that can actually confirm a blind vulnerability. Because cxg registered the session it holds the correlation id, so templates can call cxg.oast.poll(label), read the interactions back in-band, and a callback becomes a genuine confirmed=true finding with the interaction recorded as evidence. cxg is the interactsh client here; you do not run one alongside it. The optional value is the interactsh server to register against; omit it to use interactsh’s default public servers. Mutually exclusive with --oast. Two canaries would split payloads between a host cxg can poll and one it cannot, so “was this confirmed?” would have no single answer per finding. Examples: --oast-interactsh (default servers), or --oast-interactsh https://oast.example.internal (your own instance).

Output

Option Description
-o, --output <OUTPUT> JSON report output path (default report.json in session dir)
--session-dir <SESSION_DIR> Session artifacts directory (audit.jsonl, report.json)

The global options apply here too.