Repository navigation
Add OpenCode v2 runtime support and fix open-issue bugs - #1
Merged
Merged
Conversation
The triage field on findings was correctly written by `deepsec triage` but stripped during export, so downstream consumers (issue trackers, dashboards) had to read FileRecords directly to see priority data. JSON: include `metadata.triage` with the full triage object. md-dir: render a `## Triage` section showing priority, exploitability, impact, reasoning, and the triagedAt/model footer. Closes vercel-labs#64. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
triageBatch mapped each model verdict back to a finding with
`batch.find((b) => b.finding.title === verdict.title)`. Finding titles
are free-text and not unique — generic titles ("Missing authorization
check", "SQL injection") recur across the up-to-30 findings in a batch.
`find` returns the first title match, so same-titled findings are all
assigned the first one's verdict: earlier ones get overwritten, later
ones are never triaged, and the P0/skip counts are mis-attributed.
Emit the 1-based index already shown in the prompt as an `id` field and
correlate on it, falling back to title only when the model omits the id.
An applied-set guard prevents a duplicate id/title in the reply from
assigning one finding twice (and, unlike a `finding.triage`-presence
guard, it doesn't break `--force` re-triage).
Adds a regression test: two same-titled findings receive two distinct
verdicts, and both are triaged.
triageBatch parsed the model's verdict array with a bare `JSON.parse`
wrapped in `try { … } catch {}`, leaving `verdicts = []` on any error.
A common trailing-comma or truncated array was therefore swallowed: the
loop ran zero times, every finding in the (up to 30-finding) batch stayed
un-triaged, and the batch was still reported as a success ("N triaged")
— indistinguishable from a clean "nothing to do" run.
Route triage through the same tolerant parse + fail-loud path the
investigate/revalidate agents already use: `parseAgentJsonArray`
(jsonrepair-backed) recovers minor malformations, and genuinely
unparseable output is dumped via `writeParseFailureDebug` and rethrown so
the outer catch marks the batch failed rather than silently empty.
`parseAgentJsonArray` is exported from agents/shared.ts for reuse.
Adds regression tests: a trailing-comma array is recovered and triaged; a
non-array reply surfaces a failed batch instead of "0 triaged".
…gnoring When `--matchers` includes a slug that does not exist in the registry, deepsec now throws with the unknown slug names and lists all available matchers. Previously unknown slugs were silently dropped, making invalid scan requests look successful. Fixes vercel-labs#34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… JSON A hard provider refusal (Anthropic AUP block, OpenAI safety-system rejection, content_policy_violation) arrives as prose, fails JSON.parse, and surfaced as a generic parse-failure — a formatting problem on the surface, zero coverage underneath. Worse, the JSON-repair follow-up then asked the declining model to re-emit conclusions it never produced and accepted an empty findings array as a valid answer, recording unanalyzed files as investigated and clean. classifyPolicyRefusal() detects explicit refusal strings inside the parseAgentJsonArray catch (before jsonrepair, so valid JSON quoting refusal text as evidence still parses) and throws AgentPolicyRefusalError. All six investigate/revalidate parse sites rethrow it before the repair follow-up. Per-batch failure, matching the existing parse-failure semantics; the rest of the run continues. Fixes vercel-labs#92.
On a Docker Desktop / virtiofs bind mount of a macOS host, mkdir, writeFile, and symlink return success but lstat/realpath/existsSync on the path they just created report ENOENT for the next 1-2ms. open, read, and write on that path work immediately -- the lookup path lags, not the data. Measured visibility after mkdir: 0/10 at 0ms, 9/10 at 1ms, 10/10 at 2ms. Sandboxed agent environments commonly mount the workspace this way. deepsec assumed POSIX durability-on-return in a few places, so init failed with "ENOENT: no such file or directory, mkdir 'data/<projectId>'" -- recursive mkdir creates data/, then cannot see it when it looks up the child. Adds settleFs, existsSettled, and mkdirSettled: a flat 100ms wait after creating a path, tunable via DEEPSEC_FS_SETTLE_MS (0 opts out). Not a poll-until-visible loop -- this is a handful of call sites on one code path, and the simplicity is worth more than the milliseconds. mkdirSettled retries the mkdir once on ENOENT, since that is where the failure surfaces; any other errno is a real error and is rethrown untouched. Used only where this process just created the path: ensureProject's data dir, the init workspace dir, init-project's data dir, and the post-install node_modules probe. Resume detection, atomic-file temp cleanup, and the mkdir lock primitive are left alone -- those paths were created by an earlier process or are legitimately absent most of the time, where waiting is pure cost. Refs vercel-labs#159 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…le-non-coherent-mounts
existsSettled only guarded the install checkpoint check, which runs when a checkpoint already exists -- so a fresh init skipped it and then probed the just-written node_modules with a raw existsSync, where the same create-to-lookup lag reports "Install completed but workspace is unusable" for a good install. probeWorkspaceInstall takes a settle option that routes its existence checks through existsSettled; the post-install call passes it. The pre-install probe keeps the raw check, since an absent node_modules is the normal case there and waiting on it is pure cost. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…erity ordering (fixes vercel-labs#48)
… on unknown --matchers slugs (fixes vercel-labs#34)
…ta to JSON and md-dir exports (fixes vercel-labs#64) # Conflicts: # packages/deepsec/src/commands/export.ts
…cts to findings by index, not by title (fixes vercel-labs#118)
…seable triage output instead of dropping the batch (fixes vercel-labs#120)
…ken metrics to prevent table layout breakage (fixes vercel-labs#135)
…ts where a create is not immediately visible to lookup (fixes vercel-labs#159)
…ercel-labs#51) Applies PR vercel-labs#50 from vercel-labs/deepsec. rmSync(force: true) on a symlinked node_modules fails on macOS; unlinkSync removes the link itself, not the target tree.
…policy refusals instead of parsing them as findings JSON (fixes vercel-labs#92) # Conflicts: # packages/processor/src/__tests__/shared.test.ts # packages/processor/src/agents/claude-agent-sdk.ts # packages/processor/src/agents/codex-sdk.ts # packages/processor/src/agents/pi-sdk.ts
…gent harness (opencode v2 support) # Conflicts: # README.md # docs/configuration.md # docs/models.md # docs/vercel-setup.md # packages/deepsec/package.json # packages/deepsec/src/__tests__/network-policy.test.ts # packages/deepsec/src/agent-config.ts # packages/deepsec/src/commands/sandbox-all.ts # packages/deepsec/src/preflight.ts # packages/deepsec/src/sandbox/orchestrator.ts # packages/deepsec/src/sandbox/setup.ts # packages/deepsec/src/sandbox/types.ts # packages/processor/package.json # pnpm-lock.yaml
…encode-ai/sdk to ^1.18.35
- parseInvestigateResults now returns ParsedInvestigateResults {results, invalid};
route invalid entries through runInvestigateFieldRepairLoop like the other
backends, and rethrow AgentPolicyRefusalError instead of attempting JSON repair.
- Bump @opencode-ai/sdk and opencode-ai to ^1.18.35; the /v2 server+client
API (createOpencodeServer/createOpencodeClient, session create/prompt/delete)
is unchanged.
…s, docs, flaky mac test - scan()/scanFiles() now apply deepsec.config.ts matchers.only/exclude (vercel-labs#36). Explicit --matchers still wins; unknown slugs in the config filter fail loud like --matchers does. Tests included. - init no longer prompts for a Vercel login when the selected model route is direct or custom (vercel-labs#164): the platform link only enables the optional Vercel Sandbox, so it is reused silently when Vercel credentials exist and skipped otherwise. ensureConnectedWorkspace handles the platform-less path (sandboxReady: false) while still resolving + verifying the provider credential. Tests included. - docs: document ANTHROPIC_API_KEY as the direct-Anthropic credential and its x-api-key broker contract (vercel-labs#55). - e2e: raise the 64MiB tarball-fixture test timeout so it stops tripping vitest's default 5s budget on slower machines / parallel runs (vercel-labs#51).
…keys (vercel-labs#32) buildCodexInvocation now mirrors the codex CLI's own precedence: a ChatGPT/Codex subscription login (auth.json) wins over an ambient OPENAI_API_KEY / ANTHROPIC_AUTH_TOKEN when no explicit base URL routing is configured, so a stray API key in the environment no longer silently burns API credits while a valid subscription sits unused. Explicit OPENAI_BASE_URL / ANTHROPIC_BASE_URL routing (AI Gateway expansion, direct-provider routes, sandbox proxying) still wins as before.
The opencode agent harness now speaks BOTH OpenCode runtime generations,
detected automatically at startup:
- v1 (npm opencode-ai 1.x, bundled for sandbox workers): unchanged —
legacy /session API via @opencode-ai/sdk/v2, structured-output formats.
- v2 (OpenCode 2.0.x): the new Effect HttpApi under /api/…, which the
published SDK cannot drive (different paths, mandatory basic auth, an
async prompt/wait inbox, and no structured-output prompts).
Implementation, validated live against OpenCode v2.0.24:
- Own server spawn instead of the SDK's createOpencodeServer: the SDK only
recognizes the v1 startup line ('opencode server listening on …'), while
v2 prints 'server listening on …', and it has no way to pass the password
v2 always requires (v2 generates an un guessable random one otherwise).
We generate the password ourselves, set both OPENCODE_PASSWORD (v2) and
OPENCODE_SERVER_PASSWORD (v1), and accept either startup line (60s).
- Protocol detection: 'opencode --version' picks the config content shape
(v1: tools/permission/agent; v2: permissions ruleset + agents with
system prompts, read-only rules deny-first with last-match-wins), then a
GET /api/info probe authoritatively confirms which API to speak.
- v2 client: plain fetch with basic auth — POST /api/session (location +
agent + model), POST /api/session/:id/prompt, POST …/wait, GET
/api/session/:id/message (newest-first assistant extraction), agent
switching for tool-free JSON follow-ups, DELETE for cleanup. Cost, token,
and turn metadata come from the assistant messages.
- v2 results arrive as plain text (no structured output in v2 prompts) and
flow through deepsec's shared JSON-repair + field-repair pipeline, same
contract as every other backend.
- Custom provider overrides (--ai-provider/--ai-base-url/--ai-api-key-env)
are remapped onto the standard ANTHROPIC_*/OPENAI_* env pairs the v2
built-in providers read; --ai-header is rejected on v2 with a clear error.
- Read-only enforcement verified on v2: bash/edit attempts are denied while
read/glob/grep/list keep working.
Also drops the logLevel config field (v1 accepts 'WARN', v2 lowercase
'warn' — the two CLIs disagree and the default is quiet enough), and
documents the v2 differences in docs/models.md.
…runtime Two supported gateway paths on v2, both documented in docs/models.md: - Native: the v2 model catalog ships a 'vercel' provider (Vercel AI Gateway, package @ai-sdk/gateway) that reads AI_GATEWAY_API_KEY directly — --model vercel/anthropic/claude-opus-4-8 works out of the box with one key for every model behind the gateway. - Ambient expansion: deepsec's startup expansion sets ANTHROPIC_AUTH_TOKEN (bearer) + ANTHROPIC_BASE_URL / OPENAI_BASE_URL. The v2 built-in providers take their base URL from config (not those env vars) and the v2 anthropic provider only reads ANTHROPIC_API_KEY (x-api-key) from the environment — so the expansion is now translated into providers.<id>.settings.baseURL overlays in the spawned server's config content, and the gateway bearer token is bridged to ANTHROPIC_API_KEY when the API-key form is absent. Custom provider routes (--ai-base-url) redirect the model's own provider the same way. Tests: gateway bridge, overlay translation, custom-route redirection, overlay inclusion in the config content.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Brings in the best upstream MRs that fix open issues, adds OpenCode v2 runtime support to the
opencodeagent backend, and fixes the remaining open bugs. No new providers were added — the Vercel AI Gateway option remains first-class throughout.Best MR merged: vercel-labs#122 — OpenCode SDK agent harness (+1780)
The most comprehensive open MR: a full
opencodeagent backend (@opencode-ai/sdk/v2) with read-only tool enforcement, structured-output fallbacks, credential brokering, sandbox integration, packaging, and tests. Rebased onto current main (conflicts in preflight/setup/orchestrator/lockfile resolved; adapted to the evolvedParsedInvestigateResultsparse API, policy-refusal handling, and field-repair loop) and extended with v2 runtime support (below).OpenCode v2 support (new)
The published SDK cannot drive the OpenCode v2 runtime (2.0.x): different API surface (
/api/…Effect HttpApi), mandatory basic auth, async prompt/wait inbox, no structured-output prompts. The agent now detects the runtime generation at startup and speaks the matching protocol:Vercel option on v2 (kept working)
vercelprovider (Vercel AI Gateway) readingAI_GATEWAY_API_KEY—--model vercel/anthropic/claude-opus-4-8works out of the boxANTHROPIC_BASE_URL/OPENAI_BASE_URL→ v2 providerbaseURLoverlays; gateway bearer token bridged to thex-api-keyform v2 readsBug-fix MRs merged from upstream (fixing open issues)
SEVERITY_ORDER--matchersslugsIssues vercel-labs#30, vercel-labs#29, vercel-labs#33, vercel-labs#91 were verified as already fixed in current main.
Own fixes for the remaining open bugs
initno longer prompts for a Vercel login when the route isdirect/custom: the platform link is reused silently when credentials exist and skipped otherwise (sandboxReady: false), while model routes are still resolved + verifiedmatchers.excludedoes not work (matcher e.g.xssnot excluded during scan) vercel-labs/deepsec#36 — configmatchers.only/excludeis now honored byscan()/scanFiles()(explicit--matcherswins; unknown config slugs fail loud)codex loginsubscription over ambientOPENAI_API_KEY(matching codex CLI precedence) unless explicit base-URL routing is configuredANTHROPIC_API_KEY(x-api-key) as the direct-Anthropic credentialVerification
pnpm -r build,pnpm lint,pnpm knipall cleanscan→process --agent opencode(3 findings, correct cost/tokens/turns meta) →revalidate(3/3 verdicts) →export