Report privately through GitHub Security Advisories. Do not open a public issue.
tula is maintained by one person, so there is no on-call rota to promise you. Expect a reply as soon as it is read, usually within a week. If a week passes with nothing, assume it was missed rather than ignored, and say so on the same advisory.
Please allow 90 days before public disclosure, or less if the issue is being exploited — and less still if I have gone quiet.
tula is non-custodial, and read-only for the moment — placing trades will come later, and moving funds will not. So the interesting failures are about credentials and about lying to the user, not about stolen funds.
Highest severity first:
-
Any path by which a credential leaves the machine, other than to the venue it belongs to — logs, crash reports, telemetry, error messages, or a request to the model provider. The agent layer cannot import the secret store or a connector, and
scripts/guard.shfails the build if that ever changes. -
Any path that could move funds off a venue. There should be none, ever. Any path that places an order: none today either — placing trades will come later, and a build that does it before then is compromised.
-
A key that can withdraw being accepted by
connect. A key that can trade is refused today too, wherever the venue will say so. -
Credentials written outside
~/.config/tulaor at a mode wider than 600, or read through a link or out of a directory anyone can write to. The file is deliberately not encrypted — a key beside the ciphertext protects nothing and a passphrase breaks unattended commands — so its permissions are the whole defence, and a hole in them is a real finding. -
Prompt injection through venue-supplied text that changes what tula reports or where it sends data. The surface is small and worth knowing exactly, because every one of them is drawn on screen and returned to the model as a tool result:
- An asset symbol — as each venue's own listing spells it, or as an Aave
reserve contract returns it over whichever Ethereum RPC is configured.
Every one is capped at 32 characters and stripped of control characters as
it enters, in
src/cli/session.ts;src/connectors/evm.tscaps its own decode as well, so a lying ABI length prefix is never allocated. - The text of a venue's error, when one fails. Capped at 200 characters and flattened to a single line, in the same file, so it cannot pose as a second message.
No memo, NFT metadata or protocol description is read at all.
- An asset symbol — as each venue's own listing spells it, or as an Aave
reserve contract returns it over whichever Ethereum RPC is configured.
Every one is capped at 32 characters and stripped of control characters as
it enters, in
-
A wrong risk number presented as correct: a liquidation distance, health factor or net exposure that is silently incorrect, or a stale figure rendered as live. This is a security issue here, not just a bug — people size positions on these numbers.
Dependencies are pinned to exact versions and bun.lock is committed, so a
published version cannot change under a build. CI installs with
--frozen-lockfile. The runtime dependency list is deliberately near-empty and
all I/O uses Node built-ins; a dependency here runs in a process that reads
exchange API keys.
- Vulnerabilities in a venue's own API.
- A user who pastes a key with trade or withdraw permissions into a different tool.
- Missing rate limiting against a venue you authenticate to yourself.
If you observe any of these, treat the binary as compromised and report it:
- Ask for a seed phrase or an exchange password. (Coinbase's CDP API key is a private key, and the only one tula ever loads; it signs read requests and cannot move funds.)
- Send a credential anywhere except the venue it belongs to.
- Move funds off a venue.
- Place, modify or cancel an order — until trading ships, which will be announced, opt-in, and confirmed by you per trade. A build that does it silently is compromised.
- Send your positions anywhere you did not ask it to.
Everything tula contacts, and nothing else:
- The venues you connect, and the price source you chose.
- A public Ethereum RPC and a token list, for the on-chain venues. These are
defaults rather than choices —
https://ethereum-rpc.publicnode.comandhttps://tokens.uniswap.org— and each sees the address you are reading.TULA_ETH_RPCandTULA_TOKEN_LISTpoint them elsewhere, including at your own node. - Anthropic, and only when you ask a question in plain English. Answering one means sending the computed figures — assets, quantities, notional values, liquidation distances, venue names — as tool results. Credentials are never among them, by construction. Every command works without a model and sends nothing to Anthropic; if you never ask a question, tula never talks to it.
No telemetry, no crash reporting, no update check. The installer contacts GitHub; the binary never does.
Every release archive carries a GitHub artifact attestation — sigstore-backed and keyless, so there is no signing key for this project to generate, publish, rotate or lose.
gh attestation verify tula-v0.1.0-darwin-arm64.tar.gz --repo hsnice16/tulaThe subject is the archive, not the binary inside it, so verify the .tar.gz
rather than an installed tula. Homebrew downloads that same archive; npm
repackages the binary into its own tarball, which carries no attestation of its
own — use the install script or Homebrew where provenance has to be proven.
The installer runs this automatically when the GitHub CLI is present and signed
in, and refuses to install on failure. Without either, it still verifies the
published checksum, says plainly that provenance was not proven, and prints
the download and the verify command — a checksum served beside the artifact only
proves the file is intact, since whoever could replace one could replace both.
TULA_REQUIRE_ATTESTATION=1 turns the missing check into a refusal.
tula is built by github.com/hsnice16/tula and published to its install page,
Homebrew and npm — one binary, built once. Anything you cannot verify against
that repository did not come from this project.