Please do not open a public GitHub issue for security vulnerabilities.
Instead, report them privately using GitHub's private vulnerability reporting (the "Report a vulnerability" button on the repository's Security tab). Include a description of the issue, the affected version, and steps to reproduce.
We will acknowledge your report and work with you on a fix and coordinated disclosure.
This repo ships two tools with different data boundaries. Read the one you run.
- It runs entirely on your machine. Prompt text is sent only to the local embedding
endpoint you configure — never to any third-party or cloud service. Background-task
notifications (
<task-notification>turns) are not sent anywhere: no retrieval runs for them. - No raw prompts are persisted. The usage log (
~/.meta-router/usage.jsonl) stores a SHA-256 hash of the prompt plus its length — never the raw text. It also records thesession_idandprompt_idthat Claude Code supplies on the hook payload, the skills surfaced, the skills withheld because the model cannot invoke them (hidden— skill IDs from the index, the same kind of value assurfaced, never prompt text), the top cosine, latency and the decision mode. Those two identifiers are opaque and carry no prompt content. Know whatsession_idis before you share this file, though: Claude Code names each session transcript~/.claude/projects/<project>/<session_id>.jsonl, and those transcripts DO contain verbatim prompt text. So the log still holds no prompt text, but it now holds a key into text stored elsewhere on the same machine. On your own machine that changes nothing — the transcript is already readable. If you export or shareusage.jsonl, stripsession_idunless you intend the recipient to be able to look prompts up. - Fail-open by design. On any error, timeout, or cold embedder the hook surfaces nothing and exits cleanly, so it can never block or break a prompt.
- It does not edit your
settings.json. Registering and removing the hooks is always your explicit action.
The only network call the surfacer makes is to the local embedding endpoint you point it at.
The orchestrator's whole job is to dispatch your prompt to another agent CLI, so it does send data off the machine. Being explicit about that is the point of this section.
- Where prompts go. A dispatch runs the lane's own CLI as a child process:
claude(Anthropic),codex(OpenAI),glm(Z.ai — PRC-hosted), or a local model, chosen by the router. Your prompt goes wherever that lane's provider is. - Repository context is gated for third-party lanes. Any lane outside your own
Anthropic/OpenAI subscriptions (currently
glm, and the seated-but-unshippedgroq/cloudflare/gemini/nim) is deny-by-default for repository context: a dispatch may only run inside a directory you have explicitly allowlisted inglm_allow_repos. Entries must be absolute — a relative entry would resolve against whatever directory the orchestrator happens to be in, so"."would self-allowlist every checkout in turn, and it is refused. An unresolvable path, a link or junction whose target leaves the allowlisted tree, and an empty allowlist all fail closed, and--forcedoes not bypass it —--forceoverrides quota judgement, never a data export.- Because
os/execruns a child in the parent's directory, "no working directory" is not the same as "no repository context". When the inherited directory is not allowlisted, the dispatch is run in a neutral empty directory so prompt-only is enforced, not assumed. --extramay not carry a filesystem path to a third-party lane at all. Those tokens are forwarded verbatim and resolved by the child against its working directory, so this gate cannot reliably judge what they point at — and it deliberately does not try to reimplement another program's argument parser, whose handling of variadic options and optional values is subtle and free to change upstream. Only flags provably incapable of naming a path are accepted (--model,--output-format,--verbose,--dangerously-skip-permissions, …); everything else —--add-dir,--mcp-config,--settings, an unrecognized flag, a bare positional, anything after--— is refused. Repository context reaches a third-party lane through-cwdplus the allowlist, which is gated in one place with one rule.egress_prompt_only_denied: truecloses the prompt-only path, i.e. dispatches that carry no repository context at all. It is a tightening of that one case, not a master switch: with a repo allowlisted, dispatches inside it still run. To close a third-party lane entirely, empty its allowlist and set this flag, or remove the lane from routing.- Every dispatch records its gate decision and basis on the receipt (
egress_gate) — including quota deferrals and paced deferrals, which are decided after the gate has already ruled — so an export is always countable after the fact.
- Because
- Credentials are never handed to a child by accident. Lane children get a scrubbed
environment: API keys, auth tokens, base-URL and cloud-routing overrides, OAuth token file
descriptors and model pins are removed from the ambient environment before each lane appends
the credentials it deliberately uses. An ambient
ANTHROPIC_API_KEYis otherwise honoured by headless Claude Code ahead of OAuth, silently converting a subscription dispatch into metered API spend. - Receipts, not prompts.
~/.meta-router/orchestrate/dispatch.jsonlrecords routing decisions, quota accounting and gate verdicts. Nothing operator-specific lives in the repo.