Test Cross-Tenant Writes Safely
Verify that create, update, and delete operations reject another tenant's records without side effects.
A denied read does not show that a write is denied. Update handlers often load a record, apply changes, and only then check whether the caller may see it. Test mutations as separate decisions. OWASP API Security Top 10 covers object-level and property-level authorization; each operation needs its own rule.
Use two synthetic tenants in an owned staging system. Give Tenant A one mutable record and Tenant B another. Reset the fixture between cases so a previous test cannot hide a later side effect.
Mutation decision table
| Case | Caller | Target | Required result |
|---|---|---|---|
| Allowed update | A member | A record | Approved field changes once |
| Cross-tenant update | A member | B record | B remains byte-for-byte unchanged |
| Cross-tenant delete | B member | A record | A remains available to its owner |
| Create with foreign tenant ID | A member | B as supplied owner | Reject or derive A from trusted context |
| Revoked membership | Former A member | A record | No mutation after revocation |
For each denied case, compare the stored row, related child rows, queued jobs, notifications, and audit event. An error response alone cannot show that the operation had no effect. Keep the verification within the test environment and record a build ID and expected state.
Pay special attention to partial updates. A generic patch parser may accept a protected owner field even when the route checks the object ID. Define an input allowlist and test each sensitive property independently. The server should derive the acting identity from a trusted session or token; a caller-supplied tenant header is not authority.
Check downstream effects
Illustrative denied PATCH of B7 by A: before=(B7 status:draft, owner:B, jobs:0, notices:0); after must equal before for the record, child rows, jobs, and notices. Only a bounded denial audit event may be new. Run an allowed B update in the same build and require its intended state change. A 403 with B7 status:published is a failed test, not a passing denial.
The OWASP authorization regression guide helps turn the table into CI assertions. Include a permitted write so a blanket deny cannot pass as a security fix. If a failed call changes any state, hold the release and investigate before describing the issue as resolved. Do not run these checks against a third-party system without explicit written permission.
Find the gaps in your access controls
See how an illustrative cross-workspace finding connects evidence, impact and a fix. Discuss the tenant boundaries your team needs reviewed.