keepcalculator

06 Numbers and Powers

Scope Tenant Access in a Rust WebAssembly Host

Card No. 06·57485 · Updated · Standard formula, shown below

Scoping tenant access in a Rust WebAssembly host means ensuring each guest module can reach only the data and operations authorized for its tenant. The reliable approach is to keep identity and authorization in the trusted host, then give each module narrowly scoped capabilities rather than direct access to shared resources. You will need a host-side tenant identity, an authorization source, and a clear boundary between host services and WebAssembly code.

What You’ll Need

  • A Rust WebAssembly runtime and a host application that controls module instantiation and host functions.
  • A trusted way to authenticate requests and determine the tenant ID. Do not accept an unverified tenant ID supplied by guest code.
  • A policy or data-access layer that can check whether a tenant may perform an operation or access a resource.
  • Tests covering allowed access, denied access, and attempts to cross tenant boundaries.

Step-by-Step

  1. Choose the trust boundary. Treat the guest module as untrusted, even if you compile or distribute it yourself. A WebAssembly sandbox can restrict memory and calls, but it does not automatically understand your tenant model. Keep authentication, policy decisions, and access to databases or files in the host.
  2. Resolve the tenant before running guest code. Authenticate the incoming request using your normal trusted mechanism, then derive the tenant identity from the authenticated principal or server-side session. Check that the principal is permitted to act for that tenant. Never let a guest function select an arbitrary tenant by passing a string that the host blindly trusts.
  3. Represent the tenant context explicitly. Create a host-side context containing the verified tenant ID and any relevant authorization information. For example, use a Rust struct with private fields and expose only the values needed by host services. Avoid a mutable global “current tenant”: concurrent requests, async tasks, and reused instances can otherwise mix contexts.
  4. Make tenant context request-scoped. The simplest safe design is to construct a request-specific host state or store when creating an instance, and pass that state to every host function. If you reuse a WebAssembly instance, ensure its host state is reset and verified for each request; prefer separate instances or stores when the runtime’s lifecycle makes isolation hard to guarantee.
  5. Expose narrow host functions. Instead of giving the guest a database connection, filesystem access, or broad service object, provide small operations such as “read this tenant’s record” or “submit this tenant’s job.” Each function should obtain the tenant from trusted host state, not from an untrusted argument. If an operation legitimately targets another tenant, require an explicit, separately authorized capability.
  6. Enforce authorization at the resource boundary. Apply tenant filters in the host’s data-access layer for every read, write, list, and background operation. For example, fetch a record by both its resource ID and tenant ID, rather than fetching by resource ID and checking ownership later. Ensure updates and deletes use the same restriction.
  7. Pass capabilities, not ambient authority. Where your runtime supports it, give a module only the imports or handles it needs. A handle can refer to a particular tenant-scoped service or resource, with the host validating every use. Do not expose unrestricted filesystem paths, network access, or general-purpose database queries when a small operation is sufficient.
  8. Keep guest-controlled identifiers untrusted. Validate lengths and formats for identifiers, then resolve them through tenant-scoped host APIs. Validation prevents malformed input; it does not authorize access. A well-formed ID belonging to another tenant must still be rejected.
  9. Handle async work and callbacks carefully. Carry the verified tenant context explicitly into queued tasks and callbacks. Do not rely on thread-local or task-local state unless its propagation and cleanup are guaranteed by your framework. When a task is resumed or retried, re-check its authority and tenant scope.
  10. Test isolation and review failures. Use at least two tenants in tests. Verify that a guest for tenant A cannot read, modify, enumerate, or trigger jobs for tenant B, including by changing IDs or reusing an instance. Make denied operations fail closed, and avoid returning details that reveal another tenant’s data.

Common Mistakes

Trusting a tenant ID from the module: a guest-supplied tenant field is a request, not proof of authority. Bind access to host-verified identity and policy.

Filtering only in application code: a missed query, export, cache lookup, or background task can expose cross-tenant data. Put scope checks in shared data-access operations and review every path.

Assuming sandboxing equals authorization: WebAssembly isolation limits what code can access directly, but imported host functions may still grant powerful access. Audit imports and their behavior.

Reusing state carelessly: a pooled instance or store may retain tenant-specific state, handles, or cached results. Either isolate instances or make lifecycle, cleanup, and scope checks explicit and tested.

Using resource IDs as secrets: hard-to-guess identifiers are not a substitute for tenant authorization. Always check ownership at the host boundary.

Tips

  • Keep the tenant context type private to the host layer so guest-facing code cannot construct or alter it.
  • Centralize tenant-scoped reads and writes; avoid scattered checks that can be forgotten.
  • Log authorization failures with a correlation ID and operation name, but do not log credentials or sensitive record contents.
  • Review caches, object storage, search indexes, and scheduled jobs as well as the primary database. Their keys and lookups must preserve tenant scope.
  • Document which host imports are available to each module and why each is necessary.

Troubleshooting

A valid tenant receives “not found” or “denied”: trace the verified tenant ID from authentication through host state into the data query. Check whether the request is authorized for that tenant and whether a cache or worker has stale context. Do not fix this by trusting a tenant ID supplied by the guest.

One tenant sees another tenant’s results intermittently: investigate pooled instances, shared mutable state, caches, and async tasks. Add a test that alternates requests from two tenants through the same execution path, then isolate or reset state and ensure every lookup is tenant-scoped.

The guest can call an operation it should not have: inspect the module’s imported host functions and any handles it receives. Remove unnecessary imports, restrict capability creation, and enforce authorization inside the host function itself.

It is difficult to identify a leak: add structured audit events for tenant, operation, resource class, and outcome, using safe identifiers and appropriate redaction. Reproduce with synthetic tenants and confirm that error messages do not disclose whether another tenant’s resource exists.

Frequently Asked Questions

Should the tenant ID be passed into every guest function?
Usually not as an authority signal. Keep the verified tenant in host-side request state. A guest may pass a resource identifier, but the host must apply the trusted tenant scope when resolving it.

Can multiple tenants share one WebAssembly instance?
Potentially, but only if the host reliably separates request state and prevents retained memory, handles, caches, or callbacks from crossing scopes. Separate instances or stores are simpler when that guarantee is uncertain.

Does WebAssembly sandboxing prevent cross-tenant data access?
No. Sandboxing limits direct access, but host imports can grant access to shared services. Tenant checks must be implemented in those host services.

Where should authorization checks live?
At the host service or data-access boundary that performs the operation. Check every access path, including writes, lists, exports, background jobs, and cache lookups.

Should a denied request return “not found” or “forbidden”?
Choose a consistent policy that fits your application’s information-disclosure requirements. Regardless of the response, deny the operation and avoid revealing another tenant’s data or resource details.

What is the most important isolation test?
Run requests for at least two tenants and attempt reads, writes, enumeration, and asynchronous work using the other tenant’s identifiers. Confirm all cross-tenant operations are denied.

Related

Method: the standard formula for this calculation, rounded as each result states. See how the calculators work or report an error.

More cards in this drawer

The whole Numbers and Powers drawer →