Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
7de7821
Propagate triage data to JSON and md-dir exports
ohadstoller May 9, 2026
eaaa142
fix: centralize severity ordering
zerone0x May 6, 2026
b12bfc3
Add OpenCode SDK agent harness
ismaelvega Jul 20, 2026
c376576
Expose OpenCode through the CLI
ismaelvega Jul 20, 2026
1dc6445
Support OpenCode in Vercel Sandbox
ismaelvega Jul 20, 2026
04ab61a
Document the OpenCode agent harness
ismaelvega Jul 20, 2026
0573b15
Fix OpenCode SDK long-running responses
ismaelvega Jul 20, 2026
08a593c
Keep live sandbox e2e within Hobby timeout
ismaelvega Jul 20, 2026
4056b41
Fix OpenCode binary resolution in Sandbox
ismaelvega Jul 20, 2026
c0372d5
Fix OpenCode Anthropic gateway compatibility
ismaelvega Jul 20, 2026
afdacdd
Match triage verdicts to findings by index, not by title
thejesh23 Jul 20, 2026
2d02e57
Fail loud on unparseable triage output instead of dropping the batch
thejesh23 Jul 20, 2026
46ee4cd
fix(scanner): error on unknown --matchers slugs instead of silently i…
DarlingtonDeveloper Jul 22, 2026
f86fdb8
Classify provider policy refusals instead of parsing them as findings…
ValentinTorassa Aug 3, 2026
cc1814f
fix(cli): round token metrics to prevent table layout breakage
Youcef3939 Aug 10, 2026
98cae04
Tolerate host mounts where a create is not immediately visible to lookup
ezufelt Aug 22, 2026
71d69dc
Merge remote-tracking branch 'remotes/upstream/main' into fix/fs-sett…
ezufelt Aug 23, 2026
27853d8
Settle the post-install workspace probe
ezufelt Aug 23, 2026
367075a
Merge PR #61 from vercel-labs/deepsec: fix: centralize severity order…
prisis Oct 6, 2026
36394d8
Merge PR #75 from vercel-labs/deepsec: fix(scanner): error on unknown…
prisis Oct 6, 2026
c364d1e
Merge PR #71 from vercel-labs/deepsec: Propagate triage data to JSON …
prisis Oct 6, 2026
6259b6b
Merge PR #119 from vercel-labs/deepsec: Match triage verdicts to find…
prisis Oct 6, 2026
35696d1
Merge PR #121 from vercel-labs/deepsec: Fail loud on unparseable tria…
prisis Oct 6, 2026
b103fa6
Merge PR #149 from vercel-labs/deepsec: fix(cli): round token metrics…
prisis Oct 6, 2026
5d00b69
Merge PR #160 from vercel-labs/deepsec: Tolerate host mounts where a …
prisis Oct 6, 2026
08c971c
fix(test): use unlinkSync to clean up samples/webapp symlink (fixes #51)
prisis Oct 6, 2026
4bdb364
Merge PR #137 from vercel-labs/deepsec: Classify provider policy refu…
prisis Oct 6, 2026
bc41b05
Merge PR #122 from vercel-labs/deepsec: Add OpenCode SDK agent harnes…
prisis Oct 6, 2026
bfbadce
fix(processor): adapt OpenCode plugin to evolved parse API + bump @op…
prisis Oct 6, 2026
1834dbf
fix: config matchers filter, no Vercel prompt for direct/custom route…
prisis Oct 6, 2026
2b2cb39
fix(codex): prefer the codex CLI subscription login over ambient API …
prisis Oct 6, 2026
7ef0a08
feat(agents): add OpenCode v2 runtime support to the opencode agent
prisis Oct 6, 2026
250a7cb
feat(agents): wire the Vercel AI Gateway option into the OpenCode v2 …
prisis Oct 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ your key, never the key itself. See
[project link and credentials](https://github.com/vercel-labs/deepsec/blob/main/docs/vercel-setup.md)
for the full reference.

When running locally, `deepsec` can also reuse existing Claude, Codex, Pi, or
OpenCode provider authentication — including a local `opencode` CLI login
(`opencode` → `/connect`) — for evaluation-scale scans.


If a `process` or `revalidate` run halts because the upstream credential
ran out of quota or credits, deepsec stops gracefully and tells you
where to top up. Re-run the same command afterward and it picks up
Expand Down
3 changes: 2 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,13 @@ Generated specs must use unique slugs; collisions are rejected before scan.
- **Outputs:** FileRecord `findings[]` populated, `status: "analyzed"`,
`analysisHistory[]` appended.

Three agent backends are supported:
Four agent backends are supported:

| `--agent` | SDK | Default model |
|---|---|---|
| `codex` (default) | `@openai/codex-sdk` | `gpt-5.5` |
| `claude` | `@anthropic-ai/claude-agent-sdk` | `claude-opus-4-8` |
| `opencode` | `@opencode-ai/sdk/v2` | `anthropic/claude-opus-4-8` |
| `pi` | `@earendil-works/pi-coding-agent` | `zai/glm-5.2` |

Same prompt, same JSON output schema. You can mix backends within a
Expand Down
14 changes: 8 additions & 6 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ see [`samples/webapp/deepsec.config.ts`](https://github.com/vercel-labs/deepsec/
| `projects` | `ProjectDeclaration[]` | The codebases deepsec knows about. |
| `plugins` | `DeepsecPlugin[]` | Loaded in order; later plugins override single-slot capabilities. |
| `matchers` | `{ only?: string[]; exclude?: string[] }` | Filter the matcher set used by `scan`. |
| `defaultAgent` | `string` | Default `--agent` value (`codex`, `claude`, or `pi`). See [models](models.md). |
| `defaultAgent` | `string` | Default `--agent` value (`codex`, `claude`, `opencode`, or `pi`). See [models](models.md). |
| `defaultModel` | `string` | Default `--model` value selected during setup. |
| `defaultThinkingLevel` | `string` | Default reasoning effort (`minimal` through `xhigh`) selected during setup. |
| `ai` | `ModelRoute` | Non-secret model credential route selected and verified by setup. |
Expand Down Expand Up @@ -164,19 +164,21 @@ requires the complete access-token triple.

| Var | Used by | Purpose |
|---|---|---|
| `AI_GATEWAY_API_KEY` | Gateway route | Optional long-lived alternative to linked-project OIDC. Expanded for the selected agent. |
| `ANTHROPIC_AUTH_TOKEN` | `process`, `revalidate`, `triage` (Claude backend) | API token for the Claude Agent SDK. AI Gateway-issued or Anthropic-issued. Set this if you don't use `AI_GATEWAY_API_KEY`. |
| `AI_GATEWAY_API_KEY` | Gateway route | Optional long-lived alternative to linked-project OIDC. Expanded for the selected agent (Claude, Codex, OpenCode, or Pi). |
| `ANTHROPIC_AUTH_TOKEN` | `process`, `revalidate`, `triage` (Claude or OpenCode Anthropic backend) | API token for the Claude Agent SDK or the OpenCode Anthropic provider. AI Gateway-issued or Anthropic-issued. Set this if you don't use `AI_GATEWAY_API_KEY`. |
| `ANTHROPIC_API_KEY` | same | Direct-Anthropic credential (`x-api-key` broker contract). Prefer this over `ANTHROPIC_AUTH_TOKEN` when pointing directly at `https://api.anthropic.com` — some tooling rejects bearer-style tokens from that endpoint. |
| `ANTHROPIC_BASE_URL` | same | Default (when `AI_GATEWAY_API_KEY` is set): `https://ai-gateway.vercel.sh`. Set to `https://api.anthropic.com` for direct Anthropic. |
| `<ai.apiKeyEnv>` | Direct/custom route | User-chosen variable containing the provider credential. The name is stored in config; the value comes from `.env.local` or the process. |

### Optional

| Var | Used by | Purpose |
|---|---|---|
| `OPENAI_API_KEY` | `--agent codex`, `--agent pi --model openai/...` | Codex SDK token or Pi OpenAI-provider token. Unset is fine if `AI_GATEWAY_API_KEY` is set. |
| `OPENAI_BASE_URL` | `--agent codex` | Default (when `AI_GATEWAY_API_KEY` is set): `https://ai-gateway.vercel.sh/v1`. |
| `OPENAI_API_KEY` | `--agent codex`, `--agent opencode --model openai/...`, `--agent pi --model openai/...` | Codex SDK, OpenCode, or Pi OpenAI-provider token. Unset is fine if `AI_GATEWAY_API_KEY` is set. |
| `OPENAI_BASE_URL` | `--agent codex`, `--agent opencode --model openai/...` | Default (when `AI_GATEWAY_API_KEY` is set): `https://ai-gateway.vercel.sh/v1`. |
| `PI_CODING_AGENT_DIR` | `--agent pi` | Optional Pi config/auth directory. Defaults to `~/.pi/agent`; local non-sandbox runs can reuse `auth.json` there. |
| `DEEPSEC_AGENT_DEBUG` | both backends | Set to `1` to enable verbose agent logging. |
| `XDG_DATA_HOME` | `--agent opencode` | Optional OpenCode data root. Local preflight looks for provider auth at `$XDG_DATA_HOME/opencode/auth.json` (default `~/.local/share/opencode/auth.json`). |
| `DEEPSEC_AGENT_DEBUG` | AI backends | Set to `1` to enable verbose agent logging. |
| `DEEPSEC_DATA_ROOT` | core | Override the data directory location. Equivalent to `dataDir` in config. |

### Plugin-specific
Expand Down
2 changes: 1 addition & 1 deletion docs/data-layout.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ deleted.
| `investigatedAt` | `string` (ISO) | Timestamp. |
| `durationMs` | `number` | Wall-clock total. |
| `durationApiMs` | `number?` | API time only (excludes process orchestration). |
| `agentType` | `string` | `claude-agent-sdk` or `codex`. |
| `agentType` | `string` | Producing backend: `claude-agent-sdk`, `codex`, `opencode`, `pi`, or a plugin agent type. |
| `model` | `string` | Model identifier. |
| `modelConfig` | `Record<string, unknown>` | Provider-specific settings echoed back. |
| `agentSessionId` | `string?` | The agent's session/thread id, for reproducing or replaying. |
Expand Down
93 changes: 81 additions & 12 deletions docs/models.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
---
title: "Models"
description: "Choose Codex, Claude, or Pi for process and revalidate runs, and compare models under the same workload."
description: "Choose Codex, Claude, OpenCode, or Pi for process and revalidate runs, and compare models under the same workload."
---

deepsec talks to LLMs through interchangeable agent backends:

| Backend | Default model | Used by |
|-----------------------------|-----------------------|------------------------------|
| `codex` (default) | `gpt-5.5` | `process`, `revalidate` |
| `claude` | `claude-opus-4-8` | `process`, `revalidate` |
| `pi` | `zai/glm-5.2` | `process`, `revalidate` |
| `claude` (triage) | `claude-sonnet-4-6` | `triage` (Claude-only) |
| Backend | Default model | Used by |
|-----------------------------|---------------------------------|-------------------------|
| `codex` (default) | `gpt-5.5` | `process`, `revalidate` |
| `claude` | `claude-opus-4-8` | `process`, `revalidate` |
| `opencode` | `anthropic/claude-opus-4-8` | `process`, `revalidate` |
| `pi` | `zai/glm-5.2` | `process`, `revalidate` |
| `claude` (triage) | `claude-sonnet-4-6` | `triage` (Claude-only) |

Interactive one-shot setup recommends five benchmark-backed combinations:
GPT-5.6 Sol, Claude Opus 5, Kimi K3, Grok 4.5, and the current DeepSeek entry.
Expand Down Expand Up @@ -55,6 +56,12 @@ disables the env-var preflight checks — the machine-wide login is used
directly. Sandbox commands are the exception: they must broker a real token,
so they still require `AI_GATEWAY_API_KEY` (or an equivalent key).


The built-in backends also work with direct `AI_GATEWAY_API_KEY` credentials:
one key covers Codex, Claude, OpenCode, and Pi. OpenCode and Pi accept
provider/model identifiers directly, which makes them useful for comparing
harness and provider behavior under the same deepsec workload.

## CLI selection

```bash
Expand All @@ -70,6 +77,10 @@ pnpm deepsec process --project-id my-app --agent codex
# Codex backend, specific model:
pnpm deepsec process --project-id my-app --agent codex --model gpt-5.4

# OpenCode SDK harness (provider/model is required):
pnpm deepsec process --project-id my-app --agent opencode
pnpm deepsec process --project-id my-app --agent opencode --model openai/gpt-5.5

# Pi backend through Vercel AI Gateway, default model:
pnpm deepsec process --project-id my-app --agent pi

Expand Down Expand Up @@ -101,11 +112,12 @@ over large repos.

The flag maps onto each backend's native dial:

| Backend | Setting |
|----------|---------------------------------------------|
| `codex` | model reasoning effort (`minimal`–`xhigh`) |
| `pi` | thinking level (`minimal`–`xhigh`) |
| `claude` | adaptive-thinking effort (`minimal` → `low`, `xhigh` → `max`) |
| Backend | Setting |
|------------|---------------------------------------------------------------|
| `codex` | model reasoning effort (`minimal`–`xhigh`) |
| `opencode` | provider variant (Anthropic maps to `high` / `max`) |
| `pi` | thinking level (`minimal`–`xhigh`) |
| `claude` | adaptive-thinking effort (`minimal` → `low`, `xhigh` → `max`) |

It applies to the main investigation/revalidation runs only.
Special-purpose follow-up calls (the refusal report, JSON repair) keep
Expand Down Expand Up @@ -171,6 +183,62 @@ Later `process`, `revalidate`, and Sandbox commands resolve the persisted
route. Per-command `--ai-provider`, `--ai-base-url`, `--ai-api-key-env`, and
repeatable `--ai-header name=value` remain available as Pi runtime overrides.

### OpenCode SDK harness

OpenCode uses `@opencode-ai/sdk/v2` plus the `opencode` runtime — both the
v1 runtime (npm `opencode-ai` 1.x, bundled for sandbox workers) and the
**OpenCode v2** runtime (2.0.x) are supported; the generation is detected at
startup and the matching protocol is used automatically. Each batch starts a
private, password-secured local OpenCode server, creates a session rooted at
the target project, and asks for JSON output. The deepsec agent allows only
`read`, `glob`, `grep`, and `list`; shell, edits, network tools, subagents,
external directories, LSP, skills, and MCP-triggered permissions are denied.
The session and server are closed on success, error, or abort.

Differences worth knowing:

- The v1 runtime returns JSON Schema–validated structured output when the
model supports it; the v2 runtime has no structured-output prompts, so
results arrive as plain text and go through deepsec's shared JSON-repair
pipeline (same contract, same validation).
- The v2 runtime always requires basic auth on its server; deepsec generates
the credential itself and nothing is written to disk.
- `--ai-header` overrides require the v1 runtime; on v2 they are rejected
with a clear error.

**Vercel AI Gateway on the v2 runtime** — two supported options:

- Native gateway provider (recommended): the v2 model catalog ships a
`vercel` provider ("Vercel AI Gateway") that reads `AI_GATEWAY_API_KEY`
directly — one key covers every model behind the gateway:

```bash
AI_GATEWAY_API_KEY=vck_… pnpm deepsec process --project-id my-app \
--agent opencode \
--model vercel/anthropic/claude-opus-4-8
```

- The standard `ANTHROPIC_*`/`OPENAI_*` expansion also keeps working:
deepsec translates the expansion into the v2 provider `baseURL` overlays
and bridges the gateway bearer token to the `x-api-key` form the v2
anthropic provider reads, so `--model anthropic/claude-opus-4-8` with
`AI_GATEWAY_API_KEY` routes through the gateway unchanged.

Models use OpenCode's required `provider/model` form:

```bash
pnpm deepsec process --project-id my-app \
--agent opencode \
--model anthropic/claude-opus-4-8
```

For a local run without environment credentials, authenticate a provider in
OpenCode first: run `opencode`, then use `/connect`. Sandbox runs cannot reuse
that local credential store; use AI Gateway or an explicit provider token.
Generic `--ai-provider`, `--ai-base-url`, `--ai-api-key-env`, and
`--ai-header` overrides work for OpenCode as they do for Pi (headers v1
runtime only).

### `claude-sonnet-4-6` for `triage`

Triage buckets findings into P0/P1/P2/skip without re-reading the code.
Expand Down Expand Up @@ -216,6 +284,7 @@ rest of deepsec stays unchanged:
```bash
pnpm deepsec process --project-id my-app --model anthropic-mythos-1
pnpm deepsec process --project-id my-app --agent codex --model gpt-6
pnpm deepsec process --project-id my-app --agent opencode --model anthropic/claude-mythos-1
pnpm deepsec process --project-id my-app --agent pi --model vercel-ai-gateway/openai/gpt-6
```

Expand Down
33 changes: 29 additions & 4 deletions docs/vercel-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ ai: { mode: "gateway", provider: "vercel" }
```

Deepsec maps the Gateway credential to the environment expected by Codex,
Claude, or Pi. Sandbox workers receive only a placeholder. The real bearer
Claude, OpenCode, or Pi. One key covers all four harnesses. Sandbox workers receive only a placeholder. The real bearer
token is injected at the allowed Gateway host by the host-side broker.

### Your own OpenAI or Anthropic credential
Expand Down Expand Up @@ -133,13 +133,14 @@ again or put it in `.deepsec/.env.local`:
MY_OPENAI_KEY=...
```

Direct OpenAI routes require Codex; direct Anthropic routes require Claude.
Direct OpenAI routes require Codex or OpenCode (`openai/...` models); direct
Anthropic routes require Claude or OpenCode (`anthropic/...` models).
Setup rejects incompatible agent/provider combinations before scanning.

### Custom HTTPS provider

Custom routes are supported by the Pi backend. Supply an HTTPS base URL and
describe how the credential should be attached:
Custom routes are supported by the Pi and OpenCode backends. Supply an HTTPS
base URL and describe how the credential should be attached:

```bash
MARTIAN_KEY=... npx deepsec init \
Expand All @@ -156,6 +157,29 @@ Use `:bearer` for `Authorization: Bearer …` and `:raw` for provider-specific
raw token headers. HTTP URLs, embedded URL credentials, invalid header names,
and custom routes for Codex/Claude are rejected.

OpenCode uses `provider/model` identifiers, so the same custom route works
there too:

```bash
MARTIAN_KEY=... npx deepsec init \
--agent opencode \
--model openai/gpt-5.5 \
--model-auth custom \
--ai-provider martian \
--ai-api-key-env MARTIAN_KEY \
--ai-base-url https://api.martian.example/v1 \
--ai-credential-header authorization:bearer
```

### Local agent authentication

Non-sandbox runs can reuse an existing local login for the built-in
harnesses: `claude login` / `codex login` for those CLIs, or run `opencode`
and use `/connect` to connect a provider for the OpenCode backend. Local
OpenCode auth (stored under `$XDG_DATA_HOME/opencode/auth.json`) is never
copied into Vercel Sandbox workers — sandbox runs need the Gateway (or a
direct/custom route).

## Credential brokering in Sandbox

Deepsec resolves the real model credential on the host. A worker receives a
Expand Down Expand Up @@ -211,5 +235,6 @@ pnpm deepsec sandbox-all process --sandboxes 30 --concurrency 4
| Direct route asks for a custom variable | Config stores its name, not its value. | Export it in the fresh shell or add it to `.env.local`. |
| Setup repeats login verification | The link, route, agent, or verification age changed. | Complete the probe; later unchanged runs short-circuit again. |
| Gateway quota is exhausted | Processing stopped before launching more batches. | Add credits or change route, then re-run; processing resumes pending files. |
| `Missing AI credentials for --agent opencode` | No Gateway/direct credential and no local OpenCode auth. | Set `AI_GATEWAY_API_KEY`, use a direct route, or run `opencode` and use `/connect` (local runs only). |

References: [AI Gateway authentication](https://vercel.com/docs/ai-gateway/authentication-and-byok#quick-start), [Sandbox authentication](https://vercel.com/docs/vercel-sandbox/concepts/authentication).
8 changes: 7 additions & 1 deletion e2e/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ describe("bundle e2e", () => {
expect(stdout).toContain("deepsec");
expect(stdout).toContain("scan");
expect(stdout).toContain("process");

const processHelp = runBundle(["process", "--help"]);
expect(processHelp.status).toBe(0);
expect(processHelp.stdout).toContain("opencode");
});

it("config.d.ts is self-contained (no internal @deepsec/* re-exports)", () => {
Expand Down Expand Up @@ -186,7 +190,9 @@ export default defineConfig({
expect(stdout).toContain("webapp-debug-flag");
expect(stdout).toContain("webapp-route-no-rate-limit");
} finally {
fs.rmSync(link, { force: true });
try {
fs.unlinkSync(link);
} catch {}
fs.rmSync(path.join(sampleDir, "data"), { recursive: true, force: true });
}
});
Expand Down
5 changes: 5 additions & 0 deletions e2e/pipeline-sandbox.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ const HAS_SANDBOX_KEY =
Boolean(process.env.VERCEL_PROJECT_ID));

const SHOULD_RUN = LIVE && HAS_SANDBOX_KEY;
const SANDBOX_TIMEOUT_MS = 30 * 60_000;

/**
* Stub agent definition shared with pipeline.test.ts. Inlined here
Expand Down Expand Up @@ -319,6 +320,8 @@ describe.skipIf(!SHOULD_RUN)("pipeline e2e — live sandbox", () => {
"1",
"--vcpus",
"2",
"--timeout",
String(SANDBOX_TIMEOUT_MS),
"--limit",
"2",
"--concurrency",
Expand Down Expand Up @@ -358,6 +361,8 @@ describe.skipIf(!SHOULD_RUN)("pipeline e2e — live sandbox", () => {
"1",
"--vcpus",
"2",
"--timeout",
String(SANDBOX_TIMEOUT_MS),
"--limit",
"2",
"--concurrency",
Expand Down
5 changes: 4 additions & 1 deletion knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@
"@anthropic-ai/claude-agent-sdk",
"@earendil-works/pi-coding-agent",
"@openai/codex",
"@openai/codex-sdk"
"@openai/codex-sdk",
"@opencode-ai/sdk",
"opencode-ai",
"undici"
]
}
Loading