Sato Hub
← Back to blogSato Check: what an agent's code can do with your keys, before you install it

Sato Check: what an agent's code can do with your keys, before you install it

We installed the 40 most-used crypto agent packages with planted test keys. None sent a key out. 13 touch your wallet key, 4 can move funds on their own — and here is how we know, version by version.

2026-09-26 · 6 min read

An agent that trades, pays or signs holds a key. Every package, MCP server and skill you give it can reach that key. Most of the time that is the point: a wallet signs locally, and that is what a wallet does. The question nobody answers before install is what else the code does with it.

This year's public record says the question is not academic. [Koi Security](https://thehackernews.com/2026/02/researchers-find-341-malicious-clawhub.html) found 341 of 2,857 ClawHub skills built to take credentials from the agents that loaded them, many dressed up as crypto trading tools. [Snyk](https://snyk.io/blog/toxicskills-malicious-ai-agent-skills-clawhub/) found a critical issue in 13.4% of the 3,984 skills it reviewed. [Knostic](https://www.knostic.ai/blog/when-auto-signing-sends-your-wallet-private-key-to-a-remote-server-a-malicious-mcp-package-on-npm) documented an npm MCP package, downloaded 2,327 times, whose "auto-sign" path copied the wallet's private key into a request header and sent it to a remote server. In every case the behaviour was sitting in the published artifact. Nobody had read it for that.

What Sato Check does

Give it a package, an MCP server, a skill, a repo or an x402 endpoint — or an install command, and it checks every dependency in it. It answers four questions, in this order:

1. Does it take your key? 2. Does your key leave? 3. Can it move funds on its own? 4. What changed? — against the last version we profiled.

Every line of evidence carries one of three classes:

  • ▸declared — the project says so: README, config, env list, tool description.
  • ▸traced — our static read of the exact published artifact found it, file and line.
  • ▸observed — we installed and started it in a sandbox holding planted, never-funded test keys, with every outbound request passing through a logging proxy, and watched where the keys went.

The one thing a profile states as fact is an observed egress: a test key we planted was sent to a named host. "Not observed" always carries its limit — it covers install and start-up under our test conditions, not code paths that run later or only on a trigger. "Unknown" means we could not look, and we say so rather than guess. A signing library that turns a key you hand it into an account is described as handling a key, not taking one; that distinction is the difference between a useful answer and noise.

A profile describes what we read and ran, with dates. It is not a rating, an audit or an endorsement.

The launch report

Before launch we profiled a fixed set of 50 targets, chosen by a rule we published before the results: the 40 most-used npm packages in the Sato Hub library that our sandbox can install and run (live MCP servers first, then by GitHub stars), plus the 10 most-installed crypto agent skills. A person hand-checked every "reads" and "declares" row against the published code before anything went out.

  • ▸Key egress: 0 of 40. No planted test key left any of the 40 packages during install and start-up. The sandbox was proven first on a synthetic package that sends a planted key out — it was caught in raw, hex and base64 form.
  • ▸13 of 40 touch your wallet key. 6 read one from your environment or a key file; 7 ask for one in their setup. 24 showed no key read in what we read, and 3 were too large for this version of the reader (over 15 MB) — those read "unknown", not "none".
  • ▸4 of 40 expose actions that can move funds on their own — trades, transfers, withdrawals — between 2 and 30 each. Whether a spend limit can be set on them is still unknown; we say so on each profile. For 32 more we could not read what they expose, and those read "unknown", not "none".
  • ▸All 10 skills read "unknown". A skill is a document an agent follows, not a package we can trace or run. Each skill page still shows its static disclosure — hosts named, credential requests, remote-to-shell pipes — which describes, never judges.

We also publish our own error rate. The first pass flagged 4 of the 40 packages as notable. All 4 were mistakes in our rules, not in the packages: two non-wallet secrets read as wallet keys, a signed request header and an unrelated object field read as the key reaching the network, and variable names colliding across a bundled file. Each rule was fixed and regression-tested before publication. After calibration: 0 notable of 50.

Every row links to its full profile. The report, with its method and limits: [satohub.ai/check/report/2026-09](https://satohub.ai/check/report/2026-09).

Where it runs

  • ▸[/check](https://satohub.ai/check) — paste a package, an MCP server, a skill, a repo, an x402 endpoint or a whole install command, and read the four answers.
  • ▸MCP — onchain_agent_check_install gives the agent doing the installing the same answers, before it runs the command.
  • ▸Claude Code — the Sato Hub plugin's install hook shows the four answers whenever an install command is about to run. It sends only the install part of the command: not the rest of a chained command, not text inside quotes, and nothing else from your session.
  • ▸CI — [Sato Preflight](https://github.com/marketplace/actions/sato-preflight) comments the answers on every pull request that adds or bumps a dependency. Commit a .sato/policy.json and the build fails on the findings your team decides matter, for example a key observed leaving or a key read the project never declares. An "unknown" never fails a build.
  • ▸x402 payments — before an agent pays an endpoint, Preflight reads its stated terms and names any onchain agent identity that declares the endpoint or its payee. A declared identity is labelled as declared, not verified. withSatoCheck in satohub-core runs the same check from your agent's own code, and keeps a local record so a lost response never becomes a second payment.
  • ▸Preflight and listing pages — the compact summary next to everything in the Sato Hub library.

Every time it changes

A profile is tied to the exact version it read. When a new version changes key, host or fund behaviour, question four says what changed, and a Preflight watch emails you. Webhook subscribers can opt in to a custody lane that delivers the same change as an event.

Maintainers get the last word on their own page. The verified owner of a listing's domain can publish a response next to the reading, shown as their words and not ours, and can withdraw it.

We run it on ourselves

Sato Check runs inside our own automation before it runs in yours. Every install command in our repositories, typed by a person or run by an agent, goes through the same hook you can install. Our pull requests go through the same Action and a policy file. The starter repos Sato Bot generates carry each pick's reading, and any pick whose key was observed leaving is held back. Sato OS checks an x402 payee before it pays.

Running it on ourselves paid off within minutes. The hook fired on an install command sitting inside a quoted search pattern, and on a compiler flag it mistook for a package fetch. Both would have interrupted every developer using the plugin. Both were fixed and regression-tested before this post.

Try it

  • ▸Paste anything at [satohub.ai/check](https://satohub.ai/check).
  • ▸In Claude Code: /plugin marketplace add satohubai/sato-plugins, then /plugin install sato-hub@sato-plugins.
  • ▸In CI: add uses: satohubai/preflight-action@v1 to a workflow that runs on pull requests.

Check before you install. Then check again when it changes.

Sources

Join the Sato Hub Briefing

One email a week — the agents, tools, and infrastructure that actually shipped, and why they matter.