Repository navigation
plat 473
PLAT-473 — A coding CLI's own shell has no platform credentials: say so, and redirect the call (Muse hook)
| Field | Value |
|---|---|
| State | fixed on main |
| Priority | - |
| Product | coding-agents |
| Area | muse |
| Summary | fixed on main for the prompt (all CLIs) and Muse, not deployed: a platform curl in Muse's native bash is refused with the instruction to use the bridge shell; Claude Code, Cursor and Agy have the same hook (Agy not ch… |
| Coordination | Value |
|---|---|
| State | fixed on main for the prompt (all CLIs) and the hooks of Muse, Claude Code, Cursor and Agy (shell, Monitor and fetch tools; Muse cron_create refused); Codex has no hook; needs a rebuild and restart |
| Date | 2026-10-04 |
| Owner | coding-agent-bridge |
| Related | PLAT-468 (Muse had no bridge at all), PLAT-364 (Full CLI) |
The upwork Builder chat (Muse, Full CLI mode) had its bridge working but could not stamp
the contract: it ran a plain curl through Muse's native bash, got "missing or
invalid Authorization header", and reported that it had no credentials. The platform
token and MCP_CUSTOM/MCP_AUTH exist only in the bridge's own shell
(mcp__api_bridge__execute_shell_command), by design: a token in the CLI's own
environment would be readable by anything the agent runs. The agent chose the wrong
shell and nothing told it so.
- mcpagent
runtime_http_skill.go(inline routing text and the runtime-http skill, so every CLI): states that the CLI's own shell has noMCP_AUTH/MCP_CUSTOM, that a platform curl there fails with the Authorization error, and to run the same command through the bridge shell tool instead of looking for tokens. - multi-llm-provider-go Muse adapter: a
PreToolUsehook, installed whenever the bridge is mounted (including Full CLI mode, which has no allowlist hook). It refuses a nativebash/bash_inputcall that uses$MCP_CUSTOM,$MCP_AUTH,$MCP_MCP,$MCP_API_TOKENor a/tools/(custom|virtual|mcp)/route, and namesmcp__api_bridge__execute_shell_command. Anything else in bash is untouched; no secret is revealed and no capability granted. Kept next to the person's own hooks and restored byte-exact after the run. - Verified against real Muse: a platform curl in bash is blocked and the agent receives the message; an ordinary command still runs. Unit tests for the script, the settings merge (no allowlist, person's hooks kept, no bridge means no hook) and the prompt text.
- Builder
go.modpins mcpagent0a493e1and provider7fcad95(a library change only reaches the app through these pins).
-
Claude Code (
345dd4f): a nodePreToolUsehook onBash(tool_input.command), added to the per-launch--settingsin the tmux and structured launches whenever the bridge is mounted; namesmcp__api-bridge__execute_shell_command. Real Claude Code 2.1.289: platform curl blocked with the message,echo/lsran. The Go-built launches and the Landlock path were not run live. -
Cursor (
32d93ff): the adapter's existing.cursor/hooks.jsonshell hook (mlp-allow-shell.sh, which only allowed) now refuses a platform command in Full CLI mode (eventbeforeShellExecution, fieldcommand); a bash script, no node. Real cursor-agent 2026.10.01: platform curl blocked,echo hiran, bridge calls not blocked. The adapter replaces a person's ownhooks.jsonfor the session and restores it; it does not merge theirs in (unchanged behaviour). -
Agy (
5aaec25): the existing workspacePreToolUsegate also denies a nativerun_commandplatform call in Full mode, namingcall_mcp_toolwithexecute_shell_command. NOT checked against real Agy: the CLI on the machine is not logged in; the hook was run as a subprocess against realistic payloads, the real payload's argument key is unconfirmed.
Owner asked whether the hooks cover all tools and whether the CLIs' own subagents run them. Results, each against the real CLI unless noted:
-
Muse (
75bde61): the hook also checksmonitorcommands andweb_fetchURLs (platform route, or the host:port ofMCP_API_URLwhen the hook process has it; Muse's own env does not, so only the route match fires in practice) and REFUSEScron_createby name, because a Muse cron runs unattended outside the platform's schedule controls (owner decision).cron_listandcron_deletestay allowed. Subagents run the hook (childbashplatform curl blocked; tested with delegation "auto", the setting the adapter writes whensubagent_spawnis allowed). -
Claude Code (
800619c): matcher is nowBash|PowerShell|Monitor|WebFetch. WebFetch is refused for a platform route or anMCP_API_URLhost:port match. A subagent's native Bash is blocked too. Monitor and PowerShell: unit tests only. -
Cursor (
c270cb4): the shell script also reads aurlfield and a secondpreToolUseentry coversShell|WebFetch|WebSearch. Cursor fires NO hook for its web tools (fetch runs on Cursor's servers, which cannot reach localhost), so that part is inert today. Subagents run the projecthooks.jsonfor their shell calls. - Agy and Codex: unchanged (Agy not checked live; Codex has no hook).
- Findings not acted on: only the shell and URL tools are hooked; a script file that calls the platform, or another native tool, is not matched. Nothing here protects the token (it never enters those shells); the hooks only turn a call that cannot succeed into a clear instruction.
Third round: credential probing, platform address, fuller message, resume note (provider pinned 72a81a9)
Trigger: the resumed jobsearch Muse chat checked for credentials in its native bash
(for v in MCP_AUTH ...; do [ -n "${!v}" ] ..., a curl to the server root) and concluded
the bridge was missing, although the bridge was up and the tools were in its list. The hook
let it through because it names no $MCP_* and no /tools/ route.
-
Muse (
72a81a9), Claude Code (f36bdaf), Cursor (df1ab0a): the shell hooks also refuse (a) a command containing the platform's own host:port and (b) credential probing (a command that names MCP_CUSTOM/MCP_AUTH/MCP_MCP/MCP_API_TOKEN and reads the environment: printenv, env, echo$…, $ {!v}, test/[ -n/-z, compgen -e, declare -p, os.environ…).grep -rn MCP_AUTH code/,catof the helper,python3 code/x/main.py,ls,git statusand other sites still run. The deny reason (about 500–640 chars) says the variables exist only in the bridge shell, that their absence is expected, that themcp__<server>__*tools in the list are the working bridge, and to useexecute_shell_command,search_toolsandget_api_spec. -
Muse hooks run with a scrubbed environment (found with a real
curl 127.0.0.1:18743that got through): the hook process had noMCP_API_URL. The mounted bridge's host:port are therefore passed to the hook as command-line arguments (hosts only, never a token). Claude Code's and Cursor's hooks inherit the CLI's environment (checked with real runs). -
Muse resume note: when a Muse terminal is launched with
muse resumeand the bridge is mounted, the first real message carries a one-line note that the bridge is mounted and any earlier "no bridge" statement is out of date (once per terminal; never on fresh runs; theexec --session-idlane adds it on every resumed call). It goes into the text sent to Muse, not into the stored user message; Muse's own transcript contains it. - Real-CLI results: the exact failing probe refused with the new message in Muse, Claude Code and Cursor; normal commands ran. Resume note: unit-tested only.
Audit: Claude Code has an explicit tool list and a strict MCP config; Codex disables everything except the shell and its own subagents; Pi is bridge-only; Muse in Full CLI mode had NO list, so every tool it ships, and every tool a later update adds, was allowed. Owner decisions (2026-10-04): Muse's own goals out; memory and peer-session tools out; unlisted and future tools refused by default.
-
musecli_mcpsettings.go: with the bridge mounted and no bridge-only allowlist (Full mode), the settings step installs the existing PreToolUse allowlist hook withmuseFullNativeToolsand its own deny reason; the launch flags are unchanged (still--yolo; the bridge-only--disable-shell/--disable-writeare not applied). MCP tools (mcp__*) are never refused. - Allowed:
read_file,search,write_file,edit_file,bash,bash_input,monitor,web_fetch,web_search,read_skill,write_todos, the sixsubagent_*tools,work_status,work_list,work_stop,request_user_input(plustool_searchandsubmit_reminder_decision, always allowed by the hook). - Refused: goals (
get_goal,create_goal,update_goal,report_progress), memory (read_memory,add_memory,edit_memory), peer sessions (list_peer_sessions,send_session_message),cron_*,snooze_reminder,workflow, and anything not listed. Also turns workflow triggers off and subagent delegation on. - Real Muse (one Full-mode turn with the generated settings): file read/write, bash,
todos,
web_fetchand a subagent worked;get_goal,read_memoryandcron_createwere refused with the message.list_peer_sessionsdoes not exist in headlessexecmode (the hook refuses it in the TUI; unit-tested). - Adding a tool is one line in
museFullNativeTools; a refused tool shows up in the chat transcript as "tool blocked by hook".
Audit with real Cursor runs: its own CreateGoal, UpdateGoal, AskQuestion, SwitchMode,
TodoWrite and ReadLints run in Full mode, and Cursor fires NO hook for them (a control
run showed the hook firing for Shell only), so they cannot be refused like Muse's. The Full-mode
guidance now tells the agent not to use CreateGoal/UpdateGoal/AskQuestion (goals and
scheduling belong to the platform; ask in the reply, or use the clarification tool if listed;
a chat nobody is watching must never wait on a question). Real cursor-agent: without the line it
used both; with it it declined both and asked in its reply. Prompt-level only, nothing enforces it.
- Agy was not audited tool by tool (it needs a logged-in run); its Full-mode gate allows any tool name.
-
Codex has no hook. On Codex 0.160.0 a config-file hook does not run unless trusted;
the only automatic route is
--dangerously-bypass-hook-trust(trust off for every hook Codex loads, including a project's), which was not used. Decision for the owner: persist trust for our hook (needs Codex's trust hash) or stay with the prompt line. Native-tools mode also passes--disable hooks. - Pi has no hook support; it relies on the prompt text.
- Agy's hook needs one live run (logged in, Full CLI, Seatbelt) to confirm the deny text reaches the agent.
- Chats started before the restart keep the old prompt.
PLAT-473, fixed on main for the prompt
(all CLIs) and Muse, not deployed: a platform curl in Muse's native bash is refused with
the instruction to use the bridge shell; Claude Code, Cursor and Agy have the same hook
(Agy not checked live), Codex has none (trust issue, owner decision). The hooks also cover
fetch and Monitor tools, Muse refuses cron_create, and the CLIs' own subagents run them.
Auto-synced from docs/ on main. Edit there, not here.