fix(safety): keep agents away from CanvasTTY's own tokens and control socket, and tell them the way in - #99
Closed
BIackFIame wants to merge 2 commits into
Closed
BIackFIame wants to merge 2 commits into
BIackFIame wants to merge 2 commits into
Conversation
…ores and sockets Base protection now treats CanvasTTY's private data as credentials. A shell or file tool call that names the agent-control token or descriptor, the gateways' connection records, the provider and plugin secret stores, account homes, the GitHub sign-in or prepared launch runs (paths taken from the app's own userData folder, passed in by the app), or the control and runtime socket folders under the temporary folder, is refused whatever the program: readers, copies, encoders, sqlite3, recursive walks of the app folder, interpreter one-liners and heredocs, curl --unix-socket, nc -U, socat and Python sockets. The model is told calmly that agents cannot control CanvasTTY this way and to ask for an Orchestrator launch. The project, the app's settings, other sockets, an agent's own account home and the bundled control CLI are unaffected.
…hen close An unauthenticated or malformed request to the control endpoint now gets a stable INVALID_REQUEST whose message says only sessions CanvasTTY launched as orchestrators may use it and how to get one, with no protocol details, token names or paths. An HTTP request line (curl, a browser) gets a minimal 403 with the same text. Either way the connection is closed right after, instead of waiting for the idle timeout; the connection cap and the one-request-per-connection rule are unchanged. Tests cover a guessed NDJSON request, garbage and an HTTP request, and that the token file is 0600 and its folder 0700 even under umask 0 and a pre-existing loose folder.
Contributor
Author
|
Combined into #100 together with the other post-merge fixes, so they can be reviewed in one place. The commits are unchanged. |
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.
Based on main (20f6855). Independent of #96 and #97. It merges cleanly with each of them and with both together, and the full suite passes on that merge.
Incident
Someone started an OpenCode agent by hand inside a plain terminal card. CanvasTTY didn't launch it as an orchestrator, so it had no
canvastty_agentsMCP server and no control environment. It still tried to control CanvasTTY:cat.curl --unix-socketandnc -U.The gateway refused every attempt with
INVALID_REQUEST/ "Invalid or unauthenticated control request.". That message doesn't tell the agent what to do instead, so it kept guessing.What changed
1. Base protection treats CanvasTTY's private data as credentials
safety/commandFacts.tsnow records a new fact,appPrivate, andbaseProtection.tsdenies it under a new rule,app-private. That rule is checked before all the others. A shell or file tool call is refused when it names any of the following:agent-control/), the gateways' connection records and sockets (browser/runtime,lifecycle/runtime,orchestration/runtime),provider-secrets.bin,plugin-secrets/,account-homes/,github-oauth.jsonandlaunch-runs/. The app passes these paths in throughcanvasTtyPrivateData(app.getPath("userData"))→DecisionHooks→checkBaseProtection. Nothing is hard-coded per platform.ctty-control-*,ctty-runtime-*,ctty-orch-*,ctty-<uid>-*). These are recognized without the userData path.$CANVASTTY_CONTROL_CONNECTION,$CANVASTTY_*_ADDRESS,$CANVASTTY_*_CAPABILITY).The check doesn't depend on the program. It looks at every word and redirection, at paths after
=or:(--unix-socket=P,UNIX-CONNECT:P,file://), at paths quoted inside interpreter one-liners and heredocs, and at globs. It also seescdinto those folders,$(…)substitutions andbash -c. Interpreter code that builds the path from pieces is caught when the app folder's name and a private name appear together.Other rules:
grep -r,rg,find,tar,rsync…) is refused. A plainlsorcat settings.jsonof that folder is still allowed.node "$CANVASTTY_CONTROL_CLI" …,canvastty-control.mjs --connection …).2. The control gateway explains the refusal
In
AgentControlGateway, an unauthenticated or malformed NDJSON request still getsINVALID_REQUEST. Its message is now stable guidance (below), and the gateway closes the connection straight after instead of waiting for the 10 s idle timeout. An HTTP request line (curl'sGET / HTTP/1.1, a browser, an HTTP/2 preface) gets a minimalHTTP/1.1 403 Forbiddenwith the same text intext/plain, then the connection closes. The message has no protocol details, token names or paths. The 32-connection cap, the one-request-per-connection rule and the request size limit are unchanged.3. Token file permissions
Checked, and no change was needed. The token and
connection.jsonare created with mode 0600 in a folder that ischmoded to 0700, and the socket folder is 0700. A new test covers this underumask 0with a pre-existing 0777 folder.Messages shown to agents
Base protection deny:
Control endpoint (NDJSON
error.message, and the body of the HTTP 403):Scope and limits
Tests
tests/base-protection-app-private.test.mjs, table-driven, with a fake HOME and a fake userData folder:cat,head,grep,cp,base64 <,xxd,strings,sqlite3; globs;cdthen a relative read;$(cat …);bash -c;find,grep -r,rgandtarof the app folder;python3 -c,node -eand heredoc readers; a Python path built from pieces;curl --unix-socket(both spellings),nc -U,socat UNIX-CONNECT:, a PythonAF_UNIXconnect, a runtime socket in userData; the descriptor and capability variables.grep -rn plugin-secrets src, a commit message that mentions them, the app'ssettings.json,lsof the app folder, the control CLI with its descriptor, the Docker socket and other sockets,ls $TMPDIR,/tmp/ctty-notes, and a URL containingagent-control/token-1.DecisionHooksforwards the private data.tests/agent-control.test.mjs:Content-Lengthand the same text.--test-concurrency=2, fake HOME). Also 1170 passing on this branch merged with fix(startup): load the application surface only after the startup page settled #96 and fix(settings): pasting a provider API key no longer blacks out the window #97.npm run buildandaudit:secretspass.