You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(rls)!: rename templates to match gateway security hardening
Aligns MCP tool schemas, CLI help, and agent docs with the gateway's
2026-04-21 rename (run402 PR #35). The rename is security messaging:
the old names downplayed what the templates actually do. Breaking
change — MCP Zod enum rejects the old names, matching server behavior.
- public_read → public_read_authenticated_write
- public_read_write → public_read_write_UNRESTRICTED
(requires i_understand_this_is_unrestricted: true in body)
- user_owns_rows unchanged (server-internal: now type-aware + auto-indexed)
MCP schemas (src/tools/setup-rls.ts, bundle-deploy.ts):
- Zod enum replaced with the three current names
- New optional i_understand_this_is_unrestricted field
- Refinement enforced at handler boundary before network call
(MCP SDK accepts only ZodRawShape, so superRefine runs via an
internal schema used in the handler's safeParse)
- 14 new unit tests covering enum rejection, ACK refinement, and
request-body wiring
Docs (SKILL.md, openclaw/SKILL.md, cli/llms-cli.txt, CLI --help):
- New preamble: prefer user_owns_rows for anything user-scoped
- Safety copy per template, warning glyph on UNRESTRICTED
- Manifest examples include the ACK field
- Permission matrix updated
OpenSpec: full change artifacts under
openspec/changes/rls-template-rename/ (proposal, design, tasks,
rls-templates spec with 6 requirements / 17 scenarios).
Version bumped to 1.36.0 (breaking change to accepted MCP inputs).
Tests: 287 unit + 98 CLI e2e, 0 fail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: SKILL.md
+4-4Lines changed: 4 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -674,10 +674,10 @@ Use `run_sql` to apply RLS if users should only see their own rows:
674
674
run_sql(project_id: "prj_...", sql: "-- Use the /projects/v1/admin/:id/rls endpoint via HTTP for RLS templates")
675
675
```
676
676
677
-
Three RLS templates are available via the API:
678
-
-**`user_owns_rows`** — Users can only access rows where `owner_column = auth.uid()`. Best for user-scoped data.
679
-
-**`public_read`** — Anyone can read. Only authenticated users can write.
680
-
-**`public_read_write`** — Anyone can read and write. Use for guestbooks, public logs.
677
+
Three RLS templates are available via the API. **Prefer `user_owns_rows` for anything user-scoped.**
678
+
-**`user_owns_rows`** — Users can only access rows where the owner column matches `auth.uid()`. Best for user-scoped data (todos, workouts, messages). `uuid` owner columns get an index-friendly policy; other types fall back to a `::text` cast (the response includes a warning). The endpoint auto-creates a btree index on the owner column.
679
+
-**`public_read_authenticated_write`** — Anyone can read. **Any authenticated user can INSERT/UPDATE/DELETE any row** (not just their own). Appropriate for collaborative content like shared boards or announcements; do not use where users should only edit their own rows.
680
+
-**`public_read_write_UNRESTRICTED`** — ⚠ Fully open. Anyone (including `anon_key`) can read, insert, update, or delete any row. Only appropriate for intentionally public tables (guestbooks, waitlists, feedback forms). This template **requires**`"i_understand_this_is_unrestricted": true` in the request body and logs an audit line on the gateway.
Copy file name to clipboardExpand all lines: cli/llms-cli.txt
+25-16Lines changed: 25 additions & 16 deletions
Original file line number
Diff line number
Diff line change
@@ -112,8 +112,9 @@ Create a manifest file `app.json` (use the `project_id` from provision):
112
112
"migrations": "CREATE TABLE items (id serial PRIMARY KEY, title text NOT NULL, done boolean DEFAULT false); INSERT INTO items (title) VALUES ('Buy groceries'), ('Read a book');",
This pattern is safe to re-run on every deploy. Put your `CREATE TABLE IF NOT EXISTS` first, then one `DO` block per new column (or group them in a single block).
156
157
157
-
RLS templates:
158
-
- `user_owns_rows` — users see only their rows (requires `owner_column` per table)
RLS templates (prefer `user_owns_rows` for anything user-scoped):
159
+
- `user_owns_rows` — users access only their own rows (requires `owner_column` per table; `uuid` columns are index-friendly, others fall back to `::text` cast with a warning; a btree index is auto-created)
160
+
- `public_read_authenticated_write` — anyone reads; **any authenticated user can INSERT/UPDATE/DELETE any row** (not just their own). For collaborative content (shared boards, announcements).
161
+
- `public_read_write_UNRESTRICTED` — ⚠ fully open; `anon_key` can read AND write any row. For intentionally public tables only (guestbooks, waitlists, feedback forms). **Requires** `"i_understand_this_is_unrestricted": true` in the request body; logs an audit line on the gateway.
- `run402 projects demote-user <id> <email>` — demote a user from project_admin role
233
237
- `run402 projects <usage|schema> <id>`
234
238
- `run402 projects delete <id>` — **cascade deletes** all project resources: Lambda functions, subdomains, S3 site files, deployments, secrets, and published app versions. The schema slot is dropped and recreated. This is irreversible.
(The 3-arg form cannot set the UNRESTRICTED ACK. For UNRESTRICTED, use `run402 deploy` with a manifest that includes `"i_understand_this_is_unrestricted": true` in the `rls` block.)
236
241
237
242
Provisioning automatically sets the new project as the **active project**. Other commands that take `<id>` default to the active project when omitted.
238
243
@@ -565,7 +570,7 @@ The CLI's `run402 projects rest` command is great for terminal use. But when gen
**Auth header**: `apikey: {key}` — the gateway auto-forwards as `Authorization: Bearer` to PostgREST. Any valid project JWT works:
568
-
- `anon_key` → read-only by default (SELECT). Safe to embed in frontend code. **No expiry** -- permanent project identifier. If you apply `public_read_write` RLS to a table, anon_key gains INSERT/UPDATE/DELETE on that table — use this for browser-side writes without login.
573
+
- `anon_key` → read-only by default (SELECT). Safe to embed in frontend code. **No expiry** -- permanent project identifier. If you apply `public_read_write_UNRESTRICTED` RLS to a table, anon_key gains INSERT/UPDATE/DELETE on that table — use this for browser-side writes without login (only on intentionally public tables).
A working single-file app. Uses `public_read_write` RLS so the `anon_key` handles all reads and writes — no login required.
628
+
A working single-file app. Uses `public_read_write_UNRESTRICTED` RLS so the `anon_key` handles all reads and writes — no login required. (This template is intentionally open; only apply it to tables where anyone on the internet is allowed to write anything, like guestbooks.)
624
629
625
630
```html
626
631
<!DOCTYPE html>
@@ -650,7 +655,7 @@ A working single-file app. Uses `public_read_write` RLS so the `anon_key` handle
650
655
651
656
<script>
652
657
const API = 'https://api.run402.com';
653
-
const ANON_KEY = 'YOUR_ANON_KEY'; // safe to embed — read-only by default, write-enabled via public_read_write RLS
658
+
const ANON_KEY = 'YOUR_ANON_KEY'; // safe to embed — read-only by default, write-enabled here via public_read_write_UNRESTRICTED RLS
@@ -686,8 +691,12 @@ A working single-file app. Uses `public_read_write` RLS so the `anon_key` handle
686
691
# Create table
687
692
run402 projects sql $PROJECT_ID "CREATE TABLE guestbook (id serial PRIMARY KEY, name text NOT NULL, message text NOT NULL, created_at timestamptz DEFAULT now())"
9. **Workout Log** -- Track exercises, sets, reps, weight with progress over time.
839
848
10. **Flash Cards** -- Spaced-repetition study app. Pre-load 50 phrases.
840
849
841
-
Provision first so you have the `anon_key` to embed in your frontend HTML. The manifest should include `project_id` (from provision), `migrations` (CREATE TABLE + INSERT seed data), `rls` (almost always `public_read_write` for browser-writable apps), `files` (array of files), and `subdomain`. Your human gets a live URL they can use immediately.
850
+
Provision first so you have the `anon_key` to embed in your frontend HTML. The manifest should include `project_id` (from provision), `migrations` (CREATE TABLE + INSERT seed data), `rls` (for browser-writable apps: `public_read_write_UNRESTRICTED` + `"i_understand_this_is_unrestricted": true`; for user-scoped apps use `user_owns_rows`), `files` (array of files), and `subdomain`. Your human gets a live URL they can use immediately.
Copy file name to clipboardExpand all lines: openclaw/SKILL.md
+12-8Lines changed: 12 additions & 8 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -355,24 +355,28 @@ GET /storage/v1/object/list/assets
355
355
356
356
## Row-Level Security (RLS)
357
357
358
-
Three templates. Applied via `POST /projects/v1/admin/:id/rls` with `service_key`.
358
+
Three templates. Applied via `POST /projects/v1/admin/:id/rls` with `service_key`.**Prefer `user_owns_rows` for anything user-scoped.**
359
359
360
360
### `user_owns_rows`
361
-
Users access only rows where `owner_column = auth.uid()`. Best for user-scoped data.
361
+
Users access only rows where the owner column matches `auth.uid()`. Best for user-scoped data (todos, workouts, messages). `uuid` owner columns get an index-friendly policy; other types fall back to a `::text` cast with a warning. The endpoint auto-creates a btree index on the owner column.
Anyone can read (anon_key works). Only authenticated users can write.
366
+
### `public_read_authenticated_write`
367
+
Anyone can read (including `anon_key`). **Any authenticated user can INSERT/UPDATE/DELETE any row** (not just their own). Appropriate for collaborative content like shared boards or announcements; do not use where users should only edit their own rows.
Anyone can read and write. For guestbooks, public logs, open data.
372
+
### `public_read_write_UNRESTRICTED`
373
+
⚠ Fully open. Anyone (including `anon_key`) can read, insert, update, or delete any row. Only appropriate for intentionally public tables (guestbooks, waitlists, feedback forms). **Requires**`"i_understand_this_is_unrestricted": true` in the request body and logs an audit line on the gateway.
0 commit comments