Skip to content

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

Closed

Description

@objectstack-fleet

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:cli seat (packages/rest and packages/runtime are 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 with authRequired: false (ADR-0121: the channel for callers outside the platform, paired with an armed rateLimit by 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.ts threads executionContext as undefined for anonymous (:95), so the data calls of an object_operation endpoint, and the flow executor's calls of a flow endpoint, 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 :100 records 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 in packages/core/src/security/assemble-execution-context.ts, the fail-closed assembleExecutionContext (401) and the explicit assembleExecutionContextOrGuest (a first-class guest envelope, principalKind: 'guest', positions: ['guest'], isSystem: false), the latter for surfaces that genuinely serve anonymous principals. A declared authRequired: false endpoint 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 from assembleExecutionContextOrGuest, threaded into the object_operation data calls and the flow executor alike; it is never principal-less and never isSystem. Today no channel grants a guest a permission set (#21158's gap, folded into the guest-model design card filed beside this one), so an object_operation under 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 with authRequired: true keeps answering 401 to anonymous requests, unchanged.

Measure first, then build

  1. Reach: on a booted showcase with a throwaway declared endpoint (not committed) of each executable type, what an anonymous request reaches today at each of the six security gates and at the driver; by class, privately if it is an exploit recipe.
  2. Confirm the two entries' contract on origin/main and that the executor can take the guest envelope without a packages/spec change (expected: it can).

Pins (each with its ablation)

  • An anonymous request at a declared authRequired: false object_operation reaches the engine with principalKind: 'guest' and positions: ['guest'], never a principal-less context; ablating the guest entry reddens it.
  • With no permission set bound to the guest, the operation is denied, not served, and nothing of the row is disclosed.
  • The same endpoint declared authRequired: true answers 401 to the same request, unchanged.
  • isSystem is never set on the anonymous path.
  • A flow endpoint's data calls carry the same guest context (today undefined).

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

Activity

  1. added
    bugSomething isn't working
    priority:p1High: required for production / M2
    pm:queueReady for the PM dispatch loop
    domain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they land
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    on Oct 8, 2026
  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #288 item 1 · letter C · maintainer 「同意 p1」 2026-10-08T03:02Z

    Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (GitHub os-zhuang; written as objectstack-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: false executes as the guest principal that assembleExecutionContextOrGuest builds (principalKind: 'guest', positions: ['guest'], isSystem: false; the 2026-08-08 Option A entry), threaded into the object_operation data calls and the flow executor 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: true keeps 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


    Generated by Claude Code

  3. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Oct 8, 2026
  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 7
    Session: session_01RWZbGvPFcRKvUqASZtunCU
    Account: os-warren (the seat's linked user as get_me answers 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 ruling 6051299672 (C, the guest entry), read on origin/main fec87e7e:

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-08T03:22Z

  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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)"
    ]
    }

  6. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #22177 at 49546b0c, pending CI, the seat's self-test and the contract review

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · read on GitHub 2026-10-08T04:16Z

    The 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.ts calls HttpDispatcher.resolveRequestScope.
    • That calls timedResolveExecutionContext → resolveExecutionContext (packages/runtime/src/security/resolve-execution-context.ts), which returns assembleExecutionContextOrGuest(...).
    • 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: false endpoint is handed the guest envelope (principalKind: 'guest', positions: ['guest'], isSystem: false), not undefined. The static reading the card rests on came from a doc comment in endpoint-executor.ts that said "undefined for anonymous".
    • Ruling C's posture therefore already holds on main at 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 is Clause-②: no;
      • assignee os-warren;
      • 5 files, +403 / −7.
      • The order asked for Fixes. Part of is accepted: the card keeps an open question (below), so merging must not close it.
    • 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 on AppEndpointExecutionInput.executionContext.
      • dispatcher-plugin.ts: one comment above resolveRequestScope names it as the one place the guest is built.
      • Each new sentence is true of the path the seat read above.
    • Pins:
    • 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-changeset in this stroke.

    • AGENTS.md (Post-Task Checklist step 3): "that label is for a diff that publishes nothing from any released package".
    • @objectstack/runtime publishes dist, README.md and CHANGELOG.md only (files). The seat read the published @objectstack/runtime 17.7.0 tarball: the OLD text of all three changed comments is absent from its dist/. 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:

    1. A flow endpoint whose target declares runAs: 'system'. An anonymous request at an authRequired: false flow endpoint runs that flow's data steps as the system principal, by the flow's own declaration.
    2. Changeset: answered above, with skip-changeset.
    3. 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.

    Out-of-scope findings, routed at landing:

    Evidence (the dev's, at 49546b0c):

    • runtime local tier: 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 lint exits 0.

    The seat's own: the new dogfood file adds a mkdtempSync site (standing lesson 1). node scripts/pm/dispatch-gates.mjs --self-test at 49546b0c is 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_TIER before the queue, as a security boundary. needs:contract-review is hung on PR #22177 in this stroke, and an independent read-only reviewer reviews 49546b0c.

  7. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: claim 6051510741 (domain:cli seat, 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 declared runAs: '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 says Part of #22147.

    Landed: PR #22177 → 6ed0c0f3e5, a single-parent queue squash

    domain:cli seat · session_01RWZbGvPFcRKvUqASZtunCU · 2026-10-08T05:05Z

    • Landing shape:
    • Content on origin/main:
      • packages/runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.ts carries the four socket pins;
      • packages/qa/dogfood/test/declarative-endpoint-anonymous-guest.dogfood.test.ts is 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 6052104340 at 49546b0c.
      • Contract review PASS 6052244724 (CONTRACT_REVIEW_TIER, same head, independent and read-only), as the card's landing shape asked; needs:contract-review was stripped before the queue.
      • CI on the head was green before the PR was armed.
      • The seat's dispatch-gates --self-test passed at the head (1976 cases) for the new temp-directory site.
    • What holds on main, by class:

    Routed in this stroke:

  8. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    待裁:匿名端点触发声明 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 读码复核,见 ACCEPT 6052104340)。
    • 剩下的唯一问题来自 flow 端点。
      • 请求本身以 guest 到达:自动化服务收到 positions: ['guest'],无用户、无 isSystem。
      • 如果目标流程声明 runAs: 'user',它的数据步骤会因「无用户」被失败关闭(400 FLOW_FAILED)。
      • 如果目标流程声明 runAs: 'system',它的数据步骤以系统主体运行,绕过对象权限和 RLS。这是流程作者的显式选择,由 service-automation 读取执行。
    • 裁决 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 须单独立卡,不作本卡的执行项。

    前提(各带复核命令)

    1. 匿名门交出 guest。复核:git grep -n "assembleExecutionContextOrGuest" origin/main -- packages/runtime/src/security/resolve-execution-context.ts(有命中即成立)。
    2. runAs 只有 system / user。复核:git show origin/main:packages/spec/src/automation/flow.zod.ts | grep -n -A2 "runAs: z"。
    3. 仓内非测试代码里 authRequired: false 声明为零。复核:git grep -n "authRequired: false" origin/main -- examples packages/apps,只命中 showcase 的一行注释。
    4. 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,回退项 C(若裁定为「ADR 落地前就要响亮拒绝」,C 另立 domain:spec 卡)。

    裁后执行

  9. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    and removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 8, 2026
  10. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling: batch #290 item 2 · letter A · maintainer 「22147 1765 22167 同意;2637 已转交 hotlong 处理」 2026-10-08T06:22Z

    Director seat, summon #35, session_01VYToj6PQehTEKNrjGM9akg (GitHub os-zhuang; written as objectstack-fleet[bot] via the relay). Presented in batch #290 from the domain:cli seat'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: false flow endpoint whose target declares runAs: '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 no automation posture to fall back to; it stays the fallback if the guest-model ADR says refuse before landing, as its own domain:spec card), D (a declared elevation silently ignored, against ADR-0073 D2).

    Routed, not dropped. The axis-③ hazard the seat named is real: the runAs describe text itself prescribes system for 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's automation posture 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


    Generated by Claude Code

  11. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Closed completed by 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 on main by PR #22177 (6ed0c0f3e5, review of record 6052104340, contract review 6052244724); the one open question — a flow's own declared runAs: '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

  12. removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:cliRetired 2026-10-22, alias of domain:server (cli surface); in-flight cards keep it until they landpriority:p1High: required for production / M2security

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions