Skip to content

Trust model

threadle is a local tool that reads other tools’ session storage and drives their CLIs. Its threat model is a single-user localhost app: the adversary is a web page in your browser, a malicious custom-node folder, or a format change upstream. Not a remote attacker on the open internet, because nothing binds off-loopback.

ClaimPrecise reading
Read-onlyDiscovery parsers never mutate Claude / opencode / Cursor / Antigravity / Codex / Copilot / Grok storage. Runs shell out to those CLIs. they create new sessions in their own storage, exactly as a terminal invoke would.
No network from threadleThe threadle process does not upload your code or transcripts, and does not fetch list prices at runtime. Agents you launch still talk to Anthropic, OpenAI, Cursor, Google, xAI, etc. Under your credentials.
Localhost onlyBound to 127.0.0.1. Every request still faces Host and Origin guards (DNS rebinding / CSRF). There is no session auth token, because the intended user is you on this machine.

Session discovery is parse-only. Files and databases are never modified by threadle.

ProviderStorage threadle reads
Claude Code~/.claude/projects. JSONL transcripts
opencode~/.local/share/opencode/opencode.db. SQLite
Cursor~/.cursor/projects. Transcripts and chat metadata
Antigravity~/.gemini/antigravity-cli. Brain JSONL + conversation summaries
Codex~/.codex/sessions. Rollout JSONL
GitHub Copilot~/.copilot/session-store.db + session-state/
Grok Build~/.grok/sessions. Per-cwd session dirs

The database is opened read-only and the JSONL files are only ever read. Parsers skip records they do not recognize rather than throwing. These formats are undocumented and churn, and a new message kind upstream must not break your workflow list.

Agent runs go through each tool’s official CLI. Those tools then write their own session storage exactly as they would if you had invoked them from a terminal yourself. That writing is theirs, not threadle’s.


Everything threadle persists lives under ~/.config/threadle. Settings → threadle internals lists it item by item with sizes, and marks which items are safe to clear.

PathContentsClearable
settings.jsonEditor, billing, preferencesno
graphs/Your workflows and sub-workflows, one JSON eachno
graphs/versions/<id>/Snapshots on save (cap 30). Graph history
nodes/Custom nodesno
skills/custom, skills/importedSkillsets you authored or importedcustom: no
favorites.jsonJump-list bookmarks. Favorites
payloads/Content-addressed context payloadsyes
search.dbSQLite full-text index over transcripts and payloadsyes
runs/jobs.jsonl, runs/logs/Run history and per-run logsyes
runs/<jobId>/outputs.jsonPer-node text cache for partial / replay runsyes
runs/injects.jsonlLineage. Which context went whereyes
triggers.jsonCron / path-watch workflow triggers. Triggers
models-pricing.jsonLegacy price-list leftover (optional. Prices ship bundled)yes
tmp/Scratch files written while injecting contextyes

The only files threadle writes outside that directory are the ones an agent CLI creates in your project when you run it there.


A process on 127.0.0.1:4570 that can launch coding agents with tool access is an attractive target for DNS rebinding and browser CSRF from another tab. Binding loopback alone is not enough.

The server binds 127.0.0.1 on port 4570 (--port to change it). There is no bind-to-0.0.0.0 flag and no remote mode. There is also no auth token. Protection is Host + Origin checking, not a shared secret.

Two guards sit in front of every request:

Host guard (DNS rebinding). A hostile site can point its own domain at 127.0.0.1 and then treat threadle as same-origin. Any request whose Host header is not 127.0.0.1, localhost, or [::1] (with optional port) is rejected with 403 forbidden host.

Origin guard (CSRF). Browsers attach Origin to cross-site and same-origin writes alike. Any non-GET/HEAD/OPTIONS request carrying an Origin from another host is rejected with 403 cross-origin write rejected. A request with no Origin at all is curl or the CLI, which is fine on loopback.

Every response also carries:

Content-Security-Policy: default-src 'self'; script-src 'self';
style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self';
font-src 'self'; object-src 'none'; base-uri 'none';
frame-ancestors 'none'; form-action 'self'
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: no-referrer
Cross-Origin-Resource-Policy: same-origin

frame-ancestors 'none' plus X-Frame-Options: DENY means the UI cannot be framed. connect-src 'self' means the page cannot phone anywhere. Request bodies to /api/* are capped at 20 MB.

One class of hop, and it is yours, not “nothing ever leaves”:

  1. The agent CLIs you invoke. Pressing ▶ sends prompts and project context to Anthropic, OpenAI, Cursor, or whoever that CLI is configured for, exactly as running the CLI yourself would. threadle is the local driver. The network hop is the agent’s.

List prices do not leave or enter at runtime. They ship as a vetted snapshot with the package (CI refreshes from models.dev via PR). See Costs.

No telemetry, no analytics, no update check, no crash reporting from threadle itself.


Security in the sidebar lists:

  1. Elevated permission modes on discovered sessions (Claude meta.permissionMode ≠ default).
  2. Workflow agent settings. Agent-def nodes across saved graphs with non-default Claude permissionMode or Codex sandbox / askForApproval (scanned from loaded graphs).
  3. Live sessions and the static safeguards list (localhost, Host/Origin guards, read-only discovery, argv-only injects).

Per-agent posture is also set on the agent card / inspector. See Agents.


Custom nodes are code you placed on disk, so running them is the same trust level as your shell. threadle still fences them in:

GuardrailDetail
No shellcommand is an argv array. There is no shell, so no interpolation, globbing, or injection through node input.
Working directoryPinned to the node’s own folder.
Isolated processEvery run is a fresh child process. A crashing or hanging node cannot take the server with it.
TimeouttimeoutMs, default 60 000 ms, hard-capped at 300 000 ms.
Output cap4 MB of process output. Beyond that the run fails.
ConcurrencyAt most 8 custom-node processes at once.
Environment allowlistOnly a fixed allowlist (PATH, HOME, USERPROFILE, TEMP/TMP, …) plus THREADLE_NODE and the node’s own params. The server’s environment. API keys included. Is not inherited by default.
Explicit opt-outA descriptor may declare "env": "inherit" to get the full parent environment. It is one visible line in node.json, surfaced as a decision you made.
Metadata is sandboxed tooFor a node with no node.json, class metadata is extracted by a separate child process. User code never executes inside the threadle server, not even at scan time.
Import executes nothingCloning, copying, or uploading a node runs none of its code. It runs when you wire it and press Run.

Uploaded folders are additionally path-sanitized (no absolute paths, no ..), skip .git and node_modules, and are capped at 200 files / 2 MB per file / 10 MB total.


SurfaceTrust notes
threadle mcpHosts spawn the process. Published workflows run with approveAll: false (gated graphs stay in the UI). Default publish allowlist is empty (none). List graph ids or set null / * for all. Nested MCP recursion is refused.
◈ MCP tool nodeCalls a discovered server: stdio (command you declared) or HTTP/SSE (url + optional headers). Same class of risk as a custom node for stdio. Remotes use whatever auth you put in headers. Child env for stdio is the custom-node allowlist, plus that server’s own env. Not a full copy of the threadle process. Turn the client off or disable individual servers in Settings.

Unix-domain sockets / WebSockets are not supported.

See MCP.


  • Never mutates ~/.claude/projects (runs may create new Claude sessions via the claude CLI)
  • Never mutates the opencode SQLite database (same: new sessions via opencode)
  • Never mutates Cursor transcript files (same: new sessions via agent)
  • Never mutates Antigravity brain / conversation stores (same: new sessions via agy)
  • Never mutates ~/.codex/sessions (same: new sessions via codex exec)
  • Never installs, updates, or executes a custom node on its own
  • Never uploads your transcripts, prompts, or outputs from the threadle process itself