No household-level DB enforcement — isolation relies solely on application code #13
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
All household data isolation is enforced at the application layer via
HouseholdResolver. There is no row-level security (RLS) or equivalent at the database level. If any service method forgets to filter byhouseholdId, data leaks across households.Currently all service methods do check
householdIdcorrectly, but this is a fragile pattern for a multi-tenant application.Affected files
householdIdas a parameterRecommended fix
Add defense-in-depth via one of:
@Filterwith@FilterDefthat automatically applies ahousehold_idpredicateThis ensures a missed application-layer check doesn't result in cross-tenant data exposure.
Severity
Medium — defense-in-depth concern; current code is correct but fragile.
👨💻 Kai — Frontend Engineer
This is a backend/DB concern, but there's a frontend angle worth flagging:
Impact on me
householdId, the API will silently return wrong data — and my components will render it without any indication something is off. The frontend has no way to detect cross-household leakage.Questions I'd want answered before we ship
@Filteris added, does it apply automatically to all session-scoped requests? Is there any risk it bleeds between concurrent requests in a shared session/connection pool context?What I'd ask the backend to guarantee
HandlerInterceptor, not scattered per service. If it's done inHouseholdResolver, confirm that runs early enough.householdIdon the session) should return a structured JSON{ "error": "..." }body, not a raw Spring exception page — my error handling in+page.server.tsdepends on predictable shapes.Low-risk, high-value suggestion
🛠️ Backend Engineer
This is exactly the kind of defense-in-depth gap that bites multi-tenant apps in production. The current pattern is correct but one missed
householdIdfilter in any service method is a data breach. Let's fix that.On the three options — my take
app.current_household_idas a session variable on every connection, which is non-trivial with a connection pool (HikariCP). You need aConnectionCustomizeror aStatementexecuted on connection borrow. Doable, but adds connection lifecycle complexity.@Filter/@FilterDef: Simpler to integrate — activate in aHandlerInterceptoror AOP advice, applied to all queries through the JPA session. The gap: doesn't protect against native queries or JDBC calls that bypass Hibernate. Also easy to forget to activate on a new request path.Questions before implementing
@Query(nativeQuery = true)annotations in the repositories currently? Those won't be covered by Hibernate filters and would need manualhousehold_idpredicates.FORCE ROW LEVEL SECURITYis set on the table).Suggested implementation order
@Querymethods in all repositories — list which ones filter byhousehold_idand which don't.findByIdAndHouseholdIdvariants where missing (related to #12).@Filteron household-scoped entities, activate in interceptor.🧪 QA Engineer
The current state — "correct but fragile" — is exactly the kind of risk that needs test coverage to prove the invariant holds and to catch regressions the moment someone adds a new service method without the filter.
What's missing in the test suite right now
Test cases I'd add as part of this fix
For every major GET endpoint on household-scoped resources:
shouldReturn404WhenResourceBelongsToADifferentHousehold()shouldReturnEmptyListWhenHouseholdHasNoData()(distinguishes "no data" from "wrong household")shouldNotLeakHouseholdADataToHouseholdBUser()For the Hibernate filter path:
Integration test architecture question
Coverage concern
🔒 Sable — Security Engineer
This is the right issue to raise and the right time to address it — before the surface area grows. Let me add some threat model depth.
Why "correct but fragile" is a real risk
householdIdfilter is full cross-tenant read access on that resource type. In a meal planning app with dietary restrictions and household membership data, that's a meaningful privacy breach.RLS implementation specifics to get right
ALTER TABLE ... FORCE ROW LEVEL SECURITYis set. Verify the role used by HikariCP.SET LOCAL app.current_household_id = '...'must be set within the same transaction/connection that executes the query. With HikariCP's connection pool, connections are reused — a staleapp.current_household_idfrom a previous request is a serious risk. UseSET LOCAL(transaction-scoped) notSET SESSION(connection-scoped).USINGfor read operations andWITH CHECKfor write operations. Both are needed.readonlyPostgreSQL role for SELECT queries with more restrictive RLS, versus the app role for writes.Hibernate filter gaps
@Filteronly applies to JPQL/HQL and criteria queries routed through Hibernate. Any@Query(nativeQuery = true), anyJdbcTemplatecall, anyEntityManager.createNativeQuery()bypasses it entirely. Audit for those before claiming the filter provides coverage.householdIdcheck today.Questions before closing this
FORCE ROW LEVEL SECURITYto all household-scoped tables as a backstop?My recommendation: Prioritize RLS with
SET LOCAL+FORCE ROW LEVEL SECURITYon the most sensitive tables first (meals, shopping lists, recipes). Add the Hibernate filter as a second layer. Audit native queries. Write a test that connects to the DB as the app role without setting the session variable and verifies no household data is returned.🎨 Atlas — UI/UX Designer
This is infrastructure work with no direct UI output, but there are user-facing design considerations worth flagging now rather than retrofitting later.
Surfaces where isolation failures would be visible to users
Error state design question
+page.server.tserror handling is written, which affects what Kai implements.Suggestion