feat(http-source): push one-shot commands (type: "cmd") via sync + ack, sandbox-safe - #249
Open
m0Nst3r873 wants to merge 1 commit into
Open
feat(http-source): push one-shot commands (type: "cmd") via sync + ack, sandbox-safe#249m0Nst3r873 wants to merge 1 commit into
m0Nst3r873 wants to merge 1 commit into
Conversation
Add a `type: "cmd"` one-shot command to the HTTP source sync channel so the backend can push a single teamai subcommand (e.g. `teamai uninstall --agent claude`) to run once on the client, with the result reported via the existing ack channel. Delivery, ack, and retry semantics are reused unchanged. Security boundary: - teamai subcommands only — argv[0] must strictly equal `teamai`; anything else is rejected (acked failed) and never executed. - No shell — execFile(process.execPath, [entry, ...args]); shell metacharacters are literal, no PATH dependency (works in bundled-node sandboxes). - 120s timeout, 4 MiB maxBuffer, error detail capped at 200 chars. - TEAMAI_DISABLE_REMOTE_CMD=1 kill switch (acked failed). Also change the CloudStudio sandbox guard from "skip report + sync" to "skip report, still run sync + processCommands", so sandboxed agents can receive pushed commands. Report-side bookkeeping (plugin reconcile, binding prune, config save) stays gated behind the report path to avoid pruning host bindings not mounted in the container. Docs updated in both languages; unit tests cover the tokenizer, rejection, kill switch, subcommand failure, and the sandbox no-prune guarantee. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.
Closes #235.
Summary
Adds a
type: "cmd"one-shot command to the HTTP source sync channel. Beyond the existing install/uninstall resource commands, the backend can now push a single teamai subcommand (e.g.teamai uninstall --agent claude) to run once on the client, with the result reported via the existing ack channel:{ "id": 42, "type": "cmd", "cmd": "teamai uninstall --agent claude" }Delivery (
sync→processCommands), ack (success/failed), and retry semantics are all reused unchanged — only a newtype === 'cmd'branch and its executor were added.Security design
argv[0]must strictly equalteamai; anything else is rejected (ackedfailed) and never executed. No arbitrary-shell / RCE surface.execFile(process.execPath, [entry, ...args]); shell metacharacters (;,|,&,$, …) and null bytes are literal, no PATH dependency (works in bundled-node sandboxes: WorkBuddy / CodeBuddy / CloudStudio).TEAMAI_DISABLE_REMOTE_CMD=1rejects cmd (ackedfailedwithremote cmd disabled by client).Sandbox delivery
CloudStudio sandbox previously skipped report and sync entirely (to avoid a duplicate agent card), so a container could never receive pushed commands. Changed to skip report, still run sync + processCommands — sync produces no card, so there is no duplicate risk. Report-side bookkeeping (plugin reconcile, binding prune, config save) stays gated behind the report path, so host bindings not mounted in the container are never pruned.
TEAMAI_ALLOW_SANDBOX_REPORT=1keeps its original meaning.Changes
src/local-agent.ts—parseTeamaiCmd(restricted tokenizer + allowlist),resolveCmdEntry,runCmdCommand;type === 'cmd'branch inexecuteCommand;cmdfield onLocalAgentCommand; sandbox guard →skipReport.src/builtin-hooks.ts— exportresolveTeamaiEntryScriptfor entry reuse.docs/usage-guide.md/docs/usage-guide.zh-CN.md— cmd protocol, security boundary,TEAMAI_DISABLE_REMOTE_CMD, updated sandbox note (bilingual).Test plan
npx tsc --noEmitcleannpx vitest run— 1787/1787 passnpm run buildteamai hook-dispatch session-startagainst a mock HTTP server:teamai --version→ ack{status: success}(subprocess actually ran)rm -rf /→ ack{status: failed},Rejected cmd: only "teamai"...(not executed)TEAMAI_DISABLE_REMOTE_CMD=1→ ack{status: failed},remote cmd disabled by clientOut of scope
No arbitrary shell / executables, no new ack status (keeps
success|failed), no command-queue persistence / idempotency dedup (relies on backend id + existing sync semantics), no interactive/stdin commands.🤖 Generated with Claude Code