Skip to Content
Security Model

Security Model

Trust Boundaries

txfence operates within a specific trust model. Understanding what it does and does not protect against is essential for production deployments.

Trust boundaries — trusted zone contains policy definitions, SDK code, signer, and chain adapter config. Untrusted zone contains agent outputs, external data, user inputs, and on-chain state.

The trusted zone contains the Policy object, the Signer you pass to createAgent, the chain adapters, the pluggable providers (CapLockProvider, ApprovalProvider, ReceiptStore, AuditLog, ProvenanceChain), and the executor callback. Everything that arrives from the agent’s reasoning layer is untrusted input — runPipeline treats it as data, never as logic.

What txfence Guarantees

Policy enforcement is synchronous and blocking. An action cannot reach the chain unless every stage of runPipeline clears. There is no async gap between evaluation and execution — the same call that returns { status: 'success' } is the call that broadcast the transaction. The Signer lives inside AgentConfig, the executor runs inside the pipeline, and there is no observable state between “policy passed” and “transaction sent.”

Cancel-on-timeout is the hard default. A humanApprovalTimeoutMs that expires returns { status: 'approval_timeout' } — it never falls through to execute. This is the single most common failure mode in approval systems, and txfence makes the safe behavior unconditional.

Cap locks are two-phase. CapLockProvider.acquire reserves budget atomically. commit records the spend after broadcast succeeds. release returns the reservation on any failure. The Redis implementation uses atomic Lua scripts, so two agents racing to spend the last 100 USDC of a shared cap cannot both succeed.

Audit and provenance are append-only. createFileAuditLog writes NDJSON one line at a time and never rewrites. createFileProvenanceChain extends this with SHA-256 hash chaining — modifying any historical entry invalidates every entry after it, and Merkle proofs let you prove a single record exists without disclosing the rest. See Provenance Chains.

Simulation uses a forked chain snapshot. eth_call and Tenderly fork simulations both run against the latest confirmed block header — pending mempool state cannot influence the result. The state_may_diverge caveat is always present so callers know that execution will run against a newer block.

What txfence Does Not Guarantee

txfence is not a substitute for smart contract audits. It cannot prevent on-chain vulnerabilities in the contracts your agent interacts with.

txfence does not protect against:

  • Compromised signing keys — if an attacker exfiltrates the Signer’s private key, they can broadcast transactions directly to any RPC, bypassing the pipeline. Use HSM/KMS-backed signers in production.
  • Malicious chain adapters — if you supply a tampered adapter, adapter.simulate() can return arbitrary results. Adapter code is in the trusted zone; review it like signing code.
  • Policy misconfigurationPolicy.allowedContracts: [] combined with requireSimulation: false is a very permissive policy. Run validateConfig(policy) and stressTest(policy) before every deploy.
  • MEV / sandwich attacks (default mode) — standard eth_sendRawTransaction exposes the transaction to the public mempool. Set Policy.mevProtection: 'flashbots' or 'mev-blocker' to route through protected endpoints — see MEV Protection.
  • Smart contract exploits — vulnerabilities in the contracts being called are outside txfence’s scope; simulation may flag a revert but cannot detect a back-door that mints to the attacker.

Threat Model

Prompt Injection

An attacker embeds instructions in data the agent processes (a token name, a contract’s name() return value, a DeFi protocol’s description) that cause the agent to propose malicious actions.

Mitigation: Policy checks are invariants regardless of why the agent proposed the action. Even if the agent is convinced to attempt a large transfer, maxSpendPerTx, allowedContracts, and humanApprovalThreshold enforce bounds independently of agent reasoning.

Policy Bypass via Novel Action Encoding

An attacker finds an encoding of a malicious action that the chain adapter mis-decodes, so it appears benign during simulation.

Mitigation: Three actions kinds exist — transfer, swap, contract_call. Each has explicit typed fields and an optional calldata override. The adapter is the only trust boundary on encoding; review adapter code carefully. For EVM, use createEvmMetadataVerifier and pin ContractEntry.bytecodeHash so a contract that changes implementation is rejected with bytecode_hash_mismatch.

Simulation Divergence

Simulation returns success at block N, but state changes between block N and the inclusion block produce a different on-chain outcome.

Mitigation: Set Policy.simulationStalenessMs (e.g., 30,000 ms) — the pipeline returns simulation_stale if too much wall-clock time elapses between simulating and signing, forcing a re-simulate. Use Fork Simulation for multi-step intents that need state to carry between steps.

Audit Log Tampering

An attacker with write access to the audit log file attempts to delete or modify historical entries.

Mitigation: Use @txfence/provenance instead of plain @txfence/audit for high-assurance deployments. Each ProvenanceRecord includes the SHA-256 hash of the previous record, so tampering with entry N invalidates entries N+1..end. chain.verify() walks the chain and reports hash_mismatch / chain_broken / invalid_hash violations. For external durability, mirror the chain to a write-once S3 bucket with object lock.

Behavioral Manipulation

An attacker discovers that the agent’s reasoning loop can be nudged into a pattern of small individually-valid actions that, taken together, drain a cap or trigger an undesired state.

Mitigation: Temporal rules detect sliding-window patterns — spend_velocity, consecutive_failures, contract_call_frequency, approval_flood, success_drought, simulation_failure_rate. Each maps to a require_approval, reject, or flag_for_review consequence without requiring the agent to recognize the pattern itself.

Key Management

createAgent accepts a Signer in AgentConfig. The signer is the most sensitive object in the system — treat it like a smart-contract owner key.

  1. Never hardcode private keys in source code or policy files
  2. Use environment variables for local development only
  3. Use HSMs or KMS in production — wrap your key store in a Signer implementation ({ address, sign(tx): Promise<Hex> }); the EVM package’s privateKeySigner is reference-only, not for production
  4. Rotate keys if compromised — past audit and provenance entries remain verifiable by their internal hashes
  5. Principle of least privilege — fund the agent’s wallet with only what it needs; keep treasury funds in a separate wallet and use humanApprovalThreshold to gate top-ups
import type { Signer } from "@txfence/core"; const kmsSigner: Signer = { address: process.env.AGENT_ADDRESS as `0x${string}`, sign: async (tx) => kmsClient.sign(tx), }; const agent = createAgent( { chains: ["ethereum"], policies: policy, signer: kmsSigner }, adapters, rpcUrls, executor, );

Reporting Vulnerabilities

If you discover a security vulnerability in txfence, please report it via GitHub Security Advisories rather than opening a public issue. We aim to respond within 48 hours and release a patch within 7 days for critical issues.

Last updated on