$ adversarial scan . --agent

The agent you already have is the infrastructure

Keyless adversarial scanning: deep triage through the AI coding agent already installed and authenticated in your environment. No API keys, no tenant signup, no second bill — and no code leaving the machine.

Published 2026-09-05

# the problem

Procurement is a security boundary

Every CI security tool you evaluate wants the same things before it scans a single line: an account, an API key, a tenant, a pricing conversation. That's friction, and it's not just annoyance — it's a boundary. Teams run un-scanned code for weeks because the approval for "yet another security SaaS" is still sitting in someone's queue. Meanwhile, the thing those tools charge you to rent — a capable model reading your code and judging it — is already sitting in your environment. Your engineers run AI coding agents locally, already authenticated, already trusted with the codebase.

So we asked a blunt question: if the model is already there, why are you renting another one? Keyless agent mode is the answer. adversarial scan --agent runs the deep-triage half of the scan through whichever agent CLI it finds on PATH — Claude Code, Codex, Gemini, OpenCode, or Cursor's cursor-agent, probed in that order. The CLI never contacts /api/scans in this mode, never sends file contents anywhere, and works fully offline.

# how it works

Deterministic candidates, judged locally

The split of labor is deliberate: the machine does what machines do reliably, and the agent does what requires judgment.

  1. The regex engine proposes

    The offline regex engine — compiled from the same pattern data as the attack library's 56 validated techniques across 10 categories — scans the target files and emits candidate findings. This part is fully deterministic: same input, same candidates, every time.

  2. The agent disposes

    Each candidate is handed to the local agent with the library's own context: the technique's prompt_template and mitigation, plus the matched file snippets. The agent reads the actual code and replies with a JSON verdict array — one object per candidate, verdict confirmed or refuted, with evidence and severity.

  3. The CLI merges — conservatively

    A refuted candidate is dropped. A confirmed candidate becomes a finding. A candidate the agent never returned a verdict for is kept, not cleared. Agent silence can never produce a false pass.

# Scan the current directory; use the first agent found on PATH
$ adversarial scan . --agent
note: running deep triage via 'claude'

# Pin a specific agent (use = so the name isn't swallowed as a path)
$ adversarial scan . --agent=codex

# Paths precede --agent: the flag's value is optional
$ adversarial scan src/ .github/workflows/ --agent

# Agent verdicts come back as a strict JSON array
[{"id":"ADV-0013","verdict":"confirmed",
  "evidence":"checkout runs untrusted code from the fork","severity":"critical"}]

$ echo $?

Exit codes you can gate on

Keyless mode keeps the same contract as every other scan, so jobs gate the same way:

0 — clean

Every candidate was refuted by the agent (or there were none). The merge produced no findings and no triage-only matches.

10 — triage-only

The only matches are triage-class techniques — ADV-0013 (pull_request_target handling) or ADV-0024 (poisoned agent config). These always exit 10 regardless of what the agent says; they need a human, not automation.

11 — finding

At least one candidate was confirmed (or never refuted). Fail the job, read the evidence, fix the code.

2 — usage error

You named an agent that isn't installed (--agent=codex with no codex on PATH). Configuration problem, distinct from scan results.

And the failure mode that matters most: if the agent crashes, times out, or returns malformed output, the scan doesn't die and it certainly doesn't pass. It falls back to regex-only results and says so on stderr:

warning: agent triage failed ('gemini' timed out); falling back to regex-only results

The regex candidates are still reported and still gate the exit code. A broken agent degrades the signal; it never silences it.

$ adversarial scan . --agent --with-skills

Point the agent at the skills pack

The paid skills pack — 134 SKILL.md skills across 15 categories — installs into .agents/skills/ in your repo. With --with-skills, the CLI appends a summary of each installed skill to the triage prompt, so the local agent judges candidates with adversarial-method context it wouldn't otherwise have.

The entitlement model matches the threat model: access is enforced at install time (that's what your subscription is for), and execution stays entirely local. The skills, the snippets, and the verdicts never leave the runner. Nothing about keyless mode changes that — there is still no API key to manage and no hosted model in the loop.

# drop-in CI

One script in the workflow

scripts/ci-scan.sh is the CI entrypoint: it detects a GitHub Actions PR context and checks out the PR with gh pr checkout, limits the scan to files changed versus the base ref when BASE_REF is set, and passes the scan's exit code straight through for job gating.

# .github/workflows/adversarial.yml
name: adversarial-scan
on:
  pull_request:
    types: [opened, synchronize]

permissions:
  contents: read

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0   # need history for the diff vs base

      - name: Keyless adversarial scan
        env:
          BASE_REF: ${{ github.base_ref }}
        run: scripts/ci-scan.sh
        # exit 11 (finding) or 10 (triage-only) fails the step;
        # exit 0 passes; exit 2 means the named agent isn't installed

No ADVERSARIAL_TOKEN, no secrets block, nothing to rotate. The only credential involved is the one your agent CLI already has — and it authenticates to its own service, not to us.

# why keyless

The shortest path to scanned code

The hosted scan API isn't going anywhere — if you want findings triaged by our models, with patches proposed and PR comments posted, that's what the platform does. Keyless mode is for the other case: the team that wants the library's 56 techniques running on every pull request today, without waiting on procurement. One CLI install, one workflow step, one agent your team already runs. The attack library does the detection; the infrastructure you already pay for does the judgment; the exit code does the gating.