Skip to content

Document that RLS denies a read silently but throws on writes - #290

Open
daniellekorn wants to merge 7 commits into
mainfrom
sdk-rls-read-note
Open

daniellekorn wants to merge 7 commits into
mainfrom
sdk-rls-read-note

Conversation

@daniellekorn

Copy link
Copy Markdown
Collaborator

What changed

Added a note to the EntitiesModule JSDoc: a row-level security rule that denies a read doesn't error, list() and filter() return an empty result indistinguishable from a genuine no-match. A denied create(), update(), or delete() throws instead, with an HTTP 403.

Why

This is the same fact already documented on the mintlify-docs security page (base44-dev/mintlify-docs#2168) and the Apps REST API's count endpoint page, but it was missing from the one place a developer actually calling list()/filter() would look, the SDK reference itself.

How we know

Docs regeneration

This needs a paired mintlify-docs PR (base44-dev/mintlify-docs#2173) to publish the change. I did NOT run a plain npm run create-docs-local and commit its full output, that would have pulled in a large amount of unrelated accumulated drift already sitting in this module (cursor pagination, new count()/aggregate()/upsert() methods, formatting changes) into an unrelated PR. Instead I hand-applied just this one section to the affected page and its locale mirrors. The regen drift is real but belongs in its own PR; flagging it separately rather than bundling it here.

…n writes

list() and filter() return an empty result on a denied read, the same
as a query that matched nothing. create(), update(), and delete()
throw with an HTTP 403 instead. Matches the same fact already
documented for the REST entities API and the mintlify-docs security
page.
@github-actions

github-actions Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/sdk@0.8.50-pr.290.cf1f6a8

Prefer not to change any import paths? Install using npm alias so your code still imports @base44/sdk:

npm i "@base44/sdk@npm:@base44-preview/sdk@0.8.50-pr.290.cf1f6a8"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "@base44/sdk": "npm:@base44-preview/sdk@0.8.50-pr.290.cf1f6a8"
  }
}

Preview published to npm registry — try new features instantly!

…d get()

Per review feedback: the prose repeated itself, and describing get()
as throwing 'the same as if the record didn't exist' was circular.
The real point is that get() throws not-found whether or not the
record actually exists. Also drops delete() from the write/403
bucket, its RLS check runs as part of the lookup query itself (the
same mechanism as reads), so a denied delete usually comes back as
not-found too, not 403. That's a separate finding, not documented
here yet.
Accidentally committed by an earlier 'git add -A': .gitignore's
node_modules/ pattern only matches a real directory, and this
worktree links node_modules as a symlink for local docs regen, so
the pattern never matched it.
@github-actions github-actions Bot added the docs-draft PR has auto-drafted documentation suggestions label Sep 24, 2026
…TP codes

Per review feedback: two sentences up front on the reasoning (reads
stay silent about existence, writes always signal a blocked change),
and explicit status codes on the table rows. delete() stays out of
the 403 row, its RLS check runs inside the lookup query like a read,
so a denied delete usually surfaces as not-found instead.
Comment thread src/modules/entities.types.ts Outdated
* - **Anonymous or User authentication** (`base44.entities`): Access is scoped to the current user's permissions. Anonymous users can only access public entities, while authenticated users can access entities they have permission to view or modify.
* - **Service role authentication** (`base44.asServiceRole.entities`): Operations bypass entity access rules and field-level security entirely. Can read and write any record in any entity.
*
* ## How denied reads and writes differ

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section should probably be after the Built-in User one.

Comment thread src/modules/entities.types.ts Outdated
* - **Anonymous or User authentication** (`base44.entities`): Access is scoped to the current user's permissions. Anonymous users can only access public entities, while authenticated users can access entities they have permission to view or modify.
* - **Service role authentication** (`base44.asServiceRole.entities`): Operations bypass entity access rules and field-level security entirely. Can read and write any record in any entity.
*
* ## How denied reads and writes differ

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* ## How denied reads and writes differ
* ## Denied reads and writes

Comment thread src/modules/entities.types.ts Outdated
* ## How denied reads and writes differ
*
* Denied reads behave as if the data doesn't exist, so they don't reveal
* what's there. Denied writes throw, so a blocked change never fails silently.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* what's there. Denied writes throw, so a blocked change never fails silently.
* what's there. Denied writes throw an error, so a blocked change never fails silently.

Comment thread src/modules/entities.types.ts Outdated
* - **Anonymous or User authentication** (`base44.entities`): Access is scoped to the current user's permissions. Anonymous users can only access public entities, while authenticated users can access entities they have permission to view or modify.
* - **Service role authentication** (`base44.asServiceRole.entities`): Operations bypass entity access rules and field-level security entirely. Can read and write any record in any entity.
*
* ## How denied reads and writes differ

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I wonder if this should be up here or we should just add the relevant information to the various methods listed in the table below. I think that might be a better option. WDYT?

…en heading

Per Sam's review: the section reads better after the concepts it
refers to (handlers, the methods they expose) are already introduced,
and the heading matches the noun-phrase style of its siblings.
…l section

Per Sam's review question about discoverability: keeps the table as
the one source of truth, but adds a one-line pointer to each affected
method's own doc, since that's what an IDE hover tooltip shows and
the module-level section isn't visible there.
A builder-reported observation (survey respondent 35: a denied delete
returned 404 instead of the 403 other writes get, with the row still
present) independently confirms what the code traces to: delete()
applies its row-level security rule as part of the lookup query, the
same mechanism get() uses, so a denied delete usually surfaces as
not-found rather than 403.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-draft PR has auto-drafted documentation suggestions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants