Messio · Engineering case study
Controlling access to kitchen data in Messio
A security review found that some membership checks still accepted suspended members. I tightened kitchen selection and the affected meal and expense routes to require active membership.
- Project
- Messio
- Focus
- Membership and kitchen access
- Scope
- Preventive security hardening
01 / Context
Each kitchen has its own members and records
Messio supports shared-kitchen operations, including meal records, purchases, deposits, and member balances. A user can belong to multiple kitchens, so each request needs the correct kitchen context and a membership check.
A membership can be suspended for a particular kitchen. That status needs to be checked when selecting the kitchen and when accessing its meal and expense records.
The shared context resolver chooses a kitchen from an explicit request header, a saved cookie, or a membership-based fallback. Downstream routes use that context when reading or changing kitchen data.
02 / Problem
Suspended members could still pass some access checks
During a security review, I found that the shared kitchen-context helper accepted both ACTIVE and SUSPENDEDmemberships. The meal and expense read routes also allowed suspended members. A suspended member could therefore pass those context and read checks for kitchen data.
03 / Decisions
Use one active-membership rule at both boundaries
The fix needed to cover both the common kitchen resolver and the sensitive routes that consume its result. Financial state also needed to remain associated with the kitchen whose activity produced it.
- 01
Make the shared context check fail closed
Selected kitchen IDs from the request header or cookie are accepted only when the user has ACTIVE membership.
Suspended membership no longer establishes the kitchen context used by downstream API routes. Malformed IDs and invalid explicit selections are rejected.
- 02
Enforce membership on sensitive routes too
Meal and expense read routes now require ACTIVE membership, and write routes check it before saving data.
The shared resolver sets the context; route-level checks protect the actual read or write. This keeps suspension behavior consistent at both boundaries.
Trade-off: Relying only on the shared resolver would reduce repeated code. Checking membership again in sensitive routes adds maintenance and can repeat database reads, but provides another enforcement point for kitchen data access. Both layers need to stay aligned as the policy changes; the performance cost has not been measured.
- 03
Keep financial records kitchen-scoped
Balances are derived from kitchen-specific records and reports rather than a global user balance field.
A person may belong to multiple kitchens, so financial state needs to remain attached to the kitchen whose meals, expenses, and deposits produced it.
04 / Implementation
Validate selection before using kitchen context
I changed the common resolver to validate the requested kitchen ID and require active membership for header, cookie, and fallback-based selection. Malformed IDs and invalid explicit kitchen selections fail closed instead of resolving another kitchen.
I also tightened the affected meal and expense API routes so read and write paths enforce the same membership rule before accessing data. Kitchen-specific records and reports replaced the global user balance field as the basis for financial state.
05 / Verification
A clearer rule for kitchen access
The hardening change covered authentication, kitchen context, and related API routes. It was committed as d0308d1. The reviewed shared resolver and meal/expense handlers explicitly check for ACTIVE membership.
The change also added focused tests for security utilities such as same-origin validation, token tampering, date validation, and request body size limits. Those tests cover related safeguards; they are not a penetration test or a direct integration test of every kitchen route.
This closes the identified authorization gap in code. It does not establish that unauthorized access happened before the fix.
Visit live app06 / Lessons
Context resolution is part of authorization
A shared resolver influences every route that relies on it. Its membership policy therefore needs the same attention as a route's own access checks. Keeping those policies aligned makes suspension behavior easier to reason about.
The next verification step is direct integration coverage for suspended members across header, cookie, and fallback selection, including meal and expense reads and writes. The existing utility tests do not establish that coverage.