Skip to content

feat(http-source): push one-shot commands (type: "cmd") via sync + ack, sandbox-safe - #249

Open
m0Nst3r873 wants to merge 1 commit into
Tencent:mainfrom
m0Nst3r873:feat/http-source-cmd
Open

feat(http-source): push one-shot commands (type: "cmd") via sync + ack, sandbox-safe#249
m0Nst3r873 wants to merge 1 commit into
Tencent:mainfrom
m0Nst3r873:feat/http-source-cmd

Conversation

@m0Nst3r873

Copy link
Copy Markdown
Collaborator

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 (syncprocessCommands), ack (success/failed), and retry semantics are all reused unchanged — only a new type === 'cmd' branch and its executor were added.

Security design

  • teamai subcommands onlyargv[0] must strictly equal teamai; anything else is rejected (acked failed) and never executed. No arbitrary-shell / RCE surface.
  • No shellexecFile(process.execPath, [entry, ...args]); shell metacharacters (;, |, &, $, …) and null bytes are literal, no PATH dependency (works in bundled-node sandboxes: WorkBuddy / CodeBuddy / CloudStudio).
  • Bounded — 120s timeout, 4 MiB maxBuffer, error detail capped at 200 chars (avoids leaking large stderr back to the backend).
  • Kill switchTEAMAI_DISABLE_REMOTE_CMD=1 rejects cmd (acked failed with remote 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=1 keeps its original meaning.

Changes

  • src/local-agent.tsparseTeamaiCmd (restricted tokenizer + allowlist), resolveCmdEntry, runCmdCommand; type === 'cmd' branch in executeCommand; cmd field on LocalAgentCommand; sandbox guard → skipReport.
  • src/builtin-hooks.ts — export resolveTeamaiEntryScript for 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 --noEmit clean
  • npx vitest run1787/1787 pass
  • Unit: normal cmd, non-teamai prefix rejected (not executed), env-disabled, subcommand non-zero exit (stderr in ack), quote tokenization, and sandbox no-prune regression guard.
  • npm run build
  • Real-CLI E2E via teamai hook-dispatch session-start against 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 client

Out 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

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(http-source): push one-shot commands (type: "cmd") via sync + ack, sandbox-safe

1 participant