{"id":"GHSA-wrr4-782v-jhwh","aliases":[],"status":"published","title":"neotoma has tenant isolation gap in relationship query endpoints","description":"## Summary\n\nThe `/list_relationships` and `/retrieve_graph_neighborhood` endpoints call `getAuthenticatedUserId` (confirming a valid session exists) but do not pass the resolved user ID into the Supabase query as an `.eq(\"user_id\", userId)` filter. As a result, queries return rows from all users rather than scoping to the authenticated caller's data.\n\n## Affected code\n\n**`/list_relationships`** (`src/actions.ts`):\n- Calls `getAuthenticatedUserId` but does not apply `.eq(\"user_id\", userId)` to the relationships query\n- Uses `.or()` string interpolation for entity ID matching without input validation\n\n**`/retrieve_graph_neighborhood`** (`src/actions.ts`):\n- Same pattern: auth resolved, user ID not applied to query filter\n\n## Affected versions\n\nv0.13.0\n\n## Prerequisites\n\n1. A valid authentication token for the Neotoma instance (attacker must have a legitimate account on the same instance)\n2. A known entity ID belonging to another user (~96 bits of entropy — brute-force not practical)\n\nAn unauthenticated caller is rejected at the auth middleware layer. The gap requires a second user account on the instance.\n\n## Impact\n\nAn authenticated user with a known cross-user entity ID can retrieve relationship edges and graph neighborhood data belonging to another user. No write capability is exposed.\n\n## Severity\n\nLow under current conditions — no multi-tenant deployments exist. Escalates to Medium the moment two or more user accounts share an instance.\n\n## Remediation\n\n1. Add `.eq(\"user_id\", userId)` to all Supabase queries in both handlers\n2. Validate entity ID inputs with `isNeotomaEntityId` before query construction\n3. Replace `.or()` string interpolation with separate scoped `.eq()` calls\n\nFix tracked in #365 (list_relationships) and #366 (retrieve_graph_neighborhood). Gate gap tracked in #372.","severity":{"version":"4.0","score":0.6,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U","level":"low","from":"ghsa"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-201"],"published":"2026-06-25T17:46:49Z","modified":"2026-06-25T18:00:08Z","fixes":[{"ecosystem":"npm","package":"neotoma","fixed_in":["0.14.0"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"npm","package":"neotoma","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0.13.0"},{"fixed":"0.14.0"}]}]},{"source":"osv","ecosystem":"npm","package":"neotoma","ranges":[{"type":"SEMVER","events":[{"introduced":"0.13.0"},{"fixed":"0.14.0"}]}]}],"references":[{"url":"https://github.com/markmhendrickson/neotoma/security/advisories/GHSA-wrr4-782v-jhwh","tags":["web"]},{"url":"https://github.com/markmhendrickson/neotoma/issues/365","tags":["web"]},{"url":"https://github.com/markmhendrickson/neotoma/issues/366","tags":["web"]},{"url":"https://github.com/markmhendrickson/neotoma/issues/372","tags":["web"]},{"url":"https://github.com/advisories/GHSA-wrr4-782v-jhwh"},{"url":"https://github.com/markmhendrickson/neotoma","tags":["package"]}],"sources":{"ghsa":[{"id":"GHSA-wrr4-782v-jhwh","title":"neotoma has tenant isolation gap in relationship query endpoints","description":"## Summary\n\nThe `/list_relationships` and `/retrieve_graph_neighborhood` endpoints call `getAuthenticatedUserId` (confirming a valid session exists) but do not pass the resolved user ID into the Supabase query as an `.eq(\"user_id\", userId)` filter. As a result, queries return rows from all users rather than scoping to the authenticated caller's data.\n\n## Affected code\n\n**`/list_relationships`** (`src/actions.ts`):\n- Calls `getAuthenticatedUserId` but does not apply `.eq(\"user_id\", userId)` to the relationships query\n- Uses `.or()` string interpolation for entity ID matching without input validation\n\n**`/retrieve_graph_neighborhood`** (`src/actions.ts`):\n- Same pattern: auth resolved, user ID not applied to query filter\n\n## Affected versions\n\nv0.13.0\n\n## Prerequisites\n\n1. A valid authentication token for the Neotoma instance (attacker must have a legitimate account on the same instance)\n2. A known entity ID belonging to another user (~96 bits of entropy — brute-force not practical)\n\nAn unauthenticated caller is rejected at the auth middleware layer. The gap requires a second user account on the instance.\n\n## Impact\n\nAn authenticated user with a known cross-user entity ID can retrieve relationship edges and graph neighborhood data belonging to another user. No write capability is exposed.\n\n## Severity\n\nLow under current conditions — no multi-tenant deployments exist. Escalates to Medium the moment two or more user accounts share an instance.\n\n## Remediation\n\n1. Add `.eq(\"user_id\", userId)` to all Supabase queries in both handlers\n2. Validate entity ID inputs with `isNeotomaEntityId` before query construction\n3. Replace `.or()` string interpolation with separate scoped `.eq()` calls\n\nFix tracked in #365 (list_relationships) and #366 (retrieve_graph_neighborhood). Gate gap tracked in #372.","status":"published","published":"2026-06-25T17:46:49Z","modified":"2026-06-25T17:46:50Z","cvss":[{"version":"4.0","score":0.6,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U","level":"low"}],"cwes":["CWE-201"],"updated_at":"2026-11-02T14:40:33.778544Z"}],"osv":[{"id":"GHSA-wrr4-782v-jhwh","title":"neotoma has tenant isolation gap in relationship query endpoints","description":"## Summary\n\nThe `/list_relationships` and `/retrieve_graph_neighborhood` endpoints call `getAuthenticatedUserId` (confirming a valid session exists) but do not pass the resolved user ID into the Supabase query as an `.eq(\"user_id\", userId)` filter. As a result, queries return rows from all users rather than scoping to the authenticated caller's data.\n\n## Affected code\n\n**`/list_relationships`** (`src/actions.ts`):\n- Calls `getAuthenticatedUserId` but does not apply `.eq(\"user_id\", userId)` to the relationships query\n- Uses `.or()` string interpolation for entity ID matching without input validation\n\n**`/retrieve_graph_neighborhood`** (`src/actions.ts`):\n- Same pattern: auth resolved, user ID not applied to query filter\n\n## Affected versions\n\nv0.13.0\n\n## Prerequisites\n\n1. A valid authentication token for the Neotoma instance (attacker must have a legitimate account on the same instance)\n2. A known entity ID belonging to another user (~96 bits of entropy — brute-force not practical)\n\nAn unauthenticated caller is rejected at the auth middleware layer. The gap requires a second user account on the instance.\n\n## Impact\n\nAn authenticated user with a known cross-user entity ID can retrieve relationship edges and graph neighborhood data belonging to another user. No write capability is exposed.\n\n## Severity\n\nLow under current conditions — no multi-tenant deployments exist. Escalates to Medium the moment two or more user accounts share an instance.\n\n## Remediation\n\n1. Add `.eq(\"user_id\", userId)` to all Supabase queries in both handlers\n2. Validate entity ID inputs with `isNeotomaEntityId` before query construction\n3. Replace `.or()` string interpolation with separate scoped `.eq()` calls\n\nFix tracked in #365 (list_relationships) and #366 (retrieve_graph_neighborhood). Gate gap tracked in #372.","status":"published","published":"2026-06-25T17:46:49Z","modified":"2026-06-25T18:00:08.021100609Z","cvss":[{"version":"4.0","score":0.6,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N/E:U","level":"low"}],"cwes":["CWE-201"],"updated_at":"2026-11-02T15:46:59.131095Z"}]}}
