WEB / WEB-14.05
Resource authorization and tenant boundaries
Signing in is not enough to read a report. Follow identity, tenant, action and ownership decisions within one request.
1. Core · Mechanism
Read every scenario without motion
Owner read · allow
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-atlas-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Duru / member
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-atlas-01 · atlas
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- ALLOW · OWNER_READ
- Resource · verified context
- Read scope permitted
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Resource can be displayed
- Server · policy evaluator
- ALLOW · OWNER_READ
- Resource · verified context
- Read performed
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: allowed read
- Server · policy evaluator
- Audit: OWNER_READ
- Resource · verified context
- report-atlas-01
Other owner · deny
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-atlas-02
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Duru / member
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-atlas-02 · atlas
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- DENY · OWNER_REQUIRED
- Resource · verified context
- Resource body withheld
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Action denied
- Server · policy evaluator
- DENY · OWNER_REQUIRED
- Resource · verified context
- Body withheld
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: denied action
- Server · policy evaluator
- Audit: OWNER_REQUIRED
- Resource · verified context
- report-atlas-02
Tenant mismatch · deny
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-orion-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Duru / member
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-orion-01 · orion
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- DENY · TENANT_MISMATCH
- Resource · verified context
- Resource body withheld
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Action denied
- Server · policy evaluator
- DENY · TENANT_MISMATCH
- Resource · verified context
- Body withheld
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: denied action
- Server · policy evaluator
- Audit: TENANT_MISMATCH
- Resource · verified context
- report-orion-01
Reviewer read · allow
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-atlas-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Ekin / reviewer
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-atlas-01 · atlas
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- ALLOW · REVIEWER_READ
- Resource · verified context
- Read scope permitted
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Resource can be displayed
- Server · policy evaluator
- ALLOW · REVIEWER_READ
- Resource · verified context
- Read performed
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: allowed read
- Server · policy evaluator
- Audit: REVIEWER_READ
- Resource · verified context
- report-atlas-01
Reviewer other tenant · deny
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-orion-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Ekin / reviewer
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-orion-01 · orion
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- DENY · TENANT_MISMATCH
- Resource · verified context
- Resource body withheld
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Action denied
- Server · policy evaluator
- DENY · TENANT_MISMATCH
- Resource · verified context
- Body withheld
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: denied action
- Server · policy evaluator
- Audit: TENANT_MISMATCH
- Resource · verified context
- report-orion-01
No verified context · deny
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.read report-atlas-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Duru / member
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Verified context missing
- Resource · verified context
- report-atlas-01 · atlas
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- DENY · CONTEXT_REQUIRED
- Resource · verified context
- Resource body withheld
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Action denied
- Server · policy evaluator
- DENY · CONTEXT_REQUIRED
- Resource · verified context
- Body withheld
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: denied action
- Server · policy evaluator
- Audit: CONTEXT_REQUIRED
- Resource · verified context
- report-atlas-01
No action grant · deny
- A request is not a decision
The UI requests an action. A requested resource or client-displayed tenant label does not establish server authority.
- Browser · UI
- report.archive report-atlas-01
- Server · policy evaluator
- Policy not evaluated
- Resource · verified context
- Context not loaded
- The request reached the server
The server establishes the subject from a verified session/context; a name supplied by the browser is not accepted as verified identity.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Duru / member
- Resource · verified context
- Context not loaded
- Object and tenant context were established
Resource tenant and ownership metadata come from a trusted server data source. Loading and authorization may be one atomic operation; this drawing separates two logical checks.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- Subject / action / context ready
- Resource · verified context
- report-atlas-01 · atlas
- Policy conditions were evaluated
Active identity, tenant match, action permission and owner/reviewer relationship are evaluated together. Skipping a condition breaks a different trust boundary.
- Browser · UI
- Waiting for decision
- Server · policy evaluator
- DENY · ACTION_NOT_GRANTED
- Resource · verified context
- Resource body withheld
- The decision returned to the browser
The response carries a resource representation only on allow. This teaching fixture explains denial reasons; production response detail should account for information disclosure.
- Browser · UI
- Action denied
- Server · policy evaluator
- DENY · ACTION_NOT_GRANTED
- Resource · verified context
- Body withheld
- Evidence preserves decision scope
Audit records actor, action, object, tenant, decision and reason without session secrets. An allow is valid only for this action/resource/context.
- Browser · UI
- UI: denied action
- Server · policy evaluator
- Audit: ACTION_NOT_GRANTED
- Resource · verified context
- report-atlas-01
ACTORS · STATE · DATA FLOW
Which report may Duru read?
Atlas and Orion are tenants for two separate organizations. Duru signs in as an Atlas member. report-atlas-01 belongs to Duru within Atlas. Ekin is a reviewer in the same tenant; reviewing reports is part of Ekin’s work. Orion reports are outside both users’ scope. Each actor in this example belongs to one tenant; products with multiple memberships must also validate active-tenant selection.
Authentication establishes Duru’s identity. Authorization decides whether that identity may perform a particular action on a particular report. Permission to read does not imply permission to update or export. Our business policy allows members to read their own reports in their tenant and reviewers to read other reports in that tenant. Platform-wide administration and cross-tenant sharing are outside this policy.
Four inputs, and who supplies them
The subject is the verified requesting principal: actorId, active membership and role. The object is the report: objectId, ownerId, tenantId and its state. The action is report.read. Context includes the server-verified active tenant and policy version. Role is a subject attribute; ownership is a relationship between subject and object. Neither substitutes for the other.
The client may select a report or tenant; selection is not a grant. The server resolves the actor from its session, obtains role and membership from an authoritative source, and reads tenantId and ownerId from stored metadata. Treating a submitted role or owner label as authoritative gives the requester control over the decision. Authoritative data also needs freshness: a stale cached membership may no longer describe the user’s entitlement.
Unpacking an allow decision
Duru requests read access to report-atlas-01. The handler establishes an active principal and verified Atlas context. The policy checks that the target metadata belongs to Atlas, recognizes the action, verifies that the member role participates in that action, and compares ownerId with actorId. Only after all required conditions hold does it return allow/OWNER_READ and release the protected report body.
For another owner’s Atlas report, the final condition fails: deny/OWNER_REQUIRED. An Orion target fails the tenant condition: deny/TENANT_MISMATCH. Ekin’s reviewer role grants an ownership exception inside Atlas; it does not remove the Orion boundary. This ordering belongs to our teaching policy, not every framework. An application may instead perform a scoped lookup that never loads another tenant’s metadata and returns not found.
Deny prevents disclosure and execution
An unknown action, missing context or undefined role has no explicit grant and yields deny. Failure of the decision service must not permit a protected operation to continue. A denied read returns no confidential body; a denied write performs no mutation. Error handling cannot become an alternative allow path. Public resources may have an explicit public policy; the tenant check in this example is not a universal requirement for every web resource.
A decision reason is an internal observation. The UI need not expose ownerId, another tenant’s name or complete policy details. Choosing 403 or an existence-concealing 404 belongs to the product’s HTTP and disclosure design. A status code alone does not reveal which permission checks actually ran.
Keep the decision across data paths
The same report may be accessed through a screen, a download or a background export job. Each path must preserve the entitlement. Tenant equality alone is insufficient: Atlas ownership and action rules still apply. The following designs carry this report policy across different paths; middleware does not automatically secure every downstream component.
- Database: scope object lookup to the tenant and retain that scope for reads, updates, deletes and related records. Query parameterization addresses injection; it does not establish ownership or tenant permission.
- Cache: authorize access before serving a protected body. Keys for results that vary by tenant or user entitlement include the relevant scope and, where needed, permission version. A cache hit is not permission to skip policy.
- Files: private export metadata binds owner and tenant. The download service evaluates that relationship; publishing a file URL does not preserve the policy automatically.
- Jobs: bind originating actor, verified tenant and action to trusted job context when scheduling an export. The worker re-establishes context and re-evaluates permissions that may have changed during the delay. Broad service-account privileges are not Duru’s entitlement.
How long is a decision valid?
Removing Ekin’s reviewer role makes an old allow decision insufficient for the next read. Policy version distinguishes rule sets; it does not itself enforce a role change. Current membership, cache invalidation and the enforcement tier must work together. Delayed or long-running operations need an explicit rule for permission changes before execution.
This lesson models reads. For updates, ownership or state may change between authorization and mutation. The required consistency may use a transaction, lock, version check or another data-platform mechanism. An animation that checks permission before a read does not prove write safety.
2. Red · Trust & Impact
Read every scenario without motion
Field boundary
- One allow does not cover every action
Duru requests report.read on report-atlas-01. The same object identity and verified tenant do not automatically grant a different action.
- Operation request
- report.read
- Policy scope
- Policy not read
- Representation gate
- Fields not selected
- Policy scope was inspected
atlas-report-read-v1 defines tenant-owned report.read only. Subject/object metadata belongs to the server fixture; a client label is not authority.
- Operation request
- report.read
- Policy scope
- atlas-report-read-v1
- Representation gate
- Fields not selected
- The operation decision was bounded
OWN_READ produces allow with OWNER_READ. This covers only report.read/report-atlas-01/atlas context, not every property or operation.
- Operation request
- report.read
- Policy scope
- ALLOW / OWNER_READ
- Representation gate
- Fields not selected
- Field output was selected separately
deliveredFields=objectId, title, body is the permitted output in this fixture. Object access and field selection are separate responsibilities; no additional private field or real data is invented.
- Operation request
- report.read
- Policy scope
- OWNER_READ
- Representation gate
- objectId,title,body
- The delivery gate preserved the outcome
The allow branch prepares only objectId, title, body for delivery. The representation contract remains visible even for an allowed object.
- Operation request
- report.read
- Policy scope
- OWNER_READ
- Representation gate
- Permitted field set ready
- The outcome stayed within its scope
This local model presents only the permitted field set. Cards show logical owners; the scene runs no HTTP endpoint or network packet.
- Operation request
- objectId,title,body
- Policy scope
- OWNER_READ
- Representation gate
- objectId,title,body
Action boundary
- One allow does not cover every action
Duru requests report.archive on report-atlas-01. The same object identity and verified tenant do not automatically grant a different action.
- Operation request
- report.archive
- Policy scope
- Policy not read
- Representation gate
- Fields not selected
- Policy scope was inspected
atlas-report-read-v1 defines tenant-owned report.read only. Subject/object metadata belongs to the server fixture; a client label is not authority.
- Operation request
- report.archive
- Policy scope
- atlas-report-read-v1
- Representation gate
- Fields not selected
- The operation decision was bounded
The UNKNOWN_ACTION fixture produces ACTION_NOT_GRANTED. A prior or different report.read allow does not change it; each action needs its own decision.
- Operation request
- report.archive
- Policy scope
- DENY / ACTION_NOT_GRANTED
- Representation gate
- Fields not selected
- Field output was selected separately
deliveredFields=[] is the permitted output in this fixture. Object access and field selection are separate responsibilities; no additional private field or real data is invented.
- Operation request
- report.archive
- Policy scope
- ACTION_NOT_GRANTED
- Representation gate
- []
- The delivery gate preserved the outcome
The deny branch produces no protected representation. Empty deliveredFields is a policy expectation, not a claimed real HTTP capture.
- Operation request
- report.archive
- Policy scope
- ACTION_NOT_GRANTED
- Representation gate
- Body withheld
- The outcome stayed within its scope
This local model rejects the action. Cards show logical owners; the scene runs no HTTP endpoint or network packet.
- Operation request
- DENY
- Policy scope
- ACTION_NOT_GRANTED
- Representation gate
- []
ASSUMPTION · BOUNDARY · CONTROL
Which trust assumption breaks?
The normal handler establishes a trusted principal through authentication; the policy then evaluates that principal’s entitlement to this object and action. “Login succeeded, therefore the report is readable” merges distinct decisions. The fault is not failed authentication but disclosure to a verified identity without the required object permission.
The matrix expresses the expected boundary rather than an attack procedure. An allow for Duru on another owner’s report suggests ineffective ownership enforcement; an allow on Orion suggests ineffective tenant enforcement. Inspect the actual policy, authoritative metadata and delivered data together. A UI message alone does not prove the root cause.
Object, function and property ask different questions
Object-level checks ask whether this action is permitted on this report. Function-level checks ask whether a capability such as report.export is available to this subject. Property-level checks restrict which fields may be read from an allowed object or changed during an update. Passing one does not automatically pass the others.
Our member report.read view excludes internalReviewNote. If report.update were defined, ownerId and tenantId would not become ordinary editable fields; changing them needs a separate management policy. Broad serialization or input binding can turn an allowed object action into unauthorized field access. Impact depends on data sensitivity and available actions; missing checks do not all have the same consequence.
Checking the tenant only once
A tenant check on the API read path does not prove one exists on the cache or private-export path. The broken assumption is a downstream component deciding that arrival already implies permission. A worker evaluating only its service role can also widen the originating actor’s scope.
The blocking control is enforcement of the documented entitlement on every protected path, with trusted context propagation. Legitimate platform operations may have a separate explicit cross-tenant policy. Observing a cross-tenant operation alone therefore does not prove a violation. Our example defines no exception and expects Orion denial.
3. Blue · Secure development & defense
Read every scenario without motion
Deny control
- A policy record is one claim
The supplied ledgerExample identifies demo-authz-001 / OTHER_TENANT. synthetic-server-policy is not a production audit source or HTTP capture.
- Decision ledger
- demo-authz-001
- Metadata / context
- Context not read
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- Rule and context were matched
Duru’s subject tenant is atlas and the report’s object tenant is orion, within one interaction. Without knowing policyVersion atlas-report-read-v1, expectations from another rule set are not imported into this decision.
- Decision ledger
- demo-authz-001
- Metadata / context
- atlas ≠ orion / v1
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- The denial reason was checked separately
OTHER_TENANT expects deny / TENANT_MISMATCH. This is an ordinary negative control; denial counts alone prove neither attack nor incorrect policy.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Expectation matched
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- Decision and delivery were separated
The supplied ledgerExample contains deliveredFields=[]. The fixture field is known to be empty; no real empty body is claimed to have been measured on the network.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Expectation matched
- Field receipt
- deliveredFields=[]
- Evidence review
- No conclusion
- All matching records were reviewed
All supplied records within requestId/caseId/policyVersion scope are compared. A conflicting duplicate is not resolved by selecting the first row; ambiguity is preserved.
- Decision ledger
- Interaction scope
- Metadata / context
- Rule scope preserved
- Field receipt
- Empty fixture field set
- Evidence review
- Fields consistent
- The regression verdict was bounded
This fixture passes the deny/empty-fields control. OWN_READ and REVIEWER_READ must remain allowed positive controls; denying everybody is not a correct policy.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Policy expectation matched
- Field receipt
- Fixture fields=[]
- Evidence review
- PASS: supplied matrix
Delivery missing
- A policy record is one claim
The supplied ledgerExample identifies demo-authz-001 / OTHER_TENANT. synthetic-server-policy is not a production audit source or HTTP capture.
- Decision ledger
- demo-authz-001
- Metadata / context
- Context not read
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- Rule and context were matched
Duru’s subject tenant is atlas and the report’s object tenant is orion, within one interaction. Without knowing policyVersion atlas-report-read-v1, expectations from another rule set are not imported into this decision.
- Decision ledger
- demo-authz-001
- Metadata / context
- atlas ≠ orion / v1
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- The denial reason was checked separately
OTHER_TENANT expects deny / TENANT_MISMATCH. This is an ordinary negative control; denial counts alone prove neither attack nor incorrect policy.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Expectation matched
- Field receipt
- Field receipt not read
- Evidence review
- No conclusion
- Decision and delivery were separated
The review copy omits the deliveredFields observation. A denial record alone cannot establish that a real body was empty.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Expectation matched
- Field receipt
- Receipt missing
- Evidence review
- No conclusion
- All matching records were reviewed
All supplied records within requestId/caseId/policyVersion scope are compared. A conflicting duplicate is not resolved by selecting the first row; ambiguity is preserved.
- Decision ledger
- Interaction scope
- Metadata / context
- Rule scope preserved
- Field receipt
- Receipt missing
- Evidence review
- Incomplete coverage
- The regression verdict was bounded
Policy denial is known; delivery remains UNKNOWN. Additional response evidence is requested; a missing observation proves neither leakage nor successful protection.
- Decision ledger
- deny / TENANT_MISMATCH
- Metadata / context
- Policy expectation matched
- Field receipt
- UNKNOWN: delivery
- Evidence review
- UNKNOWN: delivery
IMPLEMENTATION · SECURITY CONTROL · VERIFICATION
HTTP logs and decision ledgers have different jobs
An HTTP access log may show path, status and timing; it does not by itself establish that ownership or tenant policy ran. The application policy evaluator emits a decision event with server-verified actorId, subjectTenant, objectTenant, action, objectId, decision, reason and policyVersion. These are our example’s custom fields, not an OWASP event schema.
requestId links decision and response within one interaction. A background path retains jobId and originating-actor correlation. Do not log session cookies or access tokens in plaintext for correlation. Obtain tenant and actor from verified server context rather than free-form client labels. Reading the audit store also requires permission.
Three distinct evidence outcomes
The positive control is Duru’s own-read allow/OWNER_READ with only the expected fields delivered. The negative ownership control is deny/OWNER_REQUIRED with no protected body. The cross-tenant control is deny/TENANT_MISMATCH. Expected denials are a safe baseline, not automatic attack alerts. Ekin’s Atlas reviewer-read allow is also a legitimate ownership exception.
Missing actor, objectTenant or policyVersion leaves incomplete evidence. A denial event does not prove a later component sent no data. Correlate the decision with delivered fields, response and any mutation result. Lost records, clock differences and asynchronous retries can break correlation; an unknown outcome must not be filled in as allow or attack.
Explainable review logic for this fixture
Instead of inventing a backend-specific KQL table, use matrix checks: for each caseId compare expected and observed decision/reason, and require empty deliveredFields for deny cases. In this fixture, an allow where subjectTenant differs from objectTenant is a policy-regression candidate. That predicate needs an explicit exception model in products with legitimate cross-tenant grants.
Correlate policyVersion, current membership and object metadata rather than a role label alone. A broad denial-count alert can flag harmless role changes or obsolete bookmarks. Stronger violation evidence is an expected-deny case receiving allow, or protected data leaving after deny. Keep the matrix expectations separate from actual application observations; record the observed decision independently.
Fixes need regression evidence
When enforcement is absent on one path, inventory all report access paths and policy ownership. Establish a common trusted evaluator or equivalent controls across tiers; verify that pooled connections or reused workers do not retain another request’s tenant context. Cover private downloads and cached responses as well as the main API.
Regression means more than making a forbidden case deny. Own-read and same-tenant reviewer-read must still work; other-owner, cross-tenant, missing-context and unsupported-action cases must remain denied. Also evaluate role removal and cache hit/miss paths. The matrix defines expectations; actual enforcement requires tests of the relevant code paths and their outputs.
NOW APPLY IT TO THE CASE
Which fields and actions does object permission cover, and what proves it?
Duru’s permission to read an Atlas report does not grant archiving. Ekin’s reviewer role does not remove the tenant boundary. Policy decisions and fields visible in a response are compared separately.
Separate decisions for verified actor, tenant relation and requested action in the case. Do not substitute authentication or proof success for object permission.
Open the related case file ↗READ THE EXAMPLE TOGETHER
Atlas report policy: decision matrix
Select a field and examine what it supports and where the evidence ends.
Read the raw artifact and field explanations
{
"policyVersion": "atlas-report-read-v1",
"policyScope": "Tenant-owned report.read only; no global administrator or cross-tenant grant.",
"subjects": [
{
"actorId": "duru",
"tenantId": "atlas",
"role": "member",
"active": true
},
{
"actorId": "ekin",
"tenantId": "atlas",
"role": "reviewer",
"active": true
}
],
"objects": [
{
"objectId": "report-atlas-01",
"tenantId": "atlas",
"ownerId": "duru"
},
{
"objectId": "report-atlas-02",
"tenantId": "atlas",
"ownerId": "ekin"
},
{
"objectId": "report-orion-01",
"tenantId": "orion",
"ownerId": "sarp"
}
],
"ruleOrder": [
"active-subject-and-verified-context",
"tenant-match",
"known-action",
"known-role",
"owner-or-reviewer",
"explicit-allow"
],
"cases": [
{
"caseId": "OWN_READ",
"actorId": "duru",
"objectId": "report-atlas-01",
"action": "report.read",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "allow",
"reason": "OWNER_READ",
"deliveredFields": [
"objectId",
"title",
"body"
]
}
},
{
"caseId": "OTHER_OWNER",
"actorId": "duru",
"objectId": "report-atlas-02",
"action": "report.read",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "deny",
"reason": "OWNER_REQUIRED",
"deliveredFields": []
}
},
{
"caseId": "OTHER_TENANT",
"actorId": "duru",
"objectId": "report-orion-01",
"action": "report.read",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "deny",
"reason": "TENANT_MISMATCH",
"deliveredFields": []
}
},
{
"caseId": "REVIEWER_READ",
"actorId": "ekin",
"objectId": "report-atlas-01",
"action": "report.read",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "allow",
"reason": "REVIEWER_READ",
"deliveredFields": [
"objectId",
"title",
"body"
]
}
},
{
"caseId": "REVIEWER_OTHER_TENANT",
"actorId": "ekin",
"objectId": "report-orion-01",
"action": "report.read",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "deny",
"reason": "TENANT_MISMATCH",
"deliveredFields": []
}
},
{
"caseId": "NO_CONTEXT",
"actorId": "duru",
"objectId": "report-atlas-01",
"action": "report.read",
"verifiedContextTenant": null,
"expected": {
"decision": "deny",
"reason": "CONTEXT_REQUIRED",
"deliveredFields": []
}
},
{
"caseId": "UNKNOWN_ACTION",
"actorId": "duru",
"objectId": "report-atlas-01",
"action": "report.archive",
"verifiedContextTenant": "atlas",
"expected": {
"decision": "deny",
"reason": "ACTION_NOT_GRANTED",
"deliveredFields": []
}
}
],
"ledgerExample": {
"requestId": "demo-authz-001",
"caseId": "OTHER_TENANT",
"actorId": "duru",
"subjectTenant": "atlas",
"objectId": "report-orion-01",
"objectTenant": "orion",
"action": "report.read",
"policyVersion": "atlas-report-read-v1",
"decision": "deny",
"reason": "TENANT_MISMATCH",
"deliveredFields": [],
"source": "synthetic-server-policy"
}
}- subjects / objects
- Seeded identity and report metadata, not client assertions of role or ownership.
- verifiedContextTenant
- Active context established from server-verified membership; null represents missing context.
- ruleOrder
- This teaching model’s check/reason order; the first failing condition determines the reason.
- expected
- The required policy outcome, not a runtime observation.
- deliveredFields
- Permitted response field names for allow; deny should release no protected fields.
- requestId / policyVersion
- Interaction correlation and rule-set identity, not secrets or credentials.
Synthetic inert JSON, not a real HTTP request, database capture or production audit. It is not a full model of membership, time or concurrency; policyVersion does not itself revoke permission. A browser teaching evaluator does not prove server enforcement.
Two reality checks
A signed-in user may access every application resource.
Authentication establishes identity. Authorization separately evaluates the object, action and, where needed, fields.
Hiding a UI button enforces server authorization.
The UI may reflect a permission decision; a trusted service layer must still enforce the protected operation.
YOUR TURN
Work through this story.
Predict the outcome, find the evidence boundary, and explain it in your own words.
Ekin is a reviewer in tenant atlas. Ekin requests report.read for report-orion-01; verified context is atlas. What does the selected policy return?
- Deny with TENANT_MISMATCH; no protected fields are delivered.
- Allow for reports in all tenants because Ekin is a reviewer.
- OWNER_REQUIRED solely because Ekin is not the owner.
Read the explained answer
Deny with TENANT_MISMATCH; no protected fields are delivered. — Subject, object and verified context must meet within the same tenant boundary. Reviewer permission matters only after that gate passes. The deny result must leave deliveredFields empty.
What does the expected row below prove about server enforcement?
Policy expectation for OTHER_TENANT; not a runtime record
- caseId
- OTHER_TENANT
- expected.decision / reason
- deny / TENANT_MISMATCH
- expected.deliveredFields
- []
- evidence origin
- synthetic policy matrix
- It specifies the required outcome; it does not measure actual response delivery.
- It proves no foreign-tenant field was released in production.
- The empty expected list proves the cache and jobs enforce the same control.
Read the explained answer
It specifies the required outcome; it does not measure actual response delivery. — The policy matrix provides a reasoned regression expectation. Enforcement evidence must additionally measure released fields and different DB/cache/file/job paths; this JSON alone does not establish a production result.
Duru requests her own atlas report, but verifiedContextTenant is null. Why is deny required despite matching ownership? How should a cache or job path preserve this?
Read the explained answer
The selected policy first requires an active subject and verified context. A null context cannot establish the tenant match; it returns CONTEXT_REQUIRED before ownership evaluation and releases no protected body/fields. A cache hit must not skip the same permission/context gate; a job consumer must verify identity and valid scope at execution time. Missing context does not prove Duru is always unauthorized; it means this request lacks required evidence.
□ I did not substitute matching ownership for verified context.
□ I explained that deny must leave protected delivery empty.
□ I preserved the same gate and current-context requirement for cache/jobs.
Open the application console · request / response / browser
Also open request, URL, cookie and DOM examples
MANUAL LAB
Request, session, and resource policy
Two workbooks follow actual loopback HTTP requests, cookie/store boundaries, and seeded permission decisions.
Open the prediction → observation → explanation workbook ↗Source on GitHub ↗Sources and scope
- OWASP Authorization Cheat SheetIntroduction · Deny by Default · Validate the Permissions on Every Request · Prefer Attribute and Relationship Based Access Control over RBAC · Verify that Authorization Checks are Performed in the Right Location
- OWASP ASVS 5.0.0 — V8 Authorization8.1.1–8.1.4 · 8.2.1–8.2.4 · 8.3.1–8.3.3 · 8.4.1
- OWASP API1:2023 — Broken Object Level AuthorizationIs the API Vulnerable? · How To Prevent
- OWASP API3:2023 — Broken Object Property Level AuthorizationIs the API Vulnerable? · How To Prevent
- OWASP API5:2023 — Broken Function Level AuthorizationIs the API Vulnerable? · How To Prevent
- OWASP Multi Tenant Security Cheat Sheet1. Tenant Identification & Context Management · 3. Preventing Cross-Tenant Data Access (IDOR Prevention) · 4. Cache & Session Isolation · Tenant-Aware Asynchronous Work · Tenant-Aware Logging & Monitoring · 6. File Storage & Blob Isolation
- OWASP Logging Cheat SheetEvent attributes · Data to exclude · Event collection · Verification