feat: add Antigravity subscription CLI as a provider#356
feat: add Antigravity subscription CLI as a provider#356Sanjay Ramadugu (sanjay3290) wants to merge 4 commits into
Conversation
Add a `grok-build` provider that runs documentation jobs through the local Grok Build CLI (`grok`) using a subscription login instead of a metered API key. OpenWiki spawns the binary headlessly with `--always-approve --output-format streaming-json`, streams and coalesces its NDJSON events, and reuses the CLI's own session for follow-ups. - Model provider config becomes a discriminated union (api | agent-cli) so agent-CLI providers carry a binary/install hint instead of an API key; onboarding, credential setup, and startup checks skip the key collection for them. - New engines module: a generic CLI runner (temp prompt file, detached process group with SIGTERM/SIGKILL timeout, stream parsing) plus a Grok Build adapter and streaming-json parser. - Extract shared prepareAgentRun/finalizeAgentRun so the CLI and DeepAgents run paths share snapshot/metadata handling. - Docs and tests for the new provider, adapter, and runner.
Wire OpenWiki to the local Antigravity CLI (`agy`) as an agent-cli provider alongside Grok Build. Runs use print mode with auto-approved edits, recover conversation ids from the per-run log, and accept `agy models` display names.
There was a problem hiding this comment.
Both agent-CLI providers (Grok Build, Antigravity) are launched with auto-approval flags and inherit the full process environment, creating two exploitable issues: prompt-injected content can write files outside the docs-only boundary, and it can read or exfiltrate all credentials loaded from ~/.openwiki/.env.
|
|
||
| // Path discipline for agent-cli lives in createSystemPrompt(..., "agent-cli"); | ||
| // the user message reuses the same root/cwd framing as DeepAgents. | ||
| const prompt = `${createSystemPrompt(command, outputMode, "agent-cli")}\n\n---\n\n${createRunUserMessage(command, cwd, context, options)}`; |
There was a problem hiding this comment.
The agent-CLI execution path has no enforced filesystem boundary. The DeepAgents path uses OpenWikiLocalShellBackend to hard-reject writes outside openwiki/ at the process level; the agent-CLI path relies on a prompt instruction that can be overridden by injected content in repository files or connector data.
// Path discipline for agent-cli lives in createSystemPrompt(..., "agent-cli");
// the user message reuses the same root/cwd framing as DeepAgents.
const prompt = `${createSystemPrompt(command, outputMode, "agent-cli")}\n\n---\n\n${createRunUserMessage(command, cwd, context, options)}`;Both adapters launch with auto-approval flags (--always-approve for Grok Build, --dangerously-skip-permissions --mode accept-edits for Antigravity), so the vendor CLI will write any file without prompting.
Remediation: Enforce the docs-only boundary outside the model — sandbox the vendor CLI so only openwiki/ is writable (chroot, overlay FS, or temp-copy approach). Do not rely on prompt instructions for path authorization.
Attack Path
- Attacker embeds write instruction in a repository file or connector-ingested content (Slack, Notion, etc.).
- User runs
openwiki --updatewithOPENWIKI_PROVIDER=grok-buildorantigravity. runAgentCliRun()builds a prompt that includes the attacker-controlled content.- Vendor binary is spawned with auto-approval flags; it writes the injected target path without any permission check.
- OpenWiki records a successful run.
For more details, see the finding in Corridor.
Provide feedback: Reply with whether this is a valid vulnerability or false positive to help improve Corridor's accuracy.
Address Corridor findings on the agent-CLI path: - spawn vendor CLIs with a minimal env allowlist so OpenWiki-managed credentials never reach the child process - after repository init/update runs, reject any writes outside openwiki/ (plus AGENTS.md / CLAUDE.md) based on post-run mtime checks
|
Addressed both Corridor findings in 16a452e:
Tests: @corridor-security — treating both findings as valid; mitigated as above (post-run mtime boundary rather than full OS sandbox, which is the practical control for a local CLI integration). |
Stop skipping VCS metadata trees during the post-run path scan so prompt-injected git hook or config writes under .git/ are rejected.
|
Also landed the |
There was a problem hiding this comment.
The docs-only write-boundary scanner follows symlinks and records only the symlink's own path, allowing a symlink placed under openwiki/ to target a file outside the repo (e.g. .git/hooks/pre-commit) while passing the allowlist check — a prompt-injected or malicious agent CLI can exploit this to silently modify VCS hooks or arbitrary host files.
| */ | ||
| export async function findDisallowedWrites( | ||
| rootDir: string, | ||
| sinceMs: number, |
There was a problem hiding this comment.
The docs-only write-boundary check follows symlinks and records only the path under the repo, allowing a bypass. A symlink created in openwiki/ pointing outside the repository (e.g., .git/hooks/pre-commit or ../../some/host/file) passes the allowlist because the recorded path is openwiki/…, while the actual write lands on the target outside the boundary — undetected.
The vulnerable logic accepts symlinks and calls stat() (which follows the link), pushing the symlink path into results:
if (!entry.isFile() && !entry.isSymbolicLink()) {
continue;
}
const fileStat = await stat(absolutePath);
if (fileStat.mtimeMs >= sinceMs) {
results.push(relativePath);
}Because findDisallowedWrites filters with isAllowedDocsOnlyWritePath(relativePath), a modified symlink under openwiki/ is always approved even if its target is outside the repo. Commit d869510 fixed direct .git/ writes but left this symlink path open.
Remediation: Import lstat and realpath; use lstat() for the mtime check and, when the entry is a symlink, reject it if realpath() resolves outside rootDir. Alternatively, treat any modified symlink as disallowed during docs-only runs.
Attack Path
- Attacker prompt-injects the agent CLI during a
docs-onlyrepository init/update run. - Agent creates
openwiki/evil-link → .git/hooks/pre-commit(symlink write is inside the allowed zone). - Agent writes attacker payload through the symlink, modifying the hook on disk.
- Post-run scan records
openwiki/evil-link;isAllowedDocsOnlyWritePathapproves it. - No error is thrown;
.git/hooks/pre-commitnow executes attacker code on the nextgit commit.
For more details, see the finding in Corridor.
Provide feedback: Reply with whether this is a valid vulnerability or false positive to help improve Corridor's accuracy.
Summary
Adds an
antigravityprovider that runs OpenWiki documentation jobs through the local Antigravity CLI (agy) using a subscription/session login instead of a metered API key.This builds on the agent-CLI foundation introduced for Grok Build (see also #280): OpenWiki spawns the vendor binary headlessly, streams stdout, and reuses the CLI session for interactive follow-ups.
What changed
antigravityagent-cli provider (agybinary, override viaOPENWIKI_ANTIGRAVITY_BINARY)--conversation <id>, recovered from a per-run--log-fileagy modelsdisplay strings (e.g.Gemini 3.5 Flash (Medium));isValidModelIdnow allows spaces/parentheses for those namesConfig
OPENWIKI_PROVIDER=antigravityOPENWIKI_ANTIGRAVITY_BINARYagyis not onPATHOPENWIKI_MODEL_IDagy modelsdisplay name (defaultGemini 3.5 Flash (Medium))OPENWIKI_AGENT_CLI_TIMEOUT_SECONDSRuntime flags
Testing
pnpm run typecheck— cleanpnpm run lint:check— cleanNotes
agy modelsverbatim; near-misses can no-op with empty stdoutReview
Brace Sproul (@bracesproul) Colin Francis (@colifran) — would appreciate a review when you have a chance.