Repository navigation
security(rest, runtime): an anonymous request at an app-declared authRequired: false endpoint executes principal-less — execute it as the guest principal (ruled C), never principal-less, never system #22147
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p1High: required for production / M2High: required for production / M2area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsPermissions that actually hold — RLS/FLS, sharing model, write-path guards
on Oct 8, 2026 objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsRuling: batch #288 item 1 · letter C · maintainer 「同意 p1」 2026-10-08T03:02Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). This card is the second of the two producer rows held privately on #21908, handed to the seat by the maintainer in chat. It was presented in the six-item form with options A (refuse), B (execute as system) and C (execute as the guest principal); the discussion then widened to the guest model as a whole (the maintainer: 「guest 是不是应该完整的重新设计并开adr」, 「最为一个元数据开发平台,guest 是常见的需求吧?」), under which the seat's interim recommendation for this card settled on C; the maintainer answered the seat's bundled ask (the design card at the priority named, and C here) with 「同意 p1」. The card was created in the same act, so there is no earlier thread; this comment is its ruling record and its body carries the shape.The ruling
C. An unauthenticated request at an endpoint an application declared
authRequired: falseexecutes as the guest principal thatassembleExecutionContextOrGuestbuilds (principalKind: 'guest',positions: ['guest'],isSystem: false; the 2026-08-08 Option A entry), threaded into theobject_operationdata calls and theflowexecutor alike. It is never principal-less and never the system principal. With no grants channel today the guest is denied on object operations, which is the correct answer until the guest-model ADR (#22146) lands its channel; the same path then serves.authRequired: truekeeps answering 401, unchanged. ⛔ Not taken: A (refusing the declared combination at publish, which the seat had offered as an interim while demand read as zero; the maintainer named the demand, and a refusal would be undone by the ADR), B (anonymous requests executing as the system principal: an escalation the published contract and the maintainer's 2B on objectstack-ai/cloud#2006 forbid).State
- Created
pm:queuein this act withbug·security·priority:p1·domain:cli·area:access; thedomain:cliseat claims it. Body:Ruled:line added in the same act. security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 gainsBlocked-by: #22147; this card lands before the deny.
Generated by Claude Code
- Created
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClaim: PM loop round 7
Session:session_01RWZbGvPFcRKvUqASZtunCU
Account:os-warren(the seat's linked user asget_meanswers it; the card's assignee)
Branch:claude/issue-22147-anonymous-endpoint-guest
Worktree:objectstack-issue-22147
Domain:domain:cli
Seat:domain:cli#1
File surface, per the card body and ruling6051299672(C, the guest entry), read onorigin/mainfec87e7e:packages/runtime/src/dispatcher-plugin.ts, the declarative-endpoint fallback branch only (resolveRequestScope→runAppEndpointStep, about:1866–:1960): where the anonymous request'sexecutionContextcomes outundefined.packages/runtime/src/api-endpoint-step.tsandpackages/runtime/src/endpoint-executor.ts: theexecutionContextthread intoobject_operation(callData) andflow(buildAutomationContext→IAutomationService.execute).packages/runtime/src/domains/automation.tsonly if the flow context needs it.- The guest envelope comes from the existing
assembleExecutionContextOrGuest(packages/core/src/security/assemble-execution-context.ts,domain:engine). It is consumed, ⛔ not edited. - Pins:
packages/runtimeunit tests beside the executor and step, and one dogfood file underpackages/qa/dogfood/test/(the showcase's declared endpoints, or a new file). .changeset/*.md:@objectstack/runtimepatch.- ⛔ No
packages/spec, nopackages/core, noplugin-security/service-automation(domain:engine/domain:services), and no docs edit (the card: the docs sentence becomes true and is not edited).authRequired: truekeeps its 401. (Stop on breach and explain in the report.)
Container & model:M,mode:subagent,model: default (opus).dispatch-gates --tierover the path gives no path-derived mandate. The card's landing shape owes an at-tier contract review before the queue (security boundary): an independent subagent atCONTRACT_REVIEW_TIER.
Clause-②: no - Narrowing only, per the ruling: an anonymous call that passed the principal-less hand-off is now evaluated as the guest. No accepted input or published shape widens.
Responsibility:platform code: @objectstack/runtime's declarative-endpoint executor threads executionContext as undefined for an anonymous request at an authRequired: false endpoint (endpoint-executor.ts :95), so object_operation and flow calls reach the engine principal-less|none: no pin asserts the anonymous endpoint path carries a principal; the guest entry exists (assembleExecutionContextOrGuest) but this path does not use it|any deployment that declares an apis: endpoint with authRequired: false (ADR-0121); a static reading by #21908's census dev, ⛔ not yet measured at a door — the dev's first step measures it
Thread-read: 6051299672
Serial constraints cleared: read 2026-10-08T03:22Z: - Open PRs (14 read, each file list read by
filename): none touchesdispatcher-plugin.ts,api-endpoint-step.ts,endpoint-executor.ts,domains/automation.ts,security/resolve-execution-context.ts,assemble-execution-context.tsor a declarative-endpoint test. - In-flight claims in every lane (21
pm:dispatchedcards read): none names those files. security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 (domain:services,pm:blocked) waits on this card and lands after it. - Declarations on [PM seat] domain:cli — ⏳ vacant #6024 since the last body edit:
6051001038(finding(metadata): revertPackage and publishPackage find a package's members by the item's own packageId/package key, so a code-shipped package whose items carry only the _packageId stamp answers 404 "No metadata items found" #22113,packages/runtime/src/domains/packages.tspublish/revert branches),6051162186(PR feat(plugin-security)!: the six built-in positions are declared position metadata (ADR-0131 C2 stage S2) #22139, two test files),6051351901(spec(v18): move PROTOCOL_VERSION 17 → 18 in an ordinary pull request with full CI: regenerate spec-changes.json and the 17 → 18 upgrade-guide section, rewrite the in-repo ^17 handshakes, and give the lockstep test one pre-mode exception (#22085 Q1) #22130,^17→^18handshake strings). None names this claim's files or regions; no objection. - This seat has no other card in flight.
domain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T03:22Zobjectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 22147,
"status": "done",
"branch": "claude/issue-22147-anonymous-endpoint-guest",
"pr": "#22177",
"session": "session_01RWZbGvPFcRKvUqASZtunCU — subagent of the PM session (harness Claude-Session line)",
"premise_still_valid": false,
"summary": "The card's premise is falsified: on origin/main fec87e7, measured on a real boot, an anonymous request admitted by an authRequired:false declared endpoint already executes as the guest principal ruling C names (principalKind 'guest', positions ['guest'], isSystem false, no user), never principal-less. The dispatcher's scope resolution (HttpDispatcher.resolveRequestScope, then resolveExecutionContext) takes the explicit guest entry assembleExecutionContextOrGuest on the runtime face (commit f586f1a); the static reading came from a doc comment in endpoint-executor.ts that said undefined for anonymous. Reach by class: an object_operation (find, get, create) is handed the guest, passes the gates that run before the principal-less hand-off (none fires), is not handed through, is refused at step 2 (the CRUD check, ADR-0056 D2 deny baseline) with 403 PERMISSION_DENIED, the driver is never reached and nothing of the row is returned; authRequired:true answers 401 before the engine; a signed-in user runs as that user; a flow endpoint hands the automation service positions ['guest'] with no user, no isSystem and no runAs, a runAs:'user' target refuses its data step fail-closed before the engine (400 FLOW_FAILED), and a runAs:'system' target runs its own data steps as the system principal by its declaration (open question 1). So no production change was built: PR #22177 (draft, Part of #22147) carries the card's pins at two tiers with ablations, plus the three corrected comments; it publishes nothing from @objectstack/runtime, so it carries no changeset (open question 2).",
"tests": "All at head 49546b0 unless stated. (1) Reach probe (throwaway, uncommitted, deleted): booted fixture with object_operation find/get/create and flow (runAs user, runAs system) endpoints at authRequired:false plus an authRequired:true control, engine spies, a front middleware with refusal stack frames and a last-registered middleware for driver reach, on base fec87e7. (2) pnpm --filter @objectstack/runtime test (local project): 333 files passed, 4714 passed, 19 skipped. (3) pnpm --filter @objectstack/runtime typecheck: exit 0, check:test-typecheck OK, debt ledger held. (4) pnpm --filter @objectstack/dogfood typecheck: exit 0, --listFiles shows the new file compiled. (5) dogfood: declarative-endpoint-anonymous-guest (6) + declarative-endpoint-policy (8) = 14 passed. (6) Runtime socket file dispatcher-plugin.endpoint-fallback.integration.test.ts: 21 passed (17 existing + 4 new). Ablations, each via scripts/ablation-replace.mjs inside an absolute-path trap, every restore verified blob == HEAD and git diff HEAD empty: A1 guest entry in security/resolve-execution-context.ts returns no context for a sessionless request: socket 2 of 21 red; real boot with runtime rebuilt, ablation-dist-preflight marker present in dist/index.js and index.cjs, 4 of 6 red (the anonymous find and get were SERVED the row with 200, the anonymous create WROTE a row with 201), restore rebuilt and preflight --absent exit 0 with the tree clean. A2 authRequired branch disabled in endpoint-policy.ts: 5 of 21 red (mine + 4 existing). A3 isSystem:true injected in api-endpoint-step.ts: 2 of 21 red. A4 isSystem injected in buildAutomationContext: first attempt VOID (ablation-replace refused before any test ran, the replacement repeated the anchor), redone with a non-overlapping replacement: 1 of 21 red. A5 user id dropped from the runtime face's input: 2 of 21 red.",
"mcp_calls": "0",
"api_writes": "3 — each through the fleet-write relay as one repository_dispatch (POST /repos/objectstack-ai/objectstack/dispatches) executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls, run 37726139416, created #22177 (draft), body read back 8172 of 8172 bytes identical; (2) label-write --issue 22177 --assign os-warren, assignee add on #22177, run 37726201855, read back matches; (3) this os-dev-report comment, POST /repos//issues/22147/comments via post-stamped.mjs. Not REST: three git pushes of the branch.",
"open_questions": [
{
"question": "Does ruling C's 'never the system principal' reach a flow's own declared runAs? An anonymous request at an authRequired:false flow endpoint whose target flow declares runAs:'system' runs that flow's data steps as the system principal (measured: the data step reached the driver and the row came back). The request itself arrives as the guest; the elevation is the flow author's explicit opt-in, read by service-automation. The docs' own anonymous example is a flow partner-webhook, and today a guest-triggered flow can touch data only through runAs:'system', because a runAs:'user' run with no user is refused.",
"options": [
"A — keep it: runAs:'system' is the explicit system opt-in the maintainer's 2B ruling admits; route 'which doors may trigger an elevated flow, and how loudly publish says so' to the guest-model ADR card (question 2, the closed list of doors).",
"B — refuse at this door (runtime): an anonymous request at a flow endpoint whose target declares runAs:'system' is refused; needs a getFlow read in the executor.",
"C — refuse at publish (packages/spec): authRequired:false with a flow target declaring runAs:'system' is rejected loudly.",
"D — demote in service-automation (domain:services): a guest-triggered run never elevates."
],
"recommendation": "A. Business need: anonymous flow endpoints exist to take partner and form input, and with no guest grants channel an elevated flow is the only way such a flow can write anything; zero in-repo declarations of the combination were measured (the showcase's flow endpoint is authRequired:true). Long term: the guest-model ADR decides the doors and the grants channel, and B, C or D now would be undone or reshaped by it. AI-error: C is the strongest guard against an AI silently opening anonymous system access, which is the real risk; it is worth weighing in the ADR, not as a stopgap here. Startup: A adds no gate, key or code. Trade-off for the maintainer: A leaves the combination publishable until the ADR lands."
},
{
"question": "Changeset for a diff that publishes nothing. The dispatch asked for a patch changeset and forbade skip-changeset, both premised on a code change. Measured: the three corrected comments sit on interfaces the runtime entry does not export, or inside a function body the bundler strips; none of the three new texts appears under packages/runtime/dist while the control (exported createDispatcherPlugin's docs) does; tests do not ship. PR #22177 carries neither, so Check Changeset reads red.",
"options": [
"A — the seat applies skip-changeset (the AGENTS.md criterion: a diff that publishes nothing from any released package).",
"B — add a patch changeset whose release note says nothing changed for anonymous endpoints."
],
"recommendation": "A: a patch bump and a CHANGELOG entry for a byte that does not ship would describe a change that did not happen."
},
{
"question": "Card closure and #21908's Blocked-by line. The premise is falsified, and #21908's engine-level deny does not change this door's answers: the guest is not principal-less, so it is refused at the CRUD check before and after the deny. Does #21908 still need to wait on #22147?",
"options": [
"A — release the Blocked-by line on #21908 now; the seat shuts this card once open question 1 is ruled or routed.",
"B — keep #21908 blocked until #22177 merges, so the pins land before the deny."
],
"recommendation": "B. The pins are the only check that tells a guest from a principal-less call once the deny makes both answer 403, so landing them first is cheap and keeps the guard ahead of the change it guards."
}
],
"out_of_scope_findings": [
"carrier: the #21908 deny round (the domain:services seat that claims it) · noted, not filed — HttpDispatcher.resolveRequestScope's catch leaves executionContext undefined on a non-classified identity-resolution fault, and at an authRequired:false endpoint such a call would run principal-less; a static read of the anonymous branch found no fault a request can steer (API-key admission and the session read each absorb their own faults, the tenancy and permission-store reads re-raise as 503); the engine-level deny closes it by construction; http-dispatcher.ts is outside this claim · dedupe words: resolveRequestScope catch undefined executionContext, identity resolution fault, principal-less fallback, endpoint fallback",
"carrier: #22146 (guest-model ADR) · noted, not filed — AutomationContext (packages/spec contract) has no principalKind or isSystem member, so the flow executor receives the guest as positions ['guest'] only, and service-automation's resolveRunDataContext refuses a user-less runAs:'user' run rather than running it as the guest; when the ADR lands a grants channel, a guest-triggered flow will need both · dedupe words: AutomationContext principalKind, guest flow run, resolveRunDataContext guest, runAs user no userId"
],
"gates": "Head 49546b0. All 67 dispatch-named commands exit 0 (recorded one per line); node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran: 'Run reconciliation — 59 derived, 59 run, 0 NOT-MEASURED, 0 UNRUN' (the 59 derived are a subset of the 67). pnpm check:dual-build-cjs-loads first answered PREREQUISITE NOT MET (8 packages had no dist/: studio, client-react, embedder-openai, knowledge-memory, knowledge-ragflow, organizations, service-cluster-redis, service-knowledge); built them (41 tasks, all cache hits) and reran: exit 0. pnpm lint (full eslint . --no-inline-config): exit 0. pnpm check:nul-bytes: exit 0. CI not awaited (in_progress at report time).",
"line_budget": "n/a — no skills/** or governed surface touched; 5 files, +403 / -7 (410 changed lines, under the 5000 human-merge threshold per dispatch-gates).",
"deviations": [
"PR line 1 is 'Part of #22147', not the dispatch's closing line: the premise is falsified and open question 1 bears on a ruled pin, so merging should not close the card before the seat rules.",
"No production code change and the suggested route not taken: the guest is already resolved at the one point the route named (resolveRequestScope); adding a second guest construction in the endpoint branch would be a consumer-side fallback for a value that is never undefined on the normal path.",
"No changeset and no skip-changeset label (open question 2): the diff publishes nothing, measured; the dispatch forbade the label, so zero label writes.",
"Commit trailers are the model-free pair (Co-Authored-By: Claude, Claude-Session), per AGENTS.md and the dispatch; the harness attribution reminder's model-named trailer and PR footer form were not used.",
"Ablation A4's first attempt was void (the tool refused before any test ran); redone and reported.",
"A throwaway probe test file existed uncommitted in the worktree during measurement; it was never committed and was moved out of the tree before the first commit.",
"Worktree removed after the PR opened (node_modules deleted, git worktree remove without --force, exit 0); the branch remains on origin at 49546b0."
],
"files_changed": [
"packages/runtime/src/endpoint-executor.ts (doc comment)",
"packages/runtime/src/api-endpoint-step.ts (doc comment)",
"packages/runtime/src/dispatcher-plugin.ts (comment, fallback branch)",
"packages/runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.ts (4 pins)",
"packages/qa/dogfood/test/declarative-endpoint-anonymous-guest.dogfood.test.ts (new, 6 pins)"
]
}objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsACCEPT — PR #22177 at
49546b0c, pending CI, the seat's self-test and the contract reviewdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· read on GitHub 2026-10-08T04:16ZThe card's premise is falsified, and the seat confirms it on
main. The dev measured it on a real boot. The seat read the code path:- The endpoint fallback in
dispatcher-plugin.tscallsHttpDispatcher.resolveRequestScope. - That calls
timedResolveExecutionContext→resolveExecutionContext(packages/runtime/src/security/resolve-execution-context.ts), which returnsassembleExecutionContextOrGuest(...). - Its own comment says why: "This face takes the EXPLICIT GUEST entry: a sessionless request on the runtime / MCP door is a first-class guest principal".
- So an anonymous request admitted by an
authRequired: falseendpoint is handed the guest envelope (principalKind: 'guest',positions: ['guest'],isSystem: false), notundefined. The static reading the card rests on came from a doc comment inendpoint-executor.tsthat said "undefinedfor anonymous". - Ruling C's posture therefore already holds on
mainat this door. What was missing is a pin that holds it.
Checked on GitHub and in the diff, not from the report:
- Shape:
- draft, base
main; - line 1 is
Part of #22147, line 2 isClause-②: no; - assignee
os-warren; - 5 files, +403 / −7.
- The order asked for
Fixes.Part ofis accepted: the card keeps an open question (below), so merging must not close it.
- draft, base
- No production change: the three source edits are comments only.
endpoint-executor.ts:BuildEndpointExecutionContextInput.executionContext's doc now says the anonymous request arrives as the guest.api-endpoint-step.ts: the same onAppEndpointExecutionInput.executionContext.dispatcher-plugin.ts: one comment aboveresolveRequestScopenames it as the one place the guest is built.- Each new sentence is true of the path the seat read above.
- Pins:
- four cases in
dispatcher-plugin.endpoint-fallback.integration.test.ts(socket); - six cases in a new
declarative-endpoint-anonymous-guest.dogfood.test.ts(real boot): guest context handed to the engine, the driver never reached,403with no byte of the row,authRequired: true→401, a signed-in caller runs as that user, and a flow is handedpositions: ['guest']with no user and noisSystem. - The dev's ablation A1 (the guest entry returns no context) made the real boot SERVE the row and WRITE one. That is the pin earning its place: once security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908's deny lands, a guest and a principal-less call both answer
403, and only these pins tell them apart.
- four cases in
- Security discipline: the PR body and the report describe reach by class, with no working request. The throwaway probe was never committed.
Changeset: none, and the seat applies
skip-changesetin this stroke.- AGENTS.md (Post-Task Checklist step 3): "that label is for a diff that publishes nothing from any released package".
@objectstack/runtimepublishesdist,README.mdandCHANGELOG.mdonly (files). The seat read the published@objectstack/runtime17.7.0 tarball: the OLD text of all three changed comments is absent from itsdist/. The two interfaces are not exported from the package entry, and the third comment sits in a function body. So the new text does not ship either.- Tests do not ship. There is no release note to write, because nothing changed for anonymous endpoints.
The dev's open questions, answered by the seat:
- A flow endpoint whose target declares
runAs: 'system'. An anonymous request at anauthRequired: falseflow endpoint runs that flow's data steps as the system principal, by the flow's own declaration.- Whether ruling C's "never the system principal" reaches a flow's declared
runAsis a security-boundary question. It is ⛔ not this seat's to answer. - At landing, this card moves to
needs-user-decision, with the dev's four options and recommendation (A: keep it, and route "which doors may trigger an elevated flow" to the guest-model ADR) as the decision analysis. - A pointer goes to design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146, whose table also still lists this door as a gap ("the executor threads
undefined").
- Whether ruling C's "never the system principal" reaches a flow's declared
- Changeset: answered above, with
skip-changeset. - security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908's
Blocked-by: #22147: B, as the dev recommends.- security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 stays blocked until this PR merges, so the pins land before the deny.
- At landing, the seat's note on security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 says the pins are on
mainand that the openrunAsquestion does not bear on the deny. Lifting the line is its owner's call.
Out-of-scope findings, routed at landing:
- To security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 (the deny round): the
catchinresolveRequestScopethat leavesexecutionContextundefined on an unclassified identity-resolution fault. - To design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146:
AutomationContextcarries noprincipalKind/isSystem, so a guest-triggered flow sees only theguestposition.
Evidence (the dev's, at
49546b0c):- runtime
localtier: 333 files and 4714 tests passed; - runtime and dogfood typechecks exit 0;
- the two dogfood files: 14 passed;
- the socket file: 21 passed;
- five ablations, each restored to a blob equal to HEAD;
dispatch-gates --ran: 59 of 59;pnpm lintexits 0.
The seat's own: the new dogfood file adds a
mkdtempSyncsite (standing lesson 1).node scripts/pm/dispatch-gates.mjs --self-testat49546b0cis running and is read before landing.CI on
49546b0c, read 2026-10-08T04:16Z: 23 success · 3 skipped · 6 in progress · 1 failure (Check Changeset, no changeset; the label answers it). ⛔ Not green.Contract review: the card's landing shape asks for one at
CONTRACT_REVIEW_TIERbefore the queue, as a security boundary.needs:contract-reviewis hung on PR #22177 in this stroke, and an independent read-only reviewer reviews49546b0c.- The endpoint fallback in
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsRelease: claim
6051510741(domain:cliseat,session_01RWZbGvPFcRKvUqASZtunCU). The pins of ruling C's posture have landed (PR #22177 →6ed0c0f3e5), and the card's premise is falsified. One question is left for a ruling: whether ruling C reaches a flow's own declaredrunAs: 'system'. It is not this seat's to answer. Card →needs-user-decision, with the analysis in the next comment; assignee cleared. The card stays open: PR #22177 saysPart of #22147.Landed: PR #22177 →
6ed0c0f3e5, a single-parent queue squashdomain:cliseat ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T05:05Z- Landing shape:
git rev-list --parents -n 1 6ed0c0f3e5names one parent,98998caac3(PR fix(service-settings): the Default timezone help says what the setting does, in every locale #22174).6ed0c0f3e5is an ancestor oforigin/main; the pre-merge head49546b0cis not.- It entered the merge queue 2026-10-08T04:38:14Z and merged 2026-10-08T05:04:42Z.
- Content on
origin/main:packages/runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.tscarries the four socket pins;packages/qa/dogfood/test/declarative-endpoint-anonymous-guest.dogfood.test.tsis present (six real-boot pins);- the three corrected comments (
endpoint-executor.ts,api-endpoint-step.ts,dispatcher-plugin.ts) say the anonymous request arrives as the guest. - No changeset:
skip-changeset, because the diff publishes nothing (read against the published 17.7.0 tarball).
- Review of record:
- ACCEPT
6052104340at49546b0c. - Contract review PASS
6052244724(CONTRACT_REVIEW_TIER, same head, independent and read-only), as the card's landing shape asked;needs:contract-reviewwas stripped before the queue. - CI on the head was green before the PR was armed.
- The seat's
dispatch-gates --self-testpassed at the head (1976 cases) for the new temp-directory site.
- ACCEPT
- What holds on
main, by class:- An anonymous request admitted by an
authRequired: falsedeclared endpoint executes as the guest principal (principalKind: 'guest',positions: ['guest'],isSystem: false), never principal-less. - Its object operation is refused at the CRUD check (
403 PERMISSION_DENIED), the driver is never reached, and nothing of the row is disclosed. authRequired: truekeeps its401, and a signed-in caller runs as that user.- A flow is handed the
guestposition with no user and noisSystem. - This was already true before the PR. The PR pins it, and the pins are the only check that will still tell a guest from a principal-less call once security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908's deny lands.
- An anonymous request admitted by an
Routed in this stroke:
- To security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908: a note that the pins it waited on are on
main. It also carries the dev's finding thatresolveRequestScope'scatchleaves the context undefined on an unclassified identity-resolution fault. Its engine-level deny closes that by construction. - To design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146: a pointer. Its table row for this door ("the executor threads
undefined") is falsified.AutomationContextcarries noprincipalKind/isSystem. And therunAs: 'system'question below touches its "doors" question.
- Landing shape:
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actions待裁:匿名端点触发声明
runAs: 'system'的 flow,是否在裁决 C 的「绝不以系统主体执行」之内domain:cli席位 ·session_01RWZbGvPFcRKvUqASZtunCU· 2026-10-08T05:05Z · 本卡挂needs-user-decision、退出活动队列;裁决人:总监席 / 维护者。⛔ 本席不代裁(安全边界属人工地板)。一句话问题: 应用作者声明了一个免登录端点,并让它指向一个标注为「以系统身份运行」的流程。此时任何匿名调用方都能触发这个流程,而流程内的数据步骤拥有全部数据权限。这算不算裁决 C 所禁止的「匿名请求以系统主体执行」?
背景
- 本卡原前提已被证伪(PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 的 dev 实测,本席在
origin/main读码复核,见 ACCEPT6052104340)。- 匿名请求进入
authRequired: false的声明端点后,已经以 guest 主体执行:principalKind: 'guest'、positions: ['guest']、isSystem: false。路径是resolveRequestScope→resolveExecutionContext→assembleExecutionContextOrGuest。 object_operation在 CRUD 检查处被拒,返回403,不泄露任何字节。- PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 落地的是钉住这一姿态的测试,不改运行时行为。
- 匿名请求进入
- 剩下的唯一问题来自
flow端点。- 请求本身以 guest 到达:自动化服务收到
positions: ['guest'],无用户、无isSystem。 - 如果目标流程声明
runAs: 'user',它的数据步骤会因「无用户」被失败关闭(400 FLOW_FAILED)。 - 如果目标流程声明
runAs: 'system',它的数据步骤以系统主体运行,绕过对象权限和 RLS。这是流程作者的显式选择,由service-automation读取执行。
- 请求本身以 guest 到达:自动化服务收到
- 裁决 C(
6051299672)说的是「门」交给引擎的主体:「never principal-less and never the system principal」。它没有说到流程自己声明的执行姿态。这是裁决范围内的空白,按 SKILL「仅前提被证伪才回箱」回箱。
Governing text
packages/spec/src/automation/flow.zod.ts,runAs的 describe(origin/main,约:1103):- 「A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers …」
- 枚举今天只有
['system', 'user']。
- ADR-0073 D2:
runAs是授权姿态。system等于{isSystem:true},是全量提权,「explicit opt-in」。目标形态的automation(RLS 生效的自动化主体)是「the target default for user-less triggers (schedule, unauthenticated webhook, internal execute)」,但属 M2,尚未落地。 - ADR-0073 D5:无用户触发配
runAs: 'user'是配置错误,作者在automation与system之间选。 - ADR-0121 D6:
authRequired: false端点必须自带限流。 - 维护者在 objectstack-ai/cloud#2006 的 2B:匿名请求不得以系统主体执行(裁决
6051299672转述)。 - 是否改协议:选项 C 改
packages/spec的 publish 门,属协议改动,按 SKILL 须单独立卡,不作本卡的执行项。
前提(各带复核命令)
- 匿名门交出 guest。复核:
git grep -n "assembleExecutionContextOrGuest" origin/main -- packages/runtime/src/security/resolve-execution-context.ts(有命中即成立)。 runAs只有system/user。复核:git show origin/main:packages/spec/src/automation/flow.zod.ts | grep -n -A2 "runAs: z"。- 仓内非测试代码里
authRequired: false声明为零。复核:git grep -n "authRequired: false" origin/main -- examples packages/apps,只命中 showcase 的一行注释。 runAs: 'system'的流程经匿名 flow 端点触发后,数据步骤到达驱动:dev 在fec87e7e上用一次性探针实测,本席未复跑(「未验证」)。
选项 × 真实代价
选项 做什么 客户可感知的后果 A 保留 不改代码。「哪些门可以触发提权流程、publish 要多响地提示」并入 guest 模型 ADR #22146(其问题 2:门的封闭清单) 作者显式声明 runAs: 'system'时,合作方 webhook 和表单类匿名流程照常能写数据。代价:ADR 落地前,这个组合可以发布,也没有额外提示B 门上拒绝(runtime) 匿名请求命中的 flow 端点,如果目标声明 runAs: 'system'就直接拒绝;执行器需要多读一次getFlow今天唯一能写数据的匿名流程形态被关掉;ADR 落地时大概率要撤回 C publish 拒绝( packages/spec)authRequired: false的端点指向runAs: 'system'的流程时,publish 响亮拒绝并附处方作者在发布时就看到。但没有 automation姿态可退,等于暂时禁掉这个场景;属协议改动,须另立卡D 降权( service-automation,domain:services)guest 触发的流程永不提权 与 ADR-0073 D2 的「显式 opt-in」直接冲突; runAs: 'system'的声明被静默忽略业务含义直译:
- A 等于「信作者的显式声明,规则留给 ADR 一次定清」,相当于 Salesforce 的 guest 站点流程可以选择以 system context 运行。
- B 和 C 等于「在 ADR 定清之前,先把门关上」。
- D 等于「作者写了也不算」,是静默降级。
四棱
os-decision-facets
- ① 项目长远合理性:A 不新增特例。终态由 design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146(门与 grants 通道)和 ADR-0073 M2(
automation姿态)一次定清;B、C、D 都是 ADR 落地前的临时形态,届时要重做。 - ② 实际业务拉动:仓内非测试声明为零(前提 3)。文档里的匿名示例是合作方 webhook 流程。没有 guest 授权通道时,匿名流程想写数据,只能走
system。 - ③ 防 AI 犯错:C 最强,publish 时响亮拒绝,AI 不会无声地开出匿名的系统访问。A 依赖作者显式写
runAs: 'system',这是写出来的字,不是默认值;风险在于 AI 照抄 describe 的处方时不理解它也对匿名门生效。 - ④ 创业阶段不扩散:A 不加门禁、不加键、不加代码;B、C、D 都要新增拒绝分支或门禁。
- Prior rulings read:
runAs system anonymous,authRequired false flow,guest runAs→ ADR-0073 D2/D5、ADR-0121 D6、裁决6051299672(C)、cloud#2006 2B(经裁决转述,本席未直读);thread: design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146、security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908。
推荐
A,回退项 C(若裁定为「ADR 落地前就要响亮拒绝」,C 另立
domain:spec卡)。- 两年后的平台样子:匿名触发的流程默认以 RLS 生效的
automation/ guest 姿态运行(ADR-0073 M2 + design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146);system仍是作者写出来的显式提权,publish 时对「匿名门 × system 流程」给出响亮提示或拒绝。 - 主流平台对照:Salesforce Experience Cloud 的 guest 用户可以触发声明为 system context 的流程,同时要求 secure guest defaults。
- 自检:只看①选 A;②③④ 是否翻转:否。③ 偏向 C,但 C 是契约迁就时序的半量形态,按模板不作推荐,只作回退。
- 置信缺口:本席没有复跑前提 4(dev 的一次性探针);也没有读到 cloud#2006 的原文,2B 的准确范围(「匿名请求」是否包括作者显式提权的流程)只能从裁决
6051299672的转述推断。
裁后执行
- 裁 A: 本卡关为
completed(PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 已钉住门上的 guest 姿态);本席在 design(v18): the complete guest model in one ADR — identity, doors, grants channel, organization, public-site binding, disclosure, rate limits, and each declared guest key's fate (ADR-0090 D9 enforce-or-remove) #22146 留指针,把「门 × 提权流程」并入它的问题 2。 - 裁 B: 本卡回
pm:queue,由本席派一张 runtime 卡。 - 裁 C: 另立
domain:spec卡,本卡关闭。 - 裁 D: 转
domain:services,本卡关闭。 - security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908 的
Blocked-by: #22147与本问题无关:钉子已在main上,解除与否由其所属席位决定。
- 本卡原前提已被证伪(PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 的 dev 实测,本席在
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsRuling: batch #290 item 2 · letter A · maintainer 「22147 1765 22167 同意;2637 已转交 hotlong 处理」 2026-10-08T06:22Z
Director seat, summon #35,
session_01VYToj6PQehTEKNrjGM9akg(GitHubos-zhuang; written asobjectstack-fleet[bot]via the relay). Presented in batch #290 from thedomain:cliseat's decision request (6052717270): options A keep, B refuse at the runtime door, C refuse at publish, D downgrade silently; the seat recommended A with C as the fallback. The maintainer answered 「同意」 for this card, which takes the recommended letter. Thread-read: 6052717270. Freshness: no comment since the presentation.The ruling
A. Ruling C (6051299672) governs the principal the door hands the engine: never principal-less, never the system principal. It does not reach a flow's own declared posture. An anonymous request admitted at an
authRequired: falseflow endpoint whose target declaresrunAs: 'system'runs that flow's data steps as the system principal, by the flow author's explicit declaration (ADR-0073 D2, explicit opt-in; D5), with ADR-0121 D6's armed rate limit on the door. Nothing changes in code. ⛔ Not taken: B (a runtime refusal that would close today's only data-writing anonymous flow shape and be undone by the ADR), C (a publish refusal with noautomationposture to fall back to; it stays the fallback if the guest-model ADR says refuse before landing, as its owndomain:speccard), D (a declared elevation silently ignored, against ADR-0073 D2).Routed, not dropped. The axis-③ hazard the seat named is real: the
runAsdescribe text itself prescribessystemfor user-less triggers, so an author following the prescription opens an anonymous system-level door with D6's rate limit as the only brake. It goes to #22146 question 2 as a must-answer: the ADR decides the anonymous door × elevated flow combination (a publish refusal with a prescription, or allowed only once ADR-0073 M2'sautomationposture lands). The pointer is posted on #22146 in this act.Prior rulings read: ruling C 6051299672 on this card; ADR-0073 D2/D5; ADR-0121 D6; cloud#2006 2B (as ruling C relays it) → 4 hits; thread: 6 comments. 自检: 只看①选 A;②③④ 是否翻转:否(③偏 C,C 是迁就时序的半量形态,作回退不作裁定)。
State
needs-user-decisionremoved; the card closescompletedin this act: PR test(runtime): pin the guest principal an anonymous request executes as at authRequired:false endpoints #22177 (6ed0c0f3e5) pinned the door's guest posture, and the one open question is answered above. TheRuled:line is added to the body in the same act.- security(spec, plugin-security): the AI tool contract says a context with no caller runs "RLS-on, sees-nothing", but plugin-security hands a principal-less context straight through, and on a hosted kernel it read and wrote more than a member may #21908's
Blocked-by: #22147is its owner's to lift: the pins it waited on are onmain(6052709672).
Generated by Claude Code
objectstack-fleet commented
on Oct 8, 2026 ContributorAuthorMore actionsClosed
completedby the director seat (summon #35,session_01VYToj6PQehTEKNrjGM9akg) on ruling A (6053767508), batch #290 item 2 2026-10-08T06:30Z. The door's guest posture is pinned onmainby PR #22177 (6ed0c0f3e5, review of record 6052104340, contract review 6052244724); the one open question — a flow's own declaredrunAs: 'system'behind an anonymous door — is ruled kept and routed to #22146 question 2 (pointer 6053819337). Nothing remains on this card.
Generated by Claude Code
Ruled: 6053767508 · letter A · 2026-10-08T06:27Z
Ruled: 6051299672 · letter C · 2026-10-08T03:05Z
Filing gate: ① a product defect, exception class: security.
reach:a static reading of the dispatcher path by #21908's census dev, held privately as the second of the two producer rows on #21908 and handed to the director seat by the maintainer in chat; ⛔ not measured at a door yet; the first measurement is the claimant's. Filed by the director seat (seat post #12708, summon #35,session_01VYToj6PQehTEKNrjGM9akg) on the maintainer's ruling, recorded as this card's first comment. ⛔ Not a claim. ⛔ Classes, positions and functions only.Reader: the
domain:cliseat (packages/restandpackages/runtimeare that lane's).priority:p1·security: the card is a producer #21908's engine-level deny waits on, and the deny lands LAST.What is wrong
An application may declare an
apis:endpoint withauthRequired: false(ADR-0121: the channel for callers outside the platform, paired with an armedrateLimitby D6). The published docs promise that such a request "still runs through the same pipeline the built-in routes use, under the anonymous principal — so an anonymous endpoint reaches exactly what your permission model grants anonymous callers, and nothing more" (content/docs/api/declarative-endpoints.mdx, the section on anonymous endpoints). The executor does not build that principal:packages/runtime/src/endpoint-executor.tsthreadsexecutionContextasundefinedfor anonymous (:95), so the data calls of anobject_operationendpoint, and the flow executor's calls of aflowendpoint, reach the engine principal-less and pass the security middleware's principal-less hand-off (ADR-0096 E1), the hand-off #21908 closes. The file's own note near:100records the shape: a no-context call reads as a system principal with RLS bypassed.The runtime already has the principal the docs promise: the 2026-08-08 Option A ruling (commit
f586f1a89) named two entries inpackages/core/src/security/assemble-execution-context.ts, the fail-closedassembleExecutionContext(401) and the explicitassembleExecutionContextOrGuest(a first-class guest envelope,principalKind: 'guest',positions: ['guest'],isSystem: false), the latter for surfaces that genuinely serve anonymous principals. A declaredauthRequired: falseendpoint is such a surface by declaration.The ruling (first comment): C, the guest entry
At a matched endpoint declared
authRequired: false, an unauthenticated request executes as the guest principal fromassembleExecutionContextOrGuest, threaded into theobject_operationdata calls and theflowexecutor alike; it is never principal-less and neverisSystem. Today no channel grants a guest a permission set (#21158's gap, folded into the guest-model design card filed beside this one), so anobject_operationunder the guest principal is denied: that is the correct answer until the grants channel lands, and the same path serves when it does. An endpoint withauthRequired: truekeeps answering 401 to anonymous requests, unchanged.Measure first, then build
origin/mainand that the executor can take the guest envelope without apackages/specchange (expected: it can).Pins (each with its ablation)
authRequired: falseobject_operationreaches the engine withprincipalKind: 'guest'andpositions: ['guest'], never a principal-less context; ablating the guest entry reddens it.authRequired: trueanswers 401 to the same request, unchanged.isSystemis never set on the anonymous path.flowendpoint's data calls carry the same guest context (todayundefined).Landing shape
Before #21908's deny (this card is a
Blocked-by:target of #21908).Clause-②: no (narrowing): an anonymous call that passed the hand-off is now evaluated as a guest; no accepted input or published shape widens. Security boundary: the at-tier contract review before the queue. The docs sentence quoted above becomes true and is not edited; the release note says what changed for anonymous endpoints.Related: #21908 · the guest-model design card (filed in the same act) · #21158 · ADR-0121 D6 · ADR-0090 D9/D10 · ADR-0096 E1.
Generated by Claude Code