Preventing Tenant Leakage in Background Jobs

Preventing Tenant Leakage in Background Jobs

How queue payloads and workers can preserve customer context across asynchronous work.

A web request usually has an authenticated user and an active tenant. A background job runs later, often in another process, without the original request context. If the job forgets which customer it belongs to, it may read or write another tenant's data, use the wrong settings, or send a message with the wrong branding. Tenant isolation must therefore be part of the job contract, not an assumption inherited from the browser session.

Carry the right context A queued task should include the tenant identifier and the item or operation it is meant to process. It may also need locale, actor, and correlation identifiers. Validate that the producer is authorized to schedule the work for that tenant. The worker should load current data using tenant-scoped operations rather than trusting every copied field in the payload. Avoid using a process-global “current tenant” variable; workers handle jobs from many customers over time. A missing tenant should fail clearly instead of falling back to a default customer.

Scope every side effect The worker may read records, create exports, send emails, update search indexes, or invalidate caches. Each action needs the same tenant boundary. Include tenant scope in file paths, cache keys, and search queries. A job that loads a record by ID alone can cross a boundary even if the queue payload contains a tenant field. Test with two tenants whose records have similar names and operations. The security check belongs at the data or business boundary as well as the initial job handler, because retries and alternate producers may reach it later.

Handle retries safely Jobs can run more than once after a crash or timeout. Make side effects idempotent where possible or use a job key that prevents duplicate invoices, notifications, and exports. Record which tenant and operation failed so an operator can retry the correct work. A dead-letter queue or failure table should preserve enough context to investigate without storing sensitive payloads. Set retry limits and backoff appropriate to the dependency. Repeated failure in one tenant should not occupy every worker and delay other customers.

Respect tenant lifecycle changes A job may be scheduled while a tenant is active and run after suspension, deletion, or a settings change. Decide which operations should continue. A billing reminder might need to run during suspension; a marketing export probably should not. Check current tenant state when the job begins and again before irreversible side effects when appropriate. Do not assume the original user's role is still valid hours later. If the operation requires current authorization, re-evaluate it or use a clearly defined service permission with an audit trail.

Test and observe the boundary Integration tests should enqueue jobs for two tenants, run workers with mixed ordering, and verify results remain scoped. Include a retry, a delayed job after a tenant setting change, and an invalid tenant payload. Logs should include tenant and correlation context but not customer secrets. Metrics can show queue age and failures by tenant so a noisy workload is visible. A support tool that retries a job must preserve the original tenant scope and record the operator who requested the retry.

Centralize the safe path A shared queue wrapper can require tenant context, generate correlation IDs, and enforce payload conventions. Business workers remain focused on their domain operation. This follows the platform's root-to-leaf approach: complex safety mechanics live in the core, while leaf jobs are simple. For SMB SaaS, this is a proportionate investment. A few clear rules and tests prevent a class of cross-customer mistakes that would be costly to diagnose and damaging to trust. Review jobs that were written before tenant context became mandatory and migrate them rather than relying on a new wrapper only for future work. The platform is safe when every producer and worker follows the same boundary. Check retries and manual replays too.