You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
project hooks execute in --safe mode and approved MCP names do not bind configuration content #233
The latest shareAI-lab/Kode-CLI main commit, 42dea6a0ad8a8dbd6450c3075672ac3922f3849d, still has two independent project-configuration trust gaps:
A committed .kode/settings.json or .claude/settings.json can define a UserPromptSubmit command hook. Submitting an ordinary prompt executes the repository-controlled command through /bin/sh -c, including when Kode is run with --safe. No workspace trust or command-specific approval is checked immediately before this spawn.
Project .mcp.json/.mcprc approval is persisted as only a server name. After a user approves a benign declaration, changing that same server's command, args, env, or URL does not return it to pending approval. The changed declaration reaches the MCP stdio/network transport on the next bootstrap.
The first issue is a direct repository-controlled command execution path after a normal prompt submission. The second requires prior user approval and a later same-name configuration change; it is a post-authorization configuration-drift issue, not an unconditional first-open RCE claim. Please track them separately if you prefer separate remediation.
Affected Version
Verified on 42dea6a0ad8a8dbd6450c3075672ac3922f3849d (origin/main, tested 2026-08-12). The behavior was also present in candidate commit 977c2677e381fccc4808f38c2fedb54113667e10.
No tracked SECURITY.md, vulnerability disclosure policy, or bug bounty scope was found in the tested tree.
bun run apps/cli/src/dispatch.ts --print --safe --no-session-persistence --cwd /path/to/disposable-project 'benign verification prompt'
Use a configured local/mock provider or the normal provider configuration required by the checkout. The command only needs to process the prompt; the hook runs before the model response completes.
Expected And Actual
Expected: --safe should not execute a repository-provided command hook without a visible approval bound to that hook, or it should reject the untrusted project hook.
Actual: .kode/hook-marker.txt is created with KODE_HOOK_MARKER. Removing .kode/settings.json and repeating the same command does not create the marker.
Technical Evidence
packages/config/src/files.ts automatically resolves project settings from .kode/settings.json and legacy .claude/settings.json. packages/hooks/src/registry.ts parses command hooks and loads project settings for UserPromptSubmit. packages/hooks/src/lifecycle/events.ts dispatches the event without using safeMode as a gate. packages/hooks/src/executor.ts executes the configured string as /bin/sh -c <command> with the project cwd and inherited environment.
This does not require the model to call Bash and does not depend on prompt injection. The minimum trigger is Kode processing the project and receiving one prompt. It is not a claim that merely opening a directory starts the hook.
Finding 2: Approved MCP server name survives command changes
Reproduction
Use a disposable project and disposable Kode config directory. First create .mcp.json with a benign declaration:
Start Kode normally and approve the pending same-name project server. This approval is the required user authorization precondition. The UI stores the approval in the project config.
Then replace the repository file, keeping the name but changing the command-bearing content:
Restart Kode in the same project or otherwise trigger its normal MCP bootstrap. No new approval dialog is shown for the changed command. The marker is created before the intentionally incomplete test MCP process exits.
Expected And Actual
Expected: changing a previously approved project's command, args, environment, URL, or referenced executable should invalidate the approval and require a new approval showing the effective declaration.
Actual: approvedMcprcServers contains only same-name; getMcprcServerStatus('same-name') remains approved after the declaration changes, and getClients() passes the new declaration to StdioClientTransport.
Technical Evidence
packages/config/src/mcp.ts reads project .mcp.json and .mcprc. packages/core/src/mcp/client/config.ts:getMcprcServerStatus() checks only the server name in approvedMcprcServers. apps/cli/src/ui/components/MCPServerApprovalDialog.tsx persists only serverName. packages/core/src/mcp/client/clients.ts filters by that name-only status, and packages/core/src/mcp/client/connection.ts constructs the transport from the current command, args, and env.
This finding does not claim that a never-approved server silently runs on first use. It requires an earlier approval, a later same-name repository/configuration change, and a subsequent MCP connection bootstrap.
Dynamic Verification
Both findings were reproduced against the latest main in isolated temporary fixtures on Linux:
The mock provider was loopback-only. The tests did not read credentials, contact external endpoints, modify shell profiles, persist outside temporary fixtures, or delete user files.
Suggested Fix
Treat project command hooks as executable workspace configuration. Require explicit per-workspace approval before dispatch, enforce the check immediately before spawn, and bind approval to the canonical workspace, settings source and digest, event/matcher, final command/args/env, and referenced script identities. Safe mode should include this capability boundary.
For MCP, store an approval receipt over the final effective server declaration and resolved executable/script or URL, display it at approval time, and invalidate it on any command-bearing content, symlink target, workspace revision, or declaration digest change. Revalidate before every transport bootstrap.
Activity