Skip to content

Security: mashcthomson/presign

Security

SECURITY.md

Security policy

Presign is a security tool, so the honest description of what it cannot do matters as much as the description of what it can.

What this code is allowed to do

Presign is read-only by construction. It never signs, sends or confirms a transaction, and it never handles a private key. The only transaction-touching RPC call anywhere in src/ is simulateTransaction, called with sigVerify:false.

That is not a promise in a document. test/safety.test.js enforces it on every npm test and every CI push:

  • No source file under src/, bin/, test/ or examples/ may contain a broadcast or key-handling call. The banned list covers sending, confirming, airdropping, partial signing, signing, and constructing a keypair from a secret key, plus any bare .sign( call.
  • src/ may call exactly one RPC method. The assertion is set equality against ['simulateTransaction'], so reaching for a second method fails the build rather than passing quietly.
  • The default simulation config must carry sigVerify:false and innerInstructions:true, and simulateAndExtract must reject sigVerify:true when a caller passes it.

The audit builds its own banned strings from fragments at runtime, so the file does not contain the literals it forbids and therefore audits itself. There is no self-exclusion hole.

If you find a way to make this code broadcast a transaction or touch a key, that is the highest severity bug in the project. Report it.

Threat model

Presign sits between an agent runtime and its signer. It assumes:

  • The operator runs it locally, in their own process. There is no hosted component and no third party receives the unsigned transaction.
  • The attacker controls the contents of the transaction handed to Presign, typically through prompt injection reaching the agent, and wants the agent to sign something other than what was approved.
  • The intent is trusted. It comes from the human or the supervising process, not from the same channel the attacker controls. An attacker who can rewrite the intent has already won, and Presign does not defend that boundary.
  • The RPC endpoint is semi-trusted. A malicious endpoint can return a simulation result that does not reflect reality, and Presign would evaluate that fabricated result. Point it at an endpoint you are willing to trust.

Known gaps

These are design limits, not bugs. Reporting them as vulnerabilities is fine but the answer will be this section. The full version is in the Limitations part of the README.

  • Simulate-benign, execute-malicious. Simulation runs against state at simulation time. A program that reads a clock, a slot or its own storage can behave one way under simulation and another way at execution. No simulation-based check defeats this. The planned answer is on-chain assertions, which are not built.
  • No cross-transaction memory. A delegate approved in one transaction and drained in another, or a salami-slicing series of individually in-budget transfers, will not be caught by per-transaction balance arithmetic.
  • Token-2022 is flagged, not modelled. Transfer hooks, transfer fees, permanent delegates and confidential transfers are not decoded.
  • CPI-level tag detection depends on the RPC. When the endpoint returns innerInstructions in jsonParsed form there is no raw instruction data, so inner-instruction detection matches on validator-chosen type-name strings. Top-level detection always uses raw tags.
  • Rent classification is a heuristic, based on credit size and prior non-existence, not a rent-exemption calculation.
  • Recorded simulations are not live checks. --sim replays a point-in-time record and exists for reproducible examples and for CI. Never gate a real signing decision on one.

What counts as a vulnerability

A report is most useful when it is one of these:

  1. Anything that makes Presign sign, send, confirm or broadcast, or that gives it access to a key.
  2. A bypass: a transaction that produces a genuinely harmful effect visible in its simulated result, which Presign returns ALLOW for. A working transaction and intent pair is worth far more than a description.
  3. A false positive: an ordinary, honest transaction that Presign blocks. A firewall that blocks real work gets turned off, so this is treated as a real defect rather than a nuisance.
  4. Crashes or unhandled input in the CLI or the parsers, since malformed input reaching a security check should fail closed rather than fail confusingly.

Reports that restate a gap listed above, without a new mechanism, are not vulnerabilities.

Reporting

Open a GitHub issue. If you believe a report should not be public before a fix, open an issue saying only that you have a security report and asking for a private channel, and do not include details in it.

Please include the unsigned transaction as base64, the intent JSON, the RPC endpoint or a recorded simulation, and the verdict you got against the verdict you expected. A failing test case is the ideal form.

There is no bug bounty. This is a first-milestone project with no funding attached to it.

Status

This code has not been audited. It is not a production security control, and no version of it has been reviewed by anyone but its author. Do not put it in the path of funds you cannot afford to lose.

Supported versions

Development is on main and only main receives fixes. There are no released versions and no backports.

There aren't any published security advisories