Access control
Question: Does a policy let every user it applies to reach other users’ rows, or trust a value users can change? Does a SECURITY DEFINER function leave search_path open or skip checking the caller? Does a grant open writes or private reads to every user? Does a public table hold users’ own data, a view return other users’ rows, or a reducer change rows its arguments choose, or admin-only settings, without checking the caller?
- Runs: when selected:
--rule access-control,--rule securityor--rule all· Fails the check by default: no - Right on projects JevGate was never tuned on: reviews none labeled, considers none labeled (how it is measured)
- Right on the projects it was tuned on: reviews 2 of 5, considers 5 of 14
- Looks at: SQL files: row-level security policies, SECURITY DEFINER functions and grants, in their final state across migrations; SpacetimeDB TypeScript modules: public tables, views and reducers
- Evidence unit: one policy with its table and the functions it calls, one SECURITY DEFINER function, or one grant; one SpacetimeDB public table with its user columns, or one view or reducer with the functions it calls and the framework version
- Acceptable: Policies tied to the user, account or membership; role checks; restrictive policies; public data; grants narrowed by row-level security; reducers that check the caller through
ctx.sender, the module owner, an admin or a trusted service identity, or run only on a schedule - Names:
security/access-control,access-control,access_control· Version: 4
When a finding is right
A finding says a SQL policy, SECURITY DEFINER function or grant lets users reach other users’ rows, or that a SpacetimeDB table, view or reducer exposes or changes other users’ data without checking the caller. It is right for a policy that trusts user_metadata, a SECURITY DEFINER function every role may call that deletes any stored file, or a grant that opens writes to every user. It is wrong when the rows are ones their owners chose to share, or when the function answers only what every user may read already. Two thirds of the findings labeled wrong or debatable were policies showing rows their owners marked shared.
The rule has no labels on projects JevGate was never tuned on, so its levels cannot be measured yet.
Findings it got wrong
Labeled wrong by reading the code, on open-source projects the rules were tuned on.
Basejump: accept_invitation
- Where:
supabase/migrations/20240414162100_basejump-invitations.sql:158in usebasejump/basejump at7a1f95c. - Finding (review): SECURITY DEFINER function
accept_invitationreads or changes other users’ rows without checking the caller. - Why it was wrong: The invitation token is the check: 30 random bytes, matched exactly and valid for a day. The function adds only the caller (
auth.uid()) to the account, and only signed-in users may execute it. - Since: cleared in 0.20.0: a SECURITY DEFINER function that acts only for whoever holds a secret token it looks up by value does not skip the caller check (changelog).
Chatbot UI: shared files
- Where:
supabase/migrations/20240108234544_add_files.sql:44in mckaywrigley/chatbot-ui at81328b6. - Finding (consider): Policy
allow view access to non-private filesonfileslikely lets every user it applies to read or change other users’ rows. - Why it was wrong: Others can read a file only once its owner changes
sharingfrom the default'private', and only the owner can, through the policy on their own files. This is the read side of sharing; tying the condition to the user’s id would remove the feature. - Since: 0.21.0 accepts a policy that lets others read rows their owners marked shared, and the same policy on
chatsis no longer reported. This one is still a consider, now for trusting a value users can change, which is wrong too: only the owner can setsharing. Not addressed.
Chatbot UI: non_private_file_exists
- Where:
supabase/migrations/20240108234544_add_files.sql:92in mckaywrigley/chatbot-ui at81328b6. - Finding (review): SECURITY DEFINER function
non_private_file_existsreads or changes other users’ rows without checking the caller. - Why it was wrong: The function returns only whether a file with that id exists with
sharing <> 'private': exactly the rows the shared-files policy already shows every role. Private and missing files both give false. Its opensearch_pathwould be a fair consider; a missing caller check is not. - Since: not addressed; reported the same way from 0.20.0 through 0.25.0.