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
{{ message }}
Repository navigation
Commit 929efdf
Browse filesBrowse the repository at this point in the historyBrowse files
docs(security): document requireAuth anonymous-deny across all HTTP surfaces (#2567) (#2993)
The api.requireAuth schema description + JSDoc claimed the anonymous-deny posture
applied to REST /data/* only. Post-#2567 the same value governs every entry point
that reaches object data (REST /data, /meta, GraphQL, raw-hono /data) via the
shared shouldDenyAnonymous decision. Update the source-of-truth zod describe/JSDoc
(regenerating references/api/rest-server.mdx) and the hand-written
permissions/authorization.mdx "Anonymous deny" enforcement-chain row to reflect
the uniform, by-surface posture and the single requireAuth:false opt-out.
Doc-only; no behavior change. spec rest-server tests 54/54, doc-authoring guard clean.
Claude-Session: https://claude.ai/code/session_01ShknNUYoSTspJLBiqorD8V
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: content/docs/permissions/authorization.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -48,7 +48,7 @@ site — the file you read when behavior surprises you.
48
48
49
49
| # | Gate | What it decides | Enforcement site | Failure direction |
50
50
|---|---|---|---|---|
51
-
| 1 |**Anonymous deny**| No identity → HTTP 401 on `/data/*`. **Default-on** (ADR-0056 D2): public data serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. |`packages/rest/src/rest-server.ts``enforceAuth`(default in `packages/spec/src/api/rest-server.zod.ts`) | fail-closed |
51
+
| 1 |**Anonymous deny**| No identity → HTTP 401. **Uniform across every HTTP surface that reaches object data** (#2567): REST `/data`, the metadata endpoints (`/meta`), the dispatcher GraphQL endpoint (`/graphql`), and the raw-hono standard `/data` routes — one shared decision, so a caller denied on `/data` can't read the same rows through a sibling door. **Default-on** (ADR-0056 D2): public serving requires an explicit `api.requireAuth: false` opt-out, which logs a boot warning. Control plane (`/auth`, `/health`, `/discovery`) is exempt; share-links validate their token then read as SYSTEM. |`packages/core/src/security/anonymous-deny.ts``shouldDenyAnonymous` — called by `rest-server.ts``enforceAuth`, the dispatcher `handleGraphQL`/`handleMetadata`/`handleAI`, and `plugin-hono-server``denyAnonymous`(default in `packages/spec/src/api/rest-server.zod.ts`); a source-enumerating ratchet in `authz-conformance.test.ts` fails CI if a new surface ships ungated| fail-closed |
52
52
| 2 |**Public-form grant**| An anonymous form submission carries a declaration-derived `publicFormGrant` authorizing ONLY create + read-back on the form's declared target object — never anything else (ADR-0056 Option A). No guest-portal configuration needed (anonymous principals hold the `guest` position). |`packages/plugins/plugin-security/src/security-plugin.ts` (ObjectQL middleware) | scope-limited allow |
53
53
| 3 |**Object CRUD**|`allowRead/Create/Edit/Delete` (+ the destructive lifecycle class `allowTransfer/Restore/Purge`, gated ahead of the M2 operations — #1883) resolved across the caller's permission sets. |`packages/plugins/plugin-security/src/permission-evaluator.ts``checkObjectPermission`| fail-closed 403 |
54
54
| 4 |**OWD / sharing**| Org-wide default (`private` / `public_read` / `public_read_write` / `controlled_by_parent`; **unset or unknown ⇒ `private`, fail-closed** — ADR-0090 D1) plus the external dial (`externalSharingModel`, ADR-0090 D11), manual record shares, criteria sharing rules (owner-type rules are declared but seed-skipped — [not enforced](/docs/permissions/sharing-rules#owner-based-sharing-rules)), business-unit hierarchy widening (ADR-0057 D5: scope-depth hierarchy lives on `sys_business_unit`, not positions). |`packages/plugins/plugin-sharing/src/sharing-service.ts` + `sharing-rule-service.ts`| fail-closed to owner-only |
|**requireAuth**|`boolean`| ✅ | Reject anonymous requests on /data/*with HTTP 401 (secure-by-default; set false to serve data publicly) |
195
+
|**requireAuth**|`boolean`| ✅ | Reject anonymous requests on ALL HTTP surfaces that reach object data (REST /data, /meta, GraphQL, raw-hono /data) with HTTP 401 (secure-by-default; set false to serve them publicly) |
* to the data API must be an explicit deployment decision
125
-
* (`requireAuth: false`), not a silent fallthrough. Legitimate anonymous
126
-
* surfaces survive the deny posture without any opt-out: the control
127
-
* plane (`/auth`, `/health`, `/discovery`) is exempt, share-links read
128
-
* under a system context after token validation, and public-form
129
-
* submission is self-authorizing via the declaration-derived
130
-
* `publicFormGrant` (create + read-back on the declared target object
131
-
* only). Demo/playground deployments that intentionally serve data
132
-
* publicly must now set `requireAuth: false` explicitly — the REST
133
-
* plugin logs a boot warning when they do.
122
+
* **Applies to every HTTP surface, not just REST `/data`** (#2567). The same
123
+
* `requireAuth` value is threaded to every entry point that reaches object
124
+
* data, so the anonymous-deny posture is UNIFORM by surface: REST `/data`
125
+
* CRUD + batch, the metadata endpoints (`/meta`), the dispatcher's GraphQL
126
+
* endpoint (`/graphql`), and the raw-hono standard `/data` routes. All share
127
+
* one decision (`shouldDenyAnonymous` in `@objectstack/core`). Before #2567 a
128
+
* caller denied on `/data` could read the same rows through a sibling door.
129
+
*
130
+
* **Default `true` — secure by default (ADR-0056 D2).** Anonymous access must
131
+
* be an explicit deployment decision (`requireAuth: false`), not a silent
132
+
* fallthrough. Legitimate anonymous surfaces survive the deny posture without
133
+
* any opt-out: the control plane (`/auth`, `/health`, `/discovery`) is exempt,
134
+
* share-links read under a system context after token validation, and
135
+
* public-form submission is self-authorizing via the declaration-derived
136
+
* `publicFormGrant` (create + read-back on the declared target object only).
137
+
* Demo/playground deployments that intentionally serve data publicly must set
138
+
* `requireAuth: false` explicitly — the REST plugin (and the dispatcher / hono
139
+
* plugins) log a boot warning when they do.
134
140
*/
135
141
requireAuth: z.boolean().default(true)
136
-
.describe('Reject anonymous requests on /data/* with HTTP 401 (secure-by-default; set false to serve data publicly)'),
142
+
.describe('Reject anonymous requests on ALL HTTP surfaces that reach object data (REST /data, /meta, GraphQL, raw-hono /data) with HTTP 401 (secure-by-default; set false to serve them publicly)'),
0 commit comments