Skip to content

Storage Path Scoping: Multi-Tenant Data Isolation for AI Agents

Baponi is a sandboxed code execution platform for AI agents. When you connect cloud storage (S3, GCS, or Azure) to Baponi, your AI agents read and write files through local directory mounts - no cloud SDK, no credentials in code. Storage path scoping controls exactly which subdirectories each agent can access, enforced server-side with zero trust in the client.

AI agents operating on shared data need boundaries. Without path scoping, any agent with access to a bucket can read and write anything in it. This creates problems in three common scenarios:

Multi-tenant SaaS. You run AI agents on behalf of multiple customers. Each customer’s data lives in the same bucket under a per-tenant prefix (tenants/acme/, tenants/widgets/). An agent processing Acme’s data must never see Widgets’ data, even accidentally.

Partner data sharing. A partner grants you access to their bucket for a joint project, and you agree to only use a specific folder (shared/your-company/). You need a way to enforce that agreement at the platform level so no API key or agent can accidentally wander outside that folder.

Least-privilege agents. You have one bucket with production data, staging data, and backups. Different agents should see different slices. The data analysis agent gets analytics/, the backup agent gets backups/, and neither can touch the other’s data.

Baponi solves all three with a single mechanism: three levels of path constraints, each narrowing the scope of the level above it.

Every storage mount in Baponi passes through three constraint checkpoints. Each level can only narrow the path - never widen it. If no constraint is set at a level, it inherits the constraint from the level above.

LevelWhere it’s configuredWho configures itScope
1. Connection prefixAdmin console, on the storage connectionOrganization adminAll API keys using this connection
2. API key prefixAdmin console, on each API keyOrganization adminAll executions using this key
3. Request sub_pathssub_paths field in the request bodyDeveloper / AI agentSingle execution

The principle is simple: the person configuring the connection sets the broadest boundary, the admin narrows it per key, and the developer narrows it per request. No level can escape the constraint set by the level above.

If no sub_paths are passed in a request, the tightest configured constraint becomes the mount scope automatically. If the API key has a prefix, that’s used. If not, the connection prefix is used. If neither is set, the entire bucket is accessible (subject to the connection’s read/write permissions).

When you add a storage connection (S3, GCS, or Azure bucket) in the admin console, you can set an optional sub-path prefix. This is the broadest boundary - it confines every API key and every request using this connection to a specific subtree of the bucket.

When to use it:

  • You’ve agreed with a partner to only access a specific folder in their bucket and want to enforce that at the platform level
  • You have a shared bucket and want to guarantee that no API key can ever access certain directories
  • Compliance requires that a connection’s scope is locked down at the infrastructure level, not left to per-key configuration

Example: A partner gives you access to their GCS bucket partner-analytics and you’ve agreed to only use shared/acme/. You add the connection in the admin console with:

  • Bucket: partner-analytics
  • Connection name: partner-analytics (becomes the mount slug)
  • Sub-path prefix: shared/acme/

Every API key using this connection now mounts at /data/partner-analytics/, but only the shared/acme/ subtree is accessible. An API key cannot be created with a prefix outside this boundary.

When you create an API key in the admin console, you can set a sub-path prefix per storage connection attached to that key. This prefix must start with the connection prefix (if one is set) - it can only go deeper.

When to use it:

  • Per-tenant isolation: issue different keys for different customers, each confined to their tenant folder
  • Per-team isolation: the data science team gets shared/acme/analytics/, the engineering team gets shared/acme/logs/
  • Per-agent least-privilege: the reporting agent gets read-only access to shared/acme/reports/, the ingestion agent gets write access to shared/acme/raw/

Example: Using the partner-analytics connection from Level 1 (connection prefix: shared/acme/), you create two API keys:

API KeyKey prefixEffective mount scope
sk-us-team-alphashared/acme/team-alpha/Only team-alpha/ subtree
sk-us-team-betashared/acme/team-beta/Only team-beta/ subtree

An agent using sk-us-team-alpha sees /data/partner-analytics/ as its mount point, but only files under shared/acme/team-alpha/ are accessible. It cannot access shared/acme/team-beta/ or anything else in the bucket.

Attempting to create an API key with prefix other-company/ would be rejected because it does not start with the connection prefix shared/acme/.

Developers can pass sub_paths in the request body to narrow the mount to a specific subdirectory for a single execution. The path must respect both the connection prefix and the API key prefix.

When to use it:

  • An agent processing a batch job needs access to only one day’s data: shared/acme/team-alpha/2026-03-17/
  • A multi-step workflow where each step operates on a different subdirectory
  • Dynamically scoping access based on user input or workflow context
Terminal window
# Narrow to a specific day's data within team-alpha's scope
curl -X POST https://api.baponi.ai/v1/sandbox/execute \
-H "Authorization: Bearer sk-us-team-alpha" \
-H "Content-Type: application/json" \
-d '{
"code": "import os; print(os.listdir(\"/data/partner-analytics/\"))",
"sub_paths": ["/data/partner-analytics/shared/acme/team-alpha/2026-03-17"]
}'

The sandbox sees /data/partner-analytics/ as its mount point, but only the 2026-03-17 subtree is accessible. The agent cannot list or read files from any other date.

If the request tried /data/partner-analytics/shared/acme/team-beta/2026-03-17, it would be rejected because team-beta/ is outside the API key’s prefix shared/acme/team-alpha/.

Here is the complete constraint chain for a single execution, showing how all three levels combine:

Bucket: partner-analytics
└─ Connection prefix: shared/acme/ (Level 1: set by whoever configures the connection)
└─ API key prefix: shared/acme/team-alpha/ (Level 2: admin's per-key constraint)
└─ Request sub_path: shared/acme/team-alpha/2026-03-17/ (Level 3: per-request)

At execution time, the gateway validates the request sub_path against both the API key prefix and the connection prefix. If either check fails, the request is rejected with 400 validation_error. If validation passes, the executor mounts only the requested subtree. The sandbox process sees /data/partner-analytics/ but can only access files under shared/acme/team-alpha/2026-03-17/.

Baponi’s path scoping is designed so that no client-side code, misconfiguration at a lower level, or creative path construction can escape the boundaries set by a higher level.

PropertyHow it works
Segment-aware matchingPrefix matching respects / boundaries. tenants/a matches tenants/a/subdir but does NOT match tenants/ab. This prevents a key scoped to tenants/a from accessing tenants/abc/.
Path traversal rejection.., ./, URL-encoded sequences (%2e, %2f, %5c), null bytes, and control characters are all rejected at parse time.
Fail-closed validationAny validation failure rejects the request. There is no fallback, no silent correction, no partial mount.
Deepen-never-widenAn API key prefix must start with the connection prefix. A request sub_path must start with the API key prefix. Violations are rejected at key creation time (Level 2) or request time (Level 3).
Server-side enforcementAll validation happens at the gateway before the execution reaches the sandbox. The sandbox process never sees paths outside its scope.

One bucket, one connection, per-tenant API keys:

Connection prefixAPI keyKey prefixMount scope
(none)sk-us-acmetenants/acme/Only Acme’s data
(none)sk-us-widgetstenants/widgets/Only Widgets’ data

Each tenant’s agent uses a different API key. No tenant can access another tenant’s folder.

Connection prefix locks down the outer boundary, API key prefixes scope per-team access:

Connection prefixAPI keyKey prefixMount scope
shared/your-company/sk-us-analyticsshared/your-company/analytics/Analytics data only
shared/your-company/sk-us-exportsshared/your-company/exports/Exports only

The connection-level constraint (shared/your-company/) cannot be removed or widened by any API key. To change it, you’d need to reconfigure the connection itself.

API key gives broad access, individual requests narrow to what’s needed:

from baponi import Baponi
client = Baponi(api_key="sk-us-data-pipeline")
# Process each customer's data in isolation
for customer_id in ["cust-001", "cust-002", "cust-003"]:
result = client.execute(
f"import os; files = os.listdir('/data/lake/'); print(len(files))",
sub_paths=[f"/data/lake/customers/{customer_id}/inbox"],
)
print(f"{customer_id}: {result.stdout.strip()} files")

Each iteration mounts only that customer’s inbox. The agent cannot access other customers’ data even though the API key allows the entire customers/ tree.

ConstraintLimit
Max sub_paths entries per request10
Entry format/data/{slug}/{path} where {slug} matches a mounted storage connection
Entry lengthMax 512 characters
Connection/key prefix lengthMax 512 characters
Path traversal.., ./, URL-encoded sequences, null bytes, and control characters are rejected
Prefix matchingSegment-aware: checked at / boundaries
Duplicate slugsOnly one sub_paths entry per storage connection per request

What if I don’t set any prefix at any level? The full bucket is accessible (subject to the connection’s read/write permissions). Path scoping is opt-in at each level. Many use cases don’t need it - a single-tenant deployment with one agent per bucket works fine without prefixes.

Can I widen a path at a deeper level? No. Each level can only narrow, never widen. An API key prefix must start with the connection prefix. A request sub_path must start with the API key prefix. Attempting to widen returns a validation error.

Does path scoping work with managed volumes? Yes. Managed volumes support the same three-level path scoping as BYOB connections. The mechanism is identical.

Does this work with MCP? sub_paths is deliberately excluded from MCP. MCP tool calls are typically auto-generated by LLMs, and exposing path selection to an LLM creates a prompt injection risk: an end user could manipulate the LLM into requesting paths outside the intended scope, silently breaking multi-tenant isolation. By keeping path selection out of the MCP tool schema, the boundary is always set by an admin, not by an LLM at runtime. Configure the path constraints you need on the API key in the admin console.

What error do I get if a sub_path violates a constraint? 400 validation_error with a message identifying the mount and the prefix that was violated. For example: "sub_path for '/data/partner-analytics/' does not match prefix 'shared/acme/team-alpha/'".

Is there any performance cost to path scoping? No. Path validation is a string prefix check at the gateway. It adds no measurable latency to execution.

Can the sandbox process discover what prefix is enforced? No. The sandbox sees the mount point (e.g., /data/partner-analytics/) and the files within its allowed scope. It has no visibility into the prefix configuration or what other paths exist in the bucket.