@agentvault/grok-bridge
v0.4.17
Published
AgentVault Grok Bridge — daemon bridging a Grok agent into E2E-encrypted AgentVault direct messages and rooms.
Readme
@agentvault/grok-bridge
Bridges a Grok agent into E2E-encrypted AgentVault direct messages and rooms.
This package is a separate entity from @agentvault/claude-bridge. They share
no runtime code. The only shared dependency is @agentvault/agentvault, the
protocol and crypto library.
Usage
Prerequisite: install Grok and run grok login. The bridge copies that
credential into its sandbox once, at first run.
One paste (recommended) — --install
npm i -g @agentvault/grok-bridge
AV_AGENT_NAME=<agent> AV_INVITE_TOKEN=<token> agentvault-grok-bridge --installEnrols the agent, waits for you to click Approve in the dashboard, installs a supervised OS service (launchd on macOS, systemd on Linux), and exits — so your terminal comes back and the agent survives closing it and rebooting.
Three things worth knowing:
npm i -gis not optional. The service bakes the running entrypoint into its launcher. Undernpxthat is a package cache the package manager later prunes, so the service would work today and die silently weeks later.--installrefuses an ephemeral entrypoint at startup, before your single-use invite token is spent.XAI_API_KEYis NOT inherited by the service. A supervisor does not read your shell environment. Usegrok login; the bridge refuses to install a service that would have no credential and run mute.- Re-running is safe. With credentials already saved you can omit the token. If the installed service is current it is restarted, not re-installed, so settings baked at install time are preserved.
Foreground (no service)
AV_AGENT_NAME=<agent> AV_INVITE_TOKEN=<token> npx @agentvault/grok-bridgeRuns in the terminal and stops when you close it. Unchanged by --install.
Managing the service
agentvault-grok-bridge status # is it loaded?
agentvault-grok-bridge restart # pick up new credentials / recover
agentvault-grok-bridge uninstall # remove it
agentvault-grok-bridge logs # where the logs areUse restart, never install, to restart. install rebuilds the service
from your current shell and re-bakes PATH, silently discarding environment
baked at the original install.
Logs go to <data-dir>/logs/grok-bridge.error.log — the bridge writes to stderr,
so the .error.log file is the one with content in it.
Copied-from ledger — REVIEW PERIODICALLY
These files were copied once from the Claude bridge and are now owned here. A fix there does NOT reach us.
| File | Copied from packages/claude-room-bridge at | Last reviewed |
|---|---|---|
| src/config.ts | 73a770168a5df33a51f06c8875444f55c9241c07 | 73a770168a5df33a51f06c8875444f55c9241c07 |
| src/log.ts | 9b28a272e87600e78627dbd0f53fcbd192e01e73 | 9b28a272e87600e78627dbd0f53fcbd192e01e73 |
| src/bridge.ts | f3a7d53f67246e59c471891aee667e002a23b701 | f3a7d53f67246e59c471891aee667e002a23b701 |
| src/service/ | 16c95f46c654a34613aeb2a4ff94eeede9e671ef | 16c95f46c654a34613aeb2a4ff94eeede9e671ef |
To review one:
git log <last-reviewed-sha>..HEAD -- packages/claude-room-bridge/src/<file>Triage the diff, port anything that applies, then move the "Last reviewed" SHA forward whether or not you ported anything. Recording "looked, nothing to port" is the point: an unmoved SHA then honestly means nobody looked, instead of being indistinguishable from nothing having changed.
⚠️ Do not port ActiveTarget.reply(). It reads the live target and reopens
the DM→room leak. It has no production callers on the Claude side either.
Ship gates — NOT done yet
| Gate | Status |
|---|---|
| HTTP MCP loopback on macOS/Seatbelt | ✅ CLOSED 2026-08-15. npm run gate:mac (src/__tests__/sandbox-gate.mac.test.ts). Real grok 1.0.4, real sandbox: asserts ProfileApplied … enforced: true, platform: macos/seatbelt, restrict_network: true, then that the loopback listener received tools/list. Order matters — a sandbox that failed to apply would otherwise report a false pass. Mutation-verified: pointing the plugin at a dead port fails it with ConnectionRefused. Authenticates the way production does (seeded auth.json), so it needs no XAI_API_KEY |
| HTTP MCP loopback on Linux/Landlock | ⏸️ DEFERRED 2026-08-15 — owner call: Mac first, Linux later. Landlock filters TCP connect and may refuse the loopback that Seatbelt allows, so macOS proves nothing about Linux. The test exists (landlock-gate.linux.test.ts) and its workflow is manual dispatch only; it cannot pass until someone mints an xAI API key and adds it as the XAI_API_KEY repo secret. No such key exists anywhere today |
| CI runs this suite | ✅ CLOSED 2026-08-15. .github/workflows/grok-bridge-test.yml runs vitest on every PR touching this package. First real run: 172 passed / 3 skipped. The 3 skips are the live + Landlock + macOS gates, all of which need a real grok and are opt-in by env var |
| Fenced fail-closed | ✅ CLOSED 2026-08-15. Verified against the real grok 1.0.3, fenced, with a seeded auth.json: the agent raised session/request_permission twice, Bash was refused (stopReason: "cancelled", FENCED_BASH_REACHED never ran) and room__say was allowed. The gate is a real control, not an untested branch. ⚠️ That same run found the gate was too closed — see below |
The fenced-mute bug (2026-08-15)
A blanket cancelled on every agent-initiated request made a fenced agent permanently mute.
A clean fence loads no permissions, so grok raises session/request_permission for every tool
call — including room__say, the agent's only voice. The bridge refused it, and the turn ended with
stopReason: "cancelled", spoke: false, and nothing said. The skeleton's entire milestone
("reply in a 1:1 DM, and room__say in a room") was impossible as shipped.
The design spec predicted it verbatim: "clean fence ⇒ everything asks … the bridge must explicitly allow it or the agent goes mute."
Why 156 tests missed it. FakeAcpChild never raises a permission request on its own, so
onRequest was only ever exercised by tests that constructed the request themselves — and none did.
The earlier unfenced spike could not see it either: unfenced, grok asks for nothing at all, which
is exactly why that run looked healthy.
GrokDriver.onAgentRequest now allows exactly one tool — GROK_ROOM_SAY_TOOL_NAME, imported rather
than restated so the allowlist cannot drift from the tool actually shipped — by selecting the
agent's own offered allow_once option. Everything else, and any non-permission request, still
ends the turn. If a future grok stops offering allow_once, it refuses rather than inventing an id.
Four tests now cover the branch, using the frame shape captured from the real binary.
Security notes
- 🔐 Never run grok with
--debug-file. It writes the OAuth bearer token in plaintext. UseRUST_LOG=xai_grok_shell=trace. - The room MCP URL carries an auth token.
.mcp.jsonis written0600and the URL is never passed in argv. It is still a credential anywhere it is echoed — traces, crash reports,lsof. Treat any diagnostic capture as a secret. - Daemon auth is
XAI_API_KEYin the process env (not baked into the launchd/systemd plist) or a seededauth.jsoninside the fence. - The fence (
HOME+GROK_HOME) is required even with tools off. Unfenced, grok loads the operator's entire personal MCP fleet.
Testing
npx vitest run # the gate — run locally before every commit
npm run typecheck # a BASELINE, not a gate. Observed: 2 errors