Skip to content
Sabid Mahmud, home
← Back to Messio

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.

  1. 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.

  2. 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.

  3. 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 app

06 / 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.