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

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
    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. 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

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

  5. 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.

  6. 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:

  7. 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 卡)。

    裁后执行

  8. 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

  9. 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

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:clipriority: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