Row security needs adversarial fixtures
Adversarial RLS fixtures test actors, tenants, commands, mutations, privileged paths, revocation, side effects, and denial behavior.
A row-security policy is not proven by the query it allows. It is proven by the neighboring queries it refuses.
The policy may protect SELECT while an UPDATE changes ownership, an INSERT writes into another tenant, a service role bypasses the boundary, or a stale membership remains effective. Reading SQL is necessary, but authorization correctness lives in combinations of actor, command, row state, and execution path.
PostgreSQL's CREATE POLICY documentation distinguishes existing-row checks in USING from new-row checks in WITH CHECK, and applies policies by command and role. Those dimensions should become fixtures, not reviewer memory.
I would create named actors and tenants, execute allowed and hostile operations through production-like clients, and assert both returned rows and side effects.
Authorization tests need negative space.
Use anonymous, member, manager, removed member, service, and owner contexts with explicit claims.
Read, create, mutate, delete, upsert, function call, and relationship lookup across tenant boundaries.
Check returned rows, affected counts, stored ownership, emitted jobs, audit events, and denial shape.
Write the authorization claim
Each protected table needs a plain-language statement of who may see and change which rows under which relationship.
I would pressure-test that decision with four questions:
- Who is the actor?
- What relationship grants access?
- Which command is allowed?
- When does authority end?
The failure mode here is starting with a predicate before agreeing on product authority. In multi-tenant applications where row-level authorization depends on roles, claims, ownership, membership changes, command-specific policies, security-definer functions, views, foreign keys, and service credentials, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be an authorization claim beside the schema. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is policies traceable to a product rule. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put an authorization claim beside the schema beside the question “Who is the actor?” before the first implementation review. The next pass would use “What relationship grants access?” to test the boundary, then “Which command is allowed?” to expose the state most likely to be missed. I would keep “When does authority end?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports policies traceable to a product rule.
Create named hostile actors
Fixtures should include strangers, removed members, downgraded roles, owners acting across tenants, anonymous clients, and privileged services.
The practical review starts here:
- Which identity is unaffiliated?
- Which claim is stale?
- Who changed roles?
- Which actor can bypass RLS?
Those questions keep testing only one valid member and one anonymous user from becoming the default. I would capture the decision in a reusable actor and tenant fixture catalog, then use it while the work is still cheap to change. For database-enforced tenant isolation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like repeatable pressure on identity boundaries. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make a reusable actor and tenant fixture catalog part of the working surface. I would use it to answer “Which identity is unaffiliated?” while scope is still flexible, and “Which claim is stale?” before code or content becomes expensive to unwind. During QA, “Who changed roles?” and “Which actor can bypass RLS?” become concrete checks rather than discussion prompts. That sequence turns database-enforced tenant isolation into something the team can operate and gives me a specific outcome to report: repeatable pressure on identity boundaries.
- PolicyPredicate review
Inspect roles, permissive and restrictive composition, USING, WITH CHECK, and ownership bypass.
- DatabaseTransactional fixture
Run hostile cases against realistic schema, constraints, functions, triggers, and views.
- ApplicationClient-path fixture
Use the same JWT claims, RPCs, server credentials, and error handling as the deployed product.
Cover every command
SELECT, INSERT, UPDATE, DELETE, and upsert paths can invoke different policy checks and deserve independent expectations.
Before implementation, I would answer:
- Can the row be read?
- Can a new row choose another tenant?
- Can ownership be changed?
- Can deletion reveal existence?
The artifact is a command-by-role expectation matrix. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is assuming a correct SELECT policy secures writes; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.
For me, the useful receipt is complete command-level authorization evidence. That connects an authorization fixture suite that tries to cross tenant and role boundaries using the same paths production uses to an observable result instead of a process claim.
I would test this with one typical case and one boundary case. The typical case should make “Can the row be read?” easy to answer. The boundary should force a decision about “Can a new row choose another tenant?” and “Can ownership be changed?.” I would record both in a command-by-role expectation matrix, including the part that stayed unresolved after the first pass. The final check, “Can deletion reveal existence?,” is where the artifact earns its place: it either supports complete command-level authorization evidence, or it shows exactly why another iteration is needed.
Test USING and WITH CHECK
Existing rows and proposed new rows answer different authorization questions during mutation.
I would use these prompts during the working review:
- Which old row is eligible?
- Which new values are permitted?
- Can tenant_id change?
- What does upsert evaluate?
If the team slips into copying one predicate without testing transition semantics, the product can still look complete while its operating rule stays ambiguous. I would make paired mutation fixtures for old and new row state the shared reference and keep it small enough to update as evidence changes.
The standard is blocked ownership and tenant-boundary changes. That tells me whether the decision helped the product, not merely whether the document was completed.
The working sequence is small: draft paired mutation fixtures for old and new row state, review it against “Which old row is eligible?,” implement the narrowest useful path, and then return with evidence for “Which new values are permitted?.” I would use “Can tenant_id change?” to inspect product consequence and “What does upsert evaluate?” to decide whether the result is stable enough to ship. This keeps copying one predicate without testing transition semantics visible as a known risk and makes blocked ownership and tenant-boundary changes the release receipt rather than a hopeful conclusion.
| Signal | Decision | Working note |
|---|---|---|
| Cross-tenant | Foreign identifier | A valid object ID from another tenant must reveal neither data nor useful existence signals. |
| State change | Membership revoked | Existing sessions, cached claims, queued jobs, and subscriptions must converge on the new authority. |
| Privileged | Bypass credential | Server-only roles need narrow placement, secret handling, logging, and tests proving they never reach clients. |
Exercise functions, views, and triggers
Database helpers may execute with different privileges or create side effects outside the policy reviewers inspected.
I would pressure-test that decision with four questions:
- Who owns the function?
- Is security definer used?
- Does a view bypass policy?
- What does a trigger write?
The failure mode here is testing the table directly while production uses RPCs. In multi-tenant applications where row-level authorization depends on roles, claims, ownership, membership changes, command-specific policies, security-definer functions, views, foreign keys, and service credentials, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a privileged-object inventory linked to fixtures. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is authorization evidence on the real execution path. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put a privileged-object inventory linked to fixtures beside the question “Who owns the function?” before the first implementation review. The next pass would use “Is security definer used?” to test the boundary, then “Does a view bypass policy?” to expose the state most likely to be missed. I would keep “What does a trigger write?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports authorization evidence on the real execution path.
Inspect relationship side channels
Foreign keys, counts, error differences, and timing can disclose that a protected row exists even when its content is hidden.
The practical review starts here:
- Do errors differ by existence?
- Can a foreign key probe IDs?
- Are counts filtered?
- Does latency reveal a match?
Those questions keep asserting only that response data is empty from becoming the default. I would capture the decision in an existence-disclosure test set, then use it while the work is still cheap to change. For database-enforced tenant isolation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like a smaller metadata and enumeration surface. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make an existence-disclosure test set part of the working surface. I would use it to answer “Do errors differ by existence?” while scope is still flexible, and “Can a foreign key probe IDs?” before code or content becomes expensive to unwind. During QA, “Are counts filtered?” and “Does latency reveal a match?” become concrete checks rather than discussion prompts. That sequence turns database-enforced tenant isolation into something the team can operate and gives me a specific outcome to report: a smaller metadata and enumeration surface.
Test revocation and freshness
Removing membership must affect tokens, caches, realtime subscriptions, background jobs, and long-running sessions according to a documented window.
Before implementation, I would answer:
- When does revocation take effect?
- Which cache holds authority?
- Can a socket keep receiving rows?
- What cancels queued work?
The artifact is a revocation convergence scenario. Its job is to expose the tradeoff early enough that design, engineering, support, or product can disagree with something concrete. The common trap is changing one database row and assuming every path stops; it moves uncertainty downstream and makes the final interface carry a problem the system never resolved.
For me, the useful receipt is measured time from authority change to enforcement. That connects an authorization fixture suite that tries to cross tenant and role boundaries using the same paths production uses to an observable result instead of a process claim.
I would test this with one typical case and one boundary case. The typical case should make “When does revocation take effect?” easy to answer. The boundary should force a decision about “Which cache holds authority?” and “Can a socket keep receiving rows?.” I would record both in a revocation convergence scenario, including the part that stayed unresolved after the first pass. The final check, “What cancels queued work?,” is where the artifact earns its place: it either supports measured time from authority change to enforcement, or it shows exactly why another iteration is needed.
Contain bypass credentials
Service roles and table owners can be necessary, but their location, purpose, and observable use must be narrow.
I would use these prompts during the working review:
- Where can the secret exist?
- Which operation requires bypass?
- Can a scoped function replace it?
- How is every use logged?
If the team slips into using a universal service credential throughout server code, the product can still look complete while its operating rule stays ambiguous. I would make a privileged-path register the shared reference and keep it small enough to update as evidence changes.
The standard is fewer unaudited paths around RLS. That tells me whether the decision helped the product, not merely whether the document was completed.
The working sequence is small: draft a privileged-path register, review it against “Where can the secret exist?,” implement the narrowest useful path, and then return with evidence for “Which operation requires bypass?.” I would use “Can a scoped function replace it?” to inspect product consequence and “How is every use logged?” to decide whether the result is stable enough to ship. This keeps using a universal service credential throughout server code visible as a known risk and makes fewer unaudited paths around RLS the release receipt rather than a hopeful conclusion.
Run fixtures in CI and after migrations
Authorization fixtures should fail when policies, claims, functions, relationships, or schema semantics drift.
I would pressure-test that decision with four questions:
- Which migration triggers the suite?
- Is the database realistic?
- Can failures show the actor and command?
- Are denial assertions deterministic?
The failure mode here is running policy tests only during a security audit. In multi-tenant applications where row-level authorization depends on roles, claims, ownership, membership changes, command-specific policies, security-definer functions, views, foreign keys, and service credentials, that can hide the exact boundary a reviewer or teammate needs to understand. My working artifact would be a CI authorization receipt. I want it close enough to the implementation that it can change the work, not created afterward to decorate the story.
The result I would look for is continuous evidence that tenant boundaries still hold. That is a narrower claim than saying the whole system improved, but it is also one I can verify and defend.
In practice, I would put a CI authorization receipt beside the question “Which migration triggers the suite?” before the first implementation review. The next pass would use “Is the database realistic?” to test the boundary, then “Can failures show the actor and command?” to expose the state most likely to be missed. I would keep “Are denial assertions deterministic?” for the release check because it asks whether the decision still holds outside the ideal path. The work is ready to move when the artifact can explain the choice and the observed result supports continuous evidence that tenant boundaries still hold.
Package security proof honestly
A strong case study shows the threat, policy decision, hostile fixture, discovered gap, fix, and remaining privileged boundary.
The practical review starts here:
- Which attack was attempted?
- What assumption failed?
- How did the policy change?
- What bypass still exists?
Those questions keep claiming secure multi-tenancy from a policy screenshot from becoming the default. I would capture the decision in a redacted authorization test report, then use it while the work is still cheap to change. For database-enforced tenant isolation, the artifact should make ownership, constraint, and next action visible without requiring a private explanation.
Success would look like credible proof of database security judgment. If I cannot point to that evidence, I have a direction, not a finished decision.
The implementation move is to make a redacted authorization test report part of the working surface. I would use it to answer “Which attack was attempted?” while scope is still flexible, and “What assumption failed?” before code or content becomes expensive to unwind. During QA, “How did the policy change?” and “What bypass still exists?” become concrete checks rather than discussion prompts. That sequence turns database-enforced tenant isolation into something the team can operate and gives me a specific outcome to report: credible proof of database security judgment.
What I would show in the work
The public version needs evidence from the work itself. For this topic, the first five artifacts I would reach for are:
- an authorization claim beside the schema
- a reusable actor and tenant fixture catalog
- a command-by-role expectation matrix
- paired mutation fixtures for old and new row state
- a privileged-object inventory linked to fixtures
I would not publish all five at equal weight. One should orient the reader, one should reveal the hardest tradeoff, and one should prove the result. The others can live in a downloadable note or appear as supporting frames. That edit matters because an authorization fixture suite that tries to cross tenant and role boundaries using the same paths production uses becomes harder to understand when every process detail is treated as equally important.
I would also show one rejected direction. The useful version is specific: which option looked attractive, which constraint made it wrong, and what evidence supported the narrower choice. That gives an engineering manager something real to question and keeps the case study from reading like the final answer was obvious from the beginning.
# given alice member of tenant-a tenant-b document exists; Alice knows its ID; membership claim and database row agree.
# when select, update, upsert, rpc Run direct table and application paths; attempt ownership change and relationship probe.
# then no disclosure or mutation Zero visible rows, unchanged record, bounded error, audit evidence, and no downstream side effect.
Resource path
The practical follow-up I would build is an RLS adversarial fixture pack with actors, tenants, membership states, SELECT/INSERT/UPDATE/DELETE cases, USING and WITH CHECK expectations, privileged paths, revocation, side effects, and CI evidence. I am treating that as a resource backlog item, not pretending the adjacent downloads below are the same artifact. The related cards cover useful pieces of the workflow today; this specific file should only be published when its examples, fields, and instructions are complete.
The first version should stay concise: context, constraint, decision, evidence, owner, and follow-up. Its value would come from helping someone repeat this exact review, not from adding another generic PDF to the site.
Review checklist
The article-specific review questions are:
- Who is the actor?
- Which identity is unaffiliated?
- Can the row be read?
- Which old row is eligible?
- Who owns the function?
- Do errors differ by existence?
- When does revocation take effect?
- Where can the secret exist?
- Which migration triggers the suite?
- Which attack was attempted?
I would add two editorial checks before publishing: can a recruiter find the point in the first minute, and can an engineer trace at least one claim to an implementation or production receipt? If either answer is no, the article needs another edit.
Implementation notes
For database-enforced tenant isolation, I would write the implementation note before polish. It would name the changed surface, source of truth, owner, failure boundary, and verification path. Those details prevent the principle from floating above the actual code or operational workflow.
The proof signals I care about are specific to this article:
- a smaller metadata and enumeration surface
- measured time from authority change to enforcement
- fewer unaudited paths around RLS
- continuous evidence that tenant boundaries still hold
- credible proof of database security judgment
I would choose two or three of those signals for the first release rather than instrumenting everything. The strongest pair usually combines one direct behavior check with one operating check: a route and a data query, a keyboard path and a support state, a handler replay and a reconciliation result, or a migration count and a rendered screen.
The follow-up belongs in the note before shipping. It should say what remains temporary, what evidence would trigger another pass, and who owns that decision. That is how the first version stays intentionally narrow without making the boundary invisible.
Case-study packaging
I would structure the case-study version around the four visual lessons already established:
- An RLS fixture crosses actor, command, and row state.
- Policy confidence grows from local SQL to production paths.
- The dangerous cases sit beside the happy path.
- A fixture should make the authorization claim executable.
The opening frame explains the product pressure. The middle two show the decision moving through the system. The last frame is the receipt: what was checked, what held, and what remained unresolved. That order lets the reader move from product judgment into implementation detail without reconstructing the whole project first.
I would include one caveat tied to multi-tenant applications where row-level authorization depends on roles, claims, ownership, membership changes, command-specific policies, security-definer functions, views, foreign keys, and service credentials: a data limit, rollout boundary, unsupported state, external dependency, or result that is still directional. A precise caveat makes the evidence easier to trust because it shows where the claim stops.
The final test is whether the page creates a better conversation. If the artifact helps someone ask a sharper question about product judgment, implementation detail, or release proof in a live interview, it belongs in the story.
Interview angle
In an interview, I would explain this through an authorization fixture suite that tries to cross tenant and role boundaries using the same paths production uses. The story should start with the product pressure, then move into the system constraint, the artifact, and the proof. That order keeps the answer grounded. It also gives the interviewer several places to go deeper: data, frontend architecture, design systems, support, migration, accessibility, or release process.
The strongest version of the answer includes a tradeoff. I want to be able to say what I chose, what I left alone, and how I knew the work helped. That is more credible than presenting every project as a clean win.
The hiring signal
An adversarial RLS fixture pack is a hiring signal because it shows I treat authorization as executable system behavior, not a collection of policies that merely look restrictive.
That is the level I want this site to communicate. The work should show taste, but it should also show operating judgment. It should make me look like someone who can enter a real product system, understand the messy middle, ship the useful version, and leave enough proof for the next person to trust it.
Use this after reading.
Practical downloads and templates that turn the article into something you can bring into a product review, implementation pass, or agent workflow.
UI PR Risk Review Checklist
A merge-readiness checklist for product intent, states, accessibility, visual durability, and UI implementation risk.
Agent-Ready API Spec Template
An OpenAPI and Postman starter template for APIs that AI agents can discover, call, and recover from safely.
Dependency Adoption Receipt
A reviewable receipt for package need, identity, provenance, permissions, supply-chain risk, verification, ownership, and removal.