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.
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 misconfiguration —
Policy.allowedContracts: []combined withrequireSimulation: falseis a very permissive policy. RunvalidateConfig(policy)andstressTest(policy)before every deploy. - MEV / sandwich attacks (default mode) — standard
eth_sendRawTransactionexposes the transaction to the public mempool. SetPolicy.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.
- Never hardcode private keys in source code or policy files
- Use environment variables for local development only
- Use HSMs or KMS in production — wrap your key store in a
Signerimplementation ({ address, sign(tx): Promise<Hex> }); the EVM package’sprivateKeySigneris reference-only, not for production - Rotate keys if compromised — past audit and provenance entries remain verifiable by their internal hashes
- Principle of least privilege — fund the agent’s wallet with only what it needs; keep treasury funds in a separate wallet and use
humanApprovalThresholdto 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.