AI agents · Vendor isolation · Credential scope · Security architecture

AI agent vendor isolation: scoping credentials per agent, user, and vendor

Vendor isolation for AI agents is the principle that each agent run should have its own scoped credential for each vendor API it calls — not a shared production API key. Without isolation, a runaway agent has access to your entire Stripe account: all customers, all payment methods, no spend limit. With vendor isolation, any single agent failure is bounded to the scope of its credential: a specific set of allowed endpoints, a daily spend cap, an expiration time. This is not about trusting the LLM — it's about building systems that fail safely regardless of what the LLM does.

TL;DR

Vendor isolation has four dimensions: (1) credential separation — one key per agent run, not one key per application; (2) endpoint allowlisting — the key can only call specific API paths; (3) spend caps — a hard ceiling on cost per run; (4) time bounds — the key expires after the run's expected duration. All four must be present for true isolation — a per-run key with no spend cap and no endpoint allowlist is only marginally better than a shared key.

The blast radius problem

When you give an AI agent a production Stripe API key, the blast radius of any failure is your entire Stripe account. Consider what a runaway agent can do with unrestricted access:

This isn't a theoretical threat. LLM reasoning errors, prompt injection in user-supplied tool arguments, stuck loops, and framework bugs have all caused real damage in production agent deployments. The question isn't whether your agent will ever behave unexpectedly — it's what the worst case looks like when it does.

Blast radius as a formula:

blast_radius = accessible_resources × accessible_actions × spend_ceiling

# With shared production key:
blast_radius = all_customers × all_stripe_endpoints × no_ceiling
# = unlimited

# With an isolated vault key:
blast_radius = policy.allowed_merchant_ids × policy.allowed_endpoints × policy.daily_usd_cap
# = bounded, auditable, revocable

The four dimensions of vendor isolation

1. Credential separation (per-run keys)

Each agent run gets its own credential, distinct from all other runs and from the production application key. This enables:

# Without credential separation:
# All agent runs use the same key → can't distinguish their activity
stripe_key = os.environ["STRIPE_SECRET_KEY"]  # Shared by all

# With credential separation:
# Each run issues its own key at start, revokes at end
vault_key = await keybrake.issue(
    label=f"agent-run-{run_id}",
    vendor="stripe",
    ...
)

2. Endpoint allowlisting

An isolated credential should only be able to call the specific API endpoints the agent actually needs for its task — not the full API surface. Map each agent capability to its minimum required endpoints:

Agent capabilityRequired Stripe endpointsNOT required
Create payment /v1/payment_intents Refunds, subscriptions, customers, webhooks
Check charge status /v1/charges/{id} Write endpoints of any kind
Create subscription /v1/subscriptions, /v1/subscription_schedules Payment intents, refunds, webhook management
Send invoice /v1/invoices, /v1/invoices/{id}/send Customers, payment methods, subscriptions

An allowlist that includes /v1/refunds for an agent that only needs to create payment intents is unnecessary blast radius. Define allowlists as narrowly as the agent's actual task requires.

3. Spend caps

A per-run spend cap is the single most important isolation control for financial vendors. It converts the worst-case failure from "unlimited" to "bounded":

# Calculate the spend cap for a given agent run:
#   expected_max_spend = max_legitimate_action × max_legitimate_action_count
#   cap = expected_max_spend × safety_margin (1.5–2×)

# Example: billing agent that creates one invoice per customer per month
# Max invoice = $500, max customers processed per run = 10
expected_max_spend = 500 * 10   # = $5,000
cap = expected_max_spend * 1.5  # = $7,500 (50% buffer for edge cases)

vault_key = await keybrake.issue(
    vendor="stripe",
    daily_usd_cap=cap,
    ...
)

# Now if the agent loops and creates 1,000 invoices, the damage is $7,500 max
# — not your entire Stripe account balance

4. Time bounds (TTL)

Credentials that expire cannot be used after the agent run completes. TTL is the safety net for cases where explicit revocation fails:

Agent patternTTL recommendationRationale
Single HTTP request (synchronous tool call) 5–15 minutes Longer than any single call, shorter than overnight
Multi-step workflow (Temporal / Prefect) SLA + 20% Outlives the expected workflow duration with buffer
Nightly batch agent 6–8 hours Batch should complete within one business night
Long-running autonomous agent 24 hours (with human checkpoint) Daily renewal forces a human-visible audit event

Isolation matrix: per dimension, per vendor

Vendor isolation applies per vendor, not globally. A single agent run may call three vendors, each with different isolation requirements:

async def setup_isolated_credentials(run_id: str, task: AgentTask) -> dict[str, str]:
    """Issue one vault key per vendor, scoped to the task's requirements."""
    credentials = {}

    if task.requires_stripe:
        stripe_key = await keybrake.issue(
            label=f"{run_id}-stripe",
            vendor="stripe",
            daily_usd_cap=task.max_stripe_spend,
            allowed_endpoints=task.required_stripe_endpoints,
            expires_in=task.expected_duration_with_buffer
        )
        credentials["stripe"] = stripe_key["token"]

    if task.requires_twilio:
        twilio_key = await keybrake.issue(
            label=f"{run_id}-twilio",
            vendor="twilio",
            daily_usd_cap=task.max_twilio_spend,  # SMS cost per message
            allowed_endpoints=["/2010-04-01/Accounts/*/Messages"],  # Send only
            expires_in=task.expected_duration_with_buffer
        )
        credentials["twilio"] = twilio_key["token"]

    if task.requires_resend:
        resend_key = await keybrake.issue(
            label=f"{run_id}-resend",
            vendor="resend",
            daily_usd_cap=task.max_email_count * 0.001,  # $0.001/email
            allowed_endpoints=["/emails"],
            expires_in="30m"  # Email tasks are short
        )
        credentials["resend"] = resend_key["token"]

    return credentials

Isolation vs. authentication

Vendor isolation is often confused with authentication. They're different problems:

ConcernAuthenticationVendor isolation
Question answered "Is this caller allowed to use the API at all?" "How much damage can this caller cause?"
Who controls it Vendor (Stripe validates the key) You (via the proxy + credential policy)
Granularity Key-level (all-or-nothing for most vendor APIs) Per-run, per-endpoint, per-dollar
What it stops Unauthorized callers Authorized callers doing more than intended

A production Stripe key authenticates your application — Stripe trusts all requests made with it. Vendor isolation limits what your own agents can do with that trust. Both are necessary; neither replaces the other.

Implementing isolation without a proxy

Partial vendor isolation is possible without a dedicated proxy using vendor-native features:

The gap vendor-native features leave: no per-run spend caps (only plan-level billing limits), no TTL on credentials, and no cross-vendor unified audit log. A proxy layer fills these gaps across all vendors with a consistent credential API.

Get early access

Related questions

Is vendor isolation necessary if my agent only reads from Stripe (no writes)?

For read-only agents, the spend cap dimension of vendor isolation is less critical (reads don't create charges), but credential separation and endpoint allowlisting still matter. An agent with read access to all Stripe customers, all payment methods, and all charge history can exfiltrate PCI-scoped data if it encounters a prompt injection attack or reasoning error. A credential scoped to GET /v1/charges/{id} can only read specific charge records, not enumerate the full customer base. Least-privilege applies to reads as much as writes — especially for data that has regulatory sensitivity under PCI DSS or GDPR.

How does vendor isolation interact with multi-tenant SaaS architectures?

In a multi-tenant SaaS where each tenant has their own Stripe account, vendor isolation has an additional dimension: per-tenant credential separation. Each agent run should use a credential for the specific tenant's Stripe account, not a super-admin credential with access to all tenants. Keybrake's merchant_allowlist policy field handles this: issue one vault key per agent run with merchant_allowlist: [tenant.stripe_account_id] so the credential can only act within that tenant's Stripe account, even if the underlying Keybrake token has access to multiple accounts. See multi-tenant isolation for the full pattern.

What's the minimum viable vendor isolation I can implement today without a proxy?

Without a proxy, the highest-impact minimum viable isolation steps are: (1) use Stripe Restricted API Keys instead of the secret key — restrict to only the endpoints your agent calls; (2) create one restricted key per user or per agent deployment, not one global key; (3) set up a Stripe webhook for high-value events (payment_intent.succeeded, charge.refunded) with a Slack/email alert so you get immediate visibility into unexpected activity. This doesn't give you TTL, spend caps, or a unified audit log, but it dramatically reduces the blast radius of a key compromise or runaway loop compared to sharing a single secret key.

Further reading