Skip to content
KEDBYTE
Site navigation
How Data Works
Chapter
47

Permissions, Secrets and Tenant Boundaries

Part H · Running Data Systems|4,266 words|about 19 min read|Volume H

47.0 What this chapter gives you#

  1. Finding a record and being allowed to read it are different questions. A correct query can return information to the wrong person. This chapter adds an explicit permission decision to the data machinery developed so far.
  2. You will distinguish identity verification, action permission, database privileges and tenant separation. You will trace a request across the application, database, cache, export and background worker rather than treating the login screen as the whole boundary.
  3. The examples extend the fictional shop into a teaching service with two tenants, T-A and T-B. They are independent organisations in this example, not automatically two branches of the same organisation. A branch restriction may be an additional rule inside a tenant.
  4. The companion capstone trusts a small principal object supplied by its tests. It demonstrates scoped operations in an isolated SQLite database. It does not implement login, session verification, PostgreSQL row security or a production security perimeter.

47.1 Who may do what#

47.1.1 PLAIN — in simple words#

  1. Authentication asks who is making a request. Authorisation asks whether that person or program may perform this particular action on this particular information now. Knowing a person’s name does not settle the second question.
  2. An order may be readable by a shop assistant but not changeable after acceptance. An accountant may see totals without seeing delivery notes. A support operator may investigate a failed job without downloading every customer’s records.
  3. Write permissions as specific actions. “Can access orders” is incomplete: reading one order, listing all orders, exporting them and changing their prices have different consequences.
  4. A safe decision needs a trustworthy identity, the requested action, the target resource and relevant context. When essential context is absent or invalid, do not invent a permissive interpretation.

47.1.2 PLAIN — a picture in your head#

  1. Imagine a building receptionist who recognises Dev. Recognition lets the receptionist establish who arrived; it does not give Dev the keys to every room.
  2. A separate assignment says which rooms Dev may enter and what work he may perform there. The assignment can expire even though Dev remains the same person.
  3. Where the comparison breaks: software has many entrances that do not look like doors: APIs, exports, scheduled jobs, search endpoints and cached responses. Hiding a button blocks none of those routes by itself.

47.1.3 PLAIN — a worked example#

  1. Give the synthetic principal dev-a membership in T-A and the actions order.read and order.create. Give reviewer-a order.read only. Neither principal belongs to T-B.
  2. dev-a reading T-A order CAP-001 is permitted if the order exists and any additional branch rule passes. reviewer-a creating a new order is denied even inside T-A. dev-a reading T-B CAP-001 is also denied, despite an identical local order identifier.
  3. Now revoke dev-a’s membership. The next operation must use the service’s declared revocation policy. A cached membership valid for another hour would create a one-hour revocation delay; that delay must not be described as immediate revocation.
  4. A compact decision table should include a valid same-tenant action, a forbidden action, a wrong tenant, an absent principal, an expired assignment and an unknown resource. These are different cases, not six names for one test.

47.1.4 PLAIN — what is really happening inside#

  1. The application receives a credential or authenticated session, resolves a principal and evaluates a policy. It then performs the action using only the allowed scope. The client-supplied URL or form is not authoritative evidence of that scope.
  2. A policy can depend on role, ownership, membership, branch, lifecycle state or an explicit grant. Each fact has an origin and a freshness requirement. Conflicting or stale facts need a defined decision, not whichever answer is convenient.
  3. Decisions and actions can be separated by time. If the target or assignment changes between them, the earlier decision may no longer justify the later action. Where that matters, recheck relevant conditions at the protected operation or bind them to a versioned transaction boundary.

47.1.5 TECHNICAL — the engineer’s version#

  1. OWASP distinguishes authentication from authorisation and recommends default denial, permission checks on every request and tests for access-control logic. Those principles motivate, but do not fully specify, our original permission matrix. [S185]
  2. Model a decision as allow(principal, action, resource, context). A successful decision is not transferable to a different resource, operation or tenant unless the policy explicitly grants that wider scope.
  3. The local capstone receives a trusted Principal value directly from its test harness. Constructing that object is not authentication. A real adapter must validate its session or token, issuer, audience, expiry and relevant membership before it invokes the operation.

47.1.6 WORDS — remember these#

  1. Principal: the person or program asking — an authenticated or otherwise explicitly established security identity used in policy evaluation. Authorisation: permission for a particular act — evaluation of a principal’s rights over a resource in a defined context. Revocation delay: time before a withdrawn permission stops working — the exposure window introduced by cached or long-lived authority.

47.2 Least privilege#

47.2.1 PLAIN — in simple words#

  1. Give each person and program the permissions needed for its job, not every permission that would make development easier. A reporting task should not need to rewrite orders merely to read totals.
  2. Separate routine work from exceptional maintenance. The program serving customer requests and the operator changing table definitions have different responsibilities and should not automatically share one powerful account.
  3. A permission can spread through membership in another group. Removing one direct grant does not necessarily remove the same permission inherited elsewhere.
  4. Least privilege is a continuing maintenance task. Old integrations and temporary repair accounts can accumulate privileges after their original purpose has ended.

47.2.2 PLAIN — a picture in your head#

  1. Mira gives the cleaner a key to the storage room, not authority to alter the shop’s agreements. The locksmith needs broader access for a repair, but that access is temporary and supervised.
  2. Keeping the locksmith’s master key beside the till would erase the benefit of carefully issued smaller keys.
  3. Where the comparison breaks: software privileges can be inherited through roles, functions and deployment tools. The visible account name may conceal more authority than its direct permissions suggest.

47.2.3 PLAIN — a worked example#

  1. Define three illustrative database identities: app_runtime, report_reader and schema_operator. app_runtime performs approved application changes; report_reader runs scoped reads; schema_operator performs reviewed schema work outside ordinary request handling.
  2. Suppose report_reader belongs to a broad legacy group with UPDATE privileges. Revoking its direct UPDATE grant does not demonstrate that updates are impossible. Inspect and test the effective privilege path, including inherited rights.
  3. A migration that creates a new table must also decide its owner and grants. Testing the old tables says nothing about the new table until its effective permissions have been checked.
  4. An operator review records the identity, permitted operations, scope, expiry where appropriate and a successful denied-operation test. A screenshot of a role name alone proves little.

47.2.4 PLAIN — what is really happening inside#

  1. Database privileges govern operations on objects such as schemas, tables, columns and functions. Ownership and role membership can supply additional authority beyond a single visible grant.
  2. Applications often connect using a shared database identity. In that arrangement, the database does not automatically know which human is behind each request. The application must preserve that distinction or establish a trustworthy database-level context.
  3. Powerful maintenance code can legitimately cross normal boundaries. Its input handling, caller checks and execution identity therefore deserve separate review; calling it a helper does not make its authority harmless.

47.2.5 TECHNICAL — the engineer’s version#

  1. PostgreSQL 17 documents privileges, object ownership and GRANT/REVOKE semantics. Object privileges and row-level policy are complementary: the former controls allowed operations on objects; the latter can restrict rows for applicable commands. [S186]
  2. A shared-table design requires an audit of effective runtime authority, including ownership and roles that bypass row policies. Do not assess the request path using only a superuser test session.
  3. Capability-style designs can pass narrower handles instead of a global unrestricted connection. That is an application design choice, not a feature automatically supplied by naming a Python variable read_only.

47.2.6 WORDS — remember these#

  1. Least privilege: only the authority needed — minimising effective permissions and their duration for a defined responsibility. Effective privilege: what the identity can actually do — the result of direct grants, memberships, ownership and special execution rules. Runtime identity: the account serving ordinary work — the database or service identity used by the request-handling process.

47.3 Database and application checks#

47.3.1 PLAIN — in simple words#

  1. An application can check a rule before asking the database to act. A database can also enforce selected rules when the action reaches it. These checks protect different parts of the path.
  2. Repeating the same mistaken assumption in two places is not independent protection. Both layers must use trustworthy context and agree about the intended rule.
  3. Database checks are especially valuable against forgotten application predicates. They still do not protect an export already copied elsewhere or an unrestricted administrator acting outside the normal path.

47.3.2 PLAIN — a picture in your head#

  1. The receptionist checks a visitor’s assignment, and a room door checks a badge. A mistaken receptionist decision need not open the door if the badge genuinely lacks permission.
  2. But if the receptionist can print any badge without restriction, the second check offers less separation than it first appears to.
  3. Where the comparison breaks: a database policy can inspect a session setting established by the application. A caller able to change that setting may be able to impersonate another scope unless the surrounding boundary prevents it.

47.3.3 PLAIN — a worked example#

  1. Consider this original PostgreSQL teaching sketch, not an executed server configuration. trusted_tenant() represents a separately reviewed, non-spoofable context mechanism; it is deliberately not defined as “whatever the request says.”
ALTER TABLE scoped_orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE scoped_orders FORCE ROW LEVEL SECURITY;
CREATE POLICY scoped_order_policy ON scoped_orders
  USING (tenant_id = trusted_tenant())
  WITH CHECK (tenant_id = trusted_tenant());
  1. USING governs which existing rows pass the policy for applicable operations. WITH CHECK constrains proposed row values. A write must not be allowed to move a row into another tenant merely because the original row was visible.
  2. Test through the real intended runtime role and connection pool. Also test omitted context, reused connections and an attempted tenant change. Running the sketch as an unrestricted superuser does not establish tenant isolation.
  3. This policy sketch is incomplete as a deployable configuration. Roles, table privileges, context construction, function execution rights and all other policies must be reviewed together.

47.3.4 PLAIN — what is really happening inside#

  1. A policy predicate participates in query processing. It may filter rows or reject a proposed modification, depending on the command and policy definition.
  2. Application checks add business meaning: whether an order is editable, whether a refund needs another approval, or whether the chosen export purpose is permitted. Those meanings are not inferred from a tenant column alone.
  3. Connection pooling creates a lifecycle problem. Any per-request context must be established and cleared in a scope that prevents one request from inheriting another’s identity, including error and rollback paths.

47.3.5 TECHNICAL — the engineer’s version#

  1. In PostgreSQL 17, enabled row security without an applicable policy defaults to denying row access. Superusers and BYPASSRLS roles bypass it; owners normally bypass it unless FORCE is used. Whole-table operations and integrity checks have additional documented exceptions. [S187]
  2. Permissive policies combine with OR and restrictive policies with AND, within the documented applicability rules. An accidentally broad permissive policy can therefore defeat a narrow policy’s intended result. [S187]
  3. The SQLite capstone uses explicit application predicates instead of PostgreSQL row policies. Its tests establish behaviour through that helper, not safety against someone holding an unrestricted database connection.

47.3.6 WORDS — remember these#

  1. Row-level security: a filter or rule on individual rows — database policy enforcement beyond ordinary object privileges. Connection context: identity-related state attached to database work — information whose establishment, reuse and clearing form part of the security boundary. Policy bypass: an execution path exempt from a rule — special authority that must be accounted for when testing enforcement.

47.4 Tenant separation#

47.4.1 PLAIN — in simple words#

  1. A tenant is a separately scoped organisation or customer group in a shared service. The service may share machinery, but one tenant must not automatically inherit another’s records or authority.
  2. Scope belongs in more places than a SELECT statement. It affects record identities, references, duplicate-request keys, caches, object names, exports and background jobs.
  3. Two tenants may both have an order called CAP-001. Treating CAP-001 alone as globally unique can mix their work, even when each tenant’s own data is internally consistent.
  4. A tenant boundary and a branch boundary answer different questions. A manager allowed across branches inside T-A is not thereby allowed into independent tenant T-B.

47.4.2 PLAIN — a picture in your head#

  1. Two shops rent separate rooms in one warehouse. Each room has a shelf labelled 17. “Get the item from shelf 17” is incomplete without the room.
  2. A delivery note must carry the room as well as the shelf. Otherwise the warehouse worker can follow the note faithfully and still take the wrong item.
  3. Where the comparison breaks: shared services can duplicate and transform records into caches or reports. The tenant scope must survive those transformations; it is not a physical wall that stays attached automatically.

47.4.3 PLAIN — a worked example#

  1. The capstone uses keys such as (tenant_id, order_id) and (tenant_id, request_id). A request called R-1 in T-A is not a replay of R-1 in T-B.
  2. A child line references its parent’s complete scoped key. Otherwise a syntactically valid child could point to a parent in another tenant.
  3. A cache key might include an explicit tuple of tenant, resource identity and representation version. For user-specific responses it may need additional permission context, or the design should avoid sharing that cached representation.
  4. Searching across all tenants, selecting the top ten matches and then removing forbidden results can also change the answer. Apply authorised eligibility before the result limit when that is the intended query contract, and test empty and narrow scopes.

47.4.4 PLAIN — what is really happening inside#

  1. Tenant context is established at the trusted boundary and travels with the operation. A job producer records it; a worker validates that the job is allowed; every derived resource is named and queried within that scope.
  2. Database constraints can prevent some cross-tenant associations through composite keys. They cannot decide whether the supplied tenant truly belongs to the authenticated principal without an authority mechanism.
  3. Separate databases offer a different isolation boundary from shared tables. They also change maintenance and reporting costs. Neither architecture is automatically secure if administrative credentials, exports or backup access are shared carelessly.

47.4.5 TECHNICAL — the engineer’s version#

  1. The original shared-table capstone includes tenant in relevant primary and foreign keys and in application predicates. Its boundary is the trusted helper interface, not arbitrary SQL.
  2. OWASP’s multi-tenant guidance identifies tenant context and separation across data access and shared infrastructure as concerns beyond authentication alone. Our branch/tenant fixtures are original examples of that distinction. [S188]
  3. For each store, document the isolation unit, identity source, access mechanism and privileged bypass path. This inventory is more informative than a blanket claim that the architecture is tenant-safe.

47.4.6 WORDS — remember these#

  1. Tenant: one separately scoped customer organisation — an isolation domain within a service that may share infrastructure. Scoped key: an identifier plus its domain — a composite identity such as tenant_id and order_id. Confused deputy: a privileged helper misused through supplied instructions — an authority-bearing component acting for the wrong caller or scope.

47.5 Secret handling#

47.5.1 PLAIN — in simple words#

  1. A secret is information whose possession grants a capability or exposes protected data: a database password, signing key or service token, for example. A username or public key need not be secret merely because it looks technical.
  2. Keep secrets out of books, sample files, public repositories, logs and screenshots. A placeholder should be unmistakably a placeholder, not a copied working credential.
  3. Restrict who can obtain each secret, which systems may use it and how long it remains valid. Replacing a leaked string in the source file does not necessarily invalidate copies already made.
  4. Plan rotation and revocation before an incident. A service that cannot replace its credentials without losing all availability has a serious operational dependency.

47.5.2 PLAIN — a picture in your head#

  1. A safe combination should not be taped to the safe. Moving the note into a drawer helps only if everyone who could see the tape can no longer open the drawer.
  2. If the combination was photographed, shredding the original note does not make the photograph stop working. The combination itself needs changing.
  3. Where the comparison breaks: rotating an encryption key is not always equivalent to changing a password. Existing ciphertext may still require an older key or a carefully managed re-encryption process.

47.5.3 PLAIN — a worked example#

  1. The production design proposal gives the reporting job a short-lived, read-scoped credential obtained from a protected service identity. The book supplies no such credential and makes no call to a secret manager.
  2. A rotation rehearsal records when the replacement became usable, which consumers moved to it and when the old authority was revoked. A successful new connection does not by itself demonstrate that old access is disabled.
  3. Suppose a log captured an authorization header. Treat log storage and its readers as part of the exposure, revoke the affected credential under the incident procedure and review why redaction failed. Merely deleting one log line is not a complete response.
  4. The lab uses synthetic principal names only. Its absence of passwords is not proof of a secure login implementation; login is outside the exercise.

47.5.4 PLAIN — what is really happening inside#

  1. A secret has a lifecycle: creation, distribution, use, rotation, revocation and disposal. Each step has permissions, evidence and failure cases.
  2. Applications should log operational facts without recording the capability itself. Error handlers, tracing middleware and debug dumps deserve attention because they can expose values the normal path hides.
  3. A deployment system can inject secrets without placing them in source, but the runtime still has some means of using them. Restricting process access, crash dumps and administrative inspection remains relevant.

47.5.5 TECHNICAL — the engineer’s version#

  1. OWASP’s secrets-management guidance covers centralised handling, lifecycle controls, access restriction and rotation. A specific deployment must still establish how workload identity reaches that mechanism and how a failed rotation behaves. [S189]
  2. Distinguish authentication credentials from encryption keys and signing keys. Their consumers, compromise consequences and retirement procedures differ; one generic rotate_secret function does not settle all three.
  3. The public companion code contains no production credentials. Its tests do not validate an external secret manager, hardware security module or production key-retirement procedure.

47.5.6 WORDS — remember these#

  1. Secret: protected capability-bearing information — a credential or key requiring controlled disclosure and use. Rotation: replacing active secret material — transitioning consumers to new authority under an explicit lifecycle. Revocation: withdrawing previously valid authority — disabling future accepted use, distinct from deleting one stored copy.

47.6 Adversarial boundary tests#

47.6.1 PLAIN — in simple words#

  1. A test that an authorised user succeeds is only half the story. Also test that a nearby unauthorised request fails without changing or revealing protected data.
  2. Change one important input at a time: tenant, object identifier, action, membership, version or lifecycle state. That reveals which boundary caused the decision.
  3. Repeat the tests through every supported path. A secure detail page does not prove a secure export, bulk endpoint or background worker.
  4. A rejected response is not enough if the operation already changed data. Inspect the protected state as well as the returned error.

47.6.2 PLAIN — a picture in your head#

  1. Mira tests a locked room by entering with the correct key, then trying a wrong key, an expired pass and an open side entrance.
  2. She also checks whether the attempted entry changed the lock or left the side door open. Hearing “access denied” from a guard does not establish what happened elsewhere.
  3. Where the comparison breaks: timing, error text and aggregate counts can reveal information without returning a whole record. A test suite has to name the observable channels it actually checks.

47.6.3 PLAIN — a worked example#

  1. Create T-A CAP-001 and T-B CAP-001 with deliberately different synthetic contents. Read using an authorised T-A principal and assert that only T-A’s contents appear.
  2. Attempt a T-B read and write with that same principal. Assert denial and compare both tenants’ records before and after. A cross-tenant write must not quietly become a successful zero-row business operation.
  3. Reuse R-1 independently in both tenants. Assert that each tenant receives its own result. Reuse R-1 inside T-A with a changed payload and assert a conflict without a second order.
  4. Supply a principal missing the required action and inspect that no request/outbox record was created. These controls make the capstone’s scoped helper behaviour observable, while leaving real identity-provider tests to a deployment-specific adapter.

47.6.4 PLAIN — what is really happening inside#

  1. The test constructs known protected data, invokes one path under known authority and compares outputs and state with explicit expectations. Its evidence concerns that configuration and path.
  2. A bypass test deliberately invokes a lower layer to show the boundary. The SQLite connection can execute unrestricted SQL, so possession of that connection is outside the helper’s tenant guarantee.
  3. Release evidence should identify the tested revision and runtime. A passing result from last week’s code does not automatically describe today’s additional export endpoint.

47.6.5 TECHNICAL — the engineer’s version#

  1. Organise tests by principal × action × resource scope × state. Add negative and boundary cases rather than only generating many random inputs without a policy oracle.
  2. Record denied-operation effects, error-shape expectations and tested interfaces. Avoid describing a narrow unit suite as penetration testing or an independent security audit.
  3. The final companion inventory identifies the executable checks. The production checklist separately calls for authentication integration, pooled-context lifecycle tests, real database role tests, export/worker coverage and review of privileged paths. [S185]

47.6.6 WORDS — remember these#

  1. Negative control: a case that should be refused — a deliberately unauthorised or invalid operation with a checked outcome. Policy oracle: the expected permission decision — the independently stated rule used to judge a test result. Boundary coverage: which protected paths were exercised — an inventory of tested interfaces, scopes and states, not a universal guarantee.

47.97 Practice and worked answers#

  1. Question: dev-a belongs to T-A. Does a caller-supplied tenant_id of T-B change that membership? Answer: No. Scope comes from trusted authority, not from the target supplied in a request.
  2. Question: Why is (tenant_id, request_id) preferable to request_id alone in this shared-table example? Answer: Independent tenants can legitimately choose the same request identifier; their replay histories must not collide.
  3. Question: What is missing from a successful PostgreSQL test run as superuser? Answer: Evidence about the intended restricted role, since superuser bypasses ordinary row-policy enforcement.
  4. Question: Does hiding the export button prevent an unauthorised export request? Answer: No. The server-side export path needs its own enforced permission boundary.
  5. Question: Does replacing a leaked password in a configuration file revoke existing copies? Answer: Not necessarily. The credential’s authority must be invalidated and affected consumers updated under the incident procedure.
  6. Question: What does a zero-row update establish? Answer: Only that no row matched the applied operation. It is not automatically a successful business update or a sufficiently private denial.
  7. Question: Can a branch manager in T-A read all of T-B? Answer: Not under the example’s policy. Branch scope within one tenant does not grant another tenant’s authority.
  8. Question: What is the strongest claim from the capstone’s access tests? Answer: The stated scoped helper accepts and rejects the supplied synthetic cases in its tested runtime. It does not implement or certify a complete security perimeter.

47.98 Common wrong ideas#

  1. Wrong: Logged in means allowed. Right: Each protected action still needs a permission decision.
  2. Wrong: A difficult-to-guess identifier is access control. Right: Knowledge of an identifier is not permission to use it.
  3. Wrong: Every row-policy test should use the administrator account. Right: Test the actual runtime role and its exceptions.
  4. Wrong: Tenant filtering belongs only in the main table. Right: Derived stores and background paths need scoped identities too.
  5. Wrong: Revoking a direct grant removes every route to authority. Right: Inheritance, ownership and special execution paths may remain.
  6. Wrong: A secret removed from a repository is no longer exposed. Right: Existing copies and remaining authority require separate treatment.
  7. Wrong: An error proves that nothing changed. Right: Inspect effects and transaction outcomes.
  8. Wrong: Passing unit tests means the application is secure. Right: Evidence is bounded by the paths, roles, configuration and threats actually tested.

47.99 Chapter summary in 20 lines#

  1. Authentication and authorisation answer different questions.
  2. Name the principal, action, resource and relevant context.
  3. Establish tenant scope at a trusted boundary.
  4. Deny operations whose required authority is absent.
  5. Separate reading, writing, exporting and administrative actions.
  6. Treat revocation freshness as a declared operational property.
  7. Minimise effective privileges, not only visible direct grants.
  8. Separate runtime and schema-maintenance identities.
  9. Application and database checks protect different layers.
  10. Test database policies using the intended restricted role.
  11. Account for ownership and bypass exceptions.
  12. Prevent pooled connections from retaining another request’s context.
  13. Include tenant scope in relevant keys and references.
  14. Preserve that scope through caches, jobs, search and exports.
  15. Keep branch restrictions distinct from tenant boundaries.
  16. Give secrets a complete controlled lifecycle.
  17. Rotation and revocation are not the same as deleting a file.
  18. Test refused operations and their effects on stored state.
  19. State exactly which interfaces and threats a test covers.
  20. A bounded teaching helper is not a complete production security system.

Return to contents