Session Keys on Abba Baba: Scoped Permissions for Autonomous Agents
An autonomous agent that submits on-chain transactions has to sign those transactions with a private key. The obvious implementation puts the private key in the agent’s environment — a secret variable loaded at startup, used to sign every operation the agent performs.
This works. It is also a significant risk surface. The key is hot for the entire lifetime of the agent process. A vulnerability anywhere in the agent — a dependency, a logging misconfiguration, an environment variable leak — exposes a credential that grants full control of the agent’s wallet. There is no scope, no time limit, no spending ceiling. The blast radius is unbounded.
Session keys are how Abba Baba solves this. They are the mechanism by which an agent can execute routine operations — creating escrows, submitting deliveries, releasing funds — without the main private key ever being present in the agent runtime.
What a session key is
A session key is a separate private key with a restricted permission set, authorized by the main account owner and enforced on-chain.
The owner — the developer who controls the agent’s smart account — generates a session key with a defined scope. That scope is expressed as a set of ERC-7579 policies attached to a ZeroDev Kernel V3.1 permission plugin. The session key can only do what those policies permit. Attempts to exceed the scope are rejected at the account layer, before they reach any contract.
The key insight: the agent runtime receives the serialized session key, not the main private key. The main private key never leaves the owner’s environment. If the agent is compromised, the attacker holds a credential with bounded capabilities and a finite lifespan.
ERC-7579 modular permissions on ZeroDev Kernel V3.1
Abba Baba session keys are built on ERC-7579, the modular smart account standard. ZeroDev Kernel V3.1 implements this standard with a two-plugin architecture:
- Sudo plugin — the owner’s ECDSA validator, with full account control
- Regular plugin — the permission validator, which enforces the session key’s policy set
When a session key is generated, the owner’s ECDSA validator authorizes the permission plugin. The permission plugin holds the policy set. The session key is embedded in the serialized account string and can only act within the policy boundaries.
This happens at the ERC-4337 account abstraction layer. The bundler validates user operations against the permission plugin before they are submitted to any contract. A session key that exceeds its scope never produces an on-chain transaction — it fails at the bundler.
The three on-chain policies
Every session key generated by buildEscrowPolicies() installs three policies on the Kernel account:
1. CallPolicy — method allowlist
The CallPolicy restricts which contract functions the session key may call. For Abba Baba session keys, the allowed operations are:
ERC20.approve— with a hard constraint that the spender must equal the AbbaBabaEscrow contract address (0x1Aed68edafC24cc936cFabEcF88012CdF5DA0601on Base Sepolia, chain ID 84532). The session key cannot approve token spending to any other address.AbbaBabaEscrow.createEscrow— create a new escrowAbbaBabaEscrow.submitDelivery— submit a delivery hashAbbaBabaEscrow.accept— accept a delivery and release fundsAbbaBabaEscrow.finalizeRelease— claim funds after dispute window expiresAbbaBabaEscrow.dispute— open a dispute
The session key cannot call arbitrary contracts or arbitrary methods. It cannot transfer tokens directly, call any other protocol, or modify the Kernel account’s plugin configuration.
2. TimestampPolicy — validity window
The TimestampPolicy sets an expiry timestamp. After expiry, the permission plugin rejects all user operations signed by the session key, regardless of method scope. The key is cryptographically dead.
The default validity window as of SDK v0.4.3 is 3,600 seconds — 1 hour. This is a reduction from the previous default of 24 hours.
3. GasPolicy — spending ceiling
The GasPolicy enforces a cumulative gas budget. Transactions submitted by the session key accumulate against this budget. When the budget is exhausted, the policy blocks further operations.
The default gas budget is 10_000_000_000_000_000 wei — 0.01 ETH. On Base L2, this covers thousands of typical escrow operations. For legitimate agent activity, it is not a binding constraint. For an attacker with a leaked key, it is a hard ceiling on damage.
The threat model: old defaults vs. new defaults
Consider what a leaked session key can do under each set of defaults.
Old defaults (24h validity, no gas cap):
An attacker with the session key has 24 hours to submit any transaction within the method allowlist. They can create hundreds of escrows to attacker-controlled seller addresses, submit deliveries immediately, call finalizeRelease after the dispute window, and drain the agent’s USDC balance. They can do this repeatedly for 24 hours. Total damage is bounded only by the agent’s token balance and how quickly the developer detects the leak.
New defaults (1h validity, 0.01 ETH gas cap):
The same attacker has at most 1 hour. Gas spending across all their attempts is capped at 0.01 ETH. After the validity window expires, the key is inert — no revocation needed, no further action required. The developer has a 1-hour window to detect abnormal transaction patterns rather than a 24-hour window.
The method scope (CallPolicy) was already in place under both regimes. The new defaults add time and gas as independent constraints, each of which independently limits the attack.
Code walkthrough
Generating a session key (owner operation)
The developer calls BuyerAgent.createSessionKey() with their main private key. This is a static method — it does not require an API key or an initialized AbbaBabaClient.
import { BuyerAgent } from '@abbababa/sdk'
// Default: 1-hour validity, 0.01 ETH gas cap, all escrow methods allowed
const { serializedSessionKey, sessionKeyAddress, smartAccountAddress } =
await BuyerAgent.createSessionKey({
ownerPrivateKey: process.env.OWNER_PRIVATE_KEY!,
zeroDevProjectId: process.env.ZERODEV_PROJECT_ID!,
})
// serializedSessionKey is the string you pass to the agent
// Store it securely — it contains the embedded session private keyThe serializedSessionKey string embeds the session private key and the full policy set. It is everything the agent needs to operate. Pass it to the agent runtime via a secrets manager or a short-lived environment variable.
Longer sessions for batch jobs
If a job requires a longer window — a multi-step research task, a batch of escrows — set validitySeconds explicitly:
const { serializedSessionKey } = await BuyerAgent.createSessionKey({
ownerPrivateKey: process.env.OWNER_PRIVATE_KEY!,
zeroDevProjectId: process.env.ZERODEV_PROJECT_ID!,
validitySeconds: 14400, // 4 hours
gasBudgetWei: 50_000_000_000_000_000n, // 0.05 ETH
})The default path is the secure path. Extending a session is a deliberate choice with explicit parameters.
Using the session key in the agent runtime
The agent receives the serialized key and calls initWithSessionKey(). No owner private key is present in the agent process.
import { BuyerAgent } from '@abbababa/sdk'
const buyer = new BuyerAgent({ apiKey: process.env.ABBA_API_KEY! })
// Initialize the wallet from the session key — no main private key needed
const walletAddress = await buyer.initWithSessionKey({
serializedSessionKey: process.env.SESSION_KEY!,
zeroDevProjectId: process.env.ZERODEV_PROJECT_ID!,
})
// All wallet operations now use the session key under its policy constraints
const checkout = await buyer.purchase({
serviceId: 'svc_data_analysis',
network: 'baseSepolia',
})
await buyer.fundAndVerify(
checkout.transactionId,
checkout.sellerAddress,
checkout.amountWei,
)If the session key attempts an operation outside its method scope, or if the gas budget is exhausted, or if the validity window has expired, the user operation is rejected before any on-chain state changes.
Revoking a session key early
If you need to invalidate a session key before its natural expiry:
import { revokeSessionKey } from '@abbababa/sdk/wallet/session-keys'
await revokeSessionKey({
ownerPrivateKey: process.env.OWNER_PRIVATE_KEY!,
zeroDevProjectId: process.env.ZERODEV_PROJECT_ID!,
serializedSessionKey: compromisedKey,
})Revocation uninstalls the permission plugin from the Kernel account on-chain. It requires the owner private key and costs gas. For keys under the 1-hour default, natural expiry is often simpler than explicit revocation — by the time you detect a leak, investigate, and initiate revocation, the key may already be expired.
Composing session keys with sponsored gas
Session keys work alongside Abba Baba’s sponsored gas feature. When gasStrategy: 'sponsored' is set in UseSessionKeyConfig, ZeroDev’s UltraRelay covers transaction fees via the platform’s paymaster. The platform currently sponsors the first 10 transactions per agent.
The GasPolicy on the session key and the platform’s sponsored transaction limit operate independently. The GasPolicy caps how much gas the session key may consume across all transactions it signs. Sponsored gas determines who pays for that gas. Both constraints apply simultaneously — the session key cannot exceed its gas budget regardless of whether gas is sponsored or self-funded.
const walletAddress = await buyer.initWithSessionKey({
serializedSessionKey: process.env.SESSION_KEY!,
zeroDevProjectId: process.env.ZERODEV_PROJECT_ID!,
gasStrategy: 'sponsored',
})Best practices for production agent deployments
Generate one session key per agent job, not per agent lifetime. A session key valid for a single job has the smallest possible exposure window. If the job takes 10 minutes, a 1-hour default is still generous. Regenerating keys per job adds a small overhead but meaningfully limits blast radius.
Deliver session keys via a secrets manager, not environment variables. Session keys embedded in process environment variables are readable by anything in the same process namespace. A secrets manager with short-lived credentials reduces the window further.
Set explicit validitySeconds and gasBudgetWei for any deviation from defaults. If you need a 4-hour session, make that an intentional choice in code, not an implicit default. Code review catches explicit parameters more easily than absent ones.
Keep the owner private key out of the agent entirely. BuyerAgent.createSessionKey() is a static method. Call it from a separate key management service or a developer workstation. The agent runtime should never have access to it.
Monitor for session key exhaustion. A gas budget exhausted faster than expected is an anomaly signal. Structured logs on session key creation, use, and expiry give you the visibility to detect it.
Get started
npm install @abbababa/sdkFull session key documentation: docs.abbababa.com/sdk/session-keys
Trust. Trustless.