You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 744ad0a
Browse filesBrowse the repository at this point in the historyBrowse files
fix(runtime): an API-key caller of an api action whose target is an identity route is refused by name, before the forward (#28263)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DCfTM9VrSoNVfHfD6abTWmM)_
domain:server#3 · os-elon-musk · session_016WV4d1MkJvadehWmrLUUT1
Fixes#28217
Clause-②: no (a call that was already refused now says why and is
refused before its target is called; no credential is newly accepted
anywhere, and no error code, status name, answer member or package
export is added)
Metadata-protocol: none
## 维护者速读
- 改了什么:用 API key 调用的 `type: 'api'` 动作,如果目标是身份路由(`/api/v1/auth/…`,比如 Ban
User),两个动作门(REST `/actions` 和 MCP `run_action`)在转发前就按名拒绝:401
`UNAUTHENTICATED`,说明这个端点只接受已登录会话,不接受 API key。目标不会被调用。
- 为什么改:身份路由只认自己的会话,从不认 API key,所以以前这个调用到了目标那里就成了匿名,拿回一句 "Sign in
first",而调用方其实已经认证过了。
- 没变的:身份路由接受哪些凭据不变;会话(token 或 cookie)照旧转发;API key
调其他目标(数据门、声明的端点)照旧转发并成功。
- 先看:`packages/runtime/src/action-execution.ts` 的 `forwardApiAction` 开头和
`apiActionIdentityRouteKeyRefusal`。
- 需要你做的:无;Tier S,席位验收后落地。
## What changes
`forwardApiAction` (`packages/runtime/src/action-execution.ts`), the one
forward both action doors call, now refuses one combination before it
calls the target: a caller the door admitted by API key, running an
action whose `target` is an identity route (under `/api/v1/auth/`). The
answer is `401` with the standard `UNAUTHENTICATED` code and a sentence
that names the action and its identity endpoint, says the endpoint
accepts a signed-in session only and not an API key, and says the action
was not run. Both doors relay it the way they relay a target's own
refusal: the REST door answers it, and MCP `run_action` throws it with
the same status and code.
- The identity-route reading is one private predicate,
`isIdentityRouteTarget`, which the existing `organizationId` stamp in
`buildApiActionRequest` now reads too, so the two rules cannot disagree
about which targets are identity routes.
- The sentence lives in `apiActionIdentityRouteKeyRefusal`, a module
export like the file's other refusal sentences (`@objectstack/runtime`
exports only its root entry, which does not re-export this module).
- Docs: `content/docs/ai/actions-as-tools.mdx`, the `api` row of the
dispatch table, says an API-key caller of an identity-route target is
refused `401` before the forward.
Unchanged: which credentials an identity route accepts. A signed-in
session (session token or cookie) is forwarded to it exactly as before.
An API-key caller is still forwarded to every target that is not an
identity route, and served. This does not touch #28178 (the OAuth-agent
lane), which is waiting on its own decision.
## Why
An api action's forward carries the caller's own credentials so the
target authorizes the same principal the door admitted. An identity
route reads its caller from its own signed-in session only, so a
key-admitted caller arrived there as nobody, and the target answered its
anonymous `401` ("Sign in first") to a caller that had authenticated. It
failed closed (triage 6530162284: a broken function, not a hole). Triage
ruled that refusing by name before the forward, and pinning it, needs no
decision, and that any change to which credentials an identity route
accepts does.
## Verification
Premise re-checked on `main` `86cf7cef` before the change; it held as
the card states.
- `packages/runtime/src/domains/api-action-forward.test.ts` (+5): REST
door, key-admitted caller of an identity-route action: the named `401`,
the target never called; the same action for a session-admitted caller:
forwarded as before; a key-admitted caller of a data-door target:
forwarded with the key carried across. MCP `run_action`: the
key-admitted identity-route call throws the same refusal and forwards
nothing; a key-admitted non-identity target is forwarded. 42/42.
- `packages/qa/dogfood/test/api-action-door-forward.dogfood.test.ts`
(+2, booted showcase): an admin's API key running `ban_user` gets the
named refusal, not "Sign in first", and nobody is banned; the same
admin's API key running an api action whose target is the data door is
served and the row changes. 10/10.
- Ablations: refusal removed ⇒ the two identity-route key pins red in
the unit file, and the dogfood key pin red (it answered "Sign in
first"); refusal keyed on the target alone ⇒ 13 red, the session lane
among them; keyed on the credential alone ⇒ the two non-identity key
pins red. With the fix, all green.
- `pnpm --filter @objectstack/runtime typecheck` green; the runtime
suite green (487 files, 7912 passed); the dogfood package compiles with
no error in the touched file.
- Every family `dispatch-gates --commands` derives (91) ran and exited
0, reconciled with `--ran` (0 NOT-MEASURED, after a whole-tree build);
`pnpm lint` exits 0.
Skills-docs: `grep -rniE "forward|api.?key" skills content/docs` read
for api-action forwarding and API-key callers:
`content/docs/ai/actions-as-tools.mdx` describes the forward and gains
the sentence above; no skill describes the forward or API-key callers of
actions.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes.
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed. (Not touched.)
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English (the 维护者速读 block is the
lane's maintainer summary).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4d1MkJvadehWmrLUUT1
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(runtime): an API-key caller of an api action whose target is an identity route is refused by name, before the forward
6
+
7
+
Clause-②: no (a call that was already refused now says why and is refused before its target is called; no credential is newly accepted anywhere, and no error code, status name, answer member or package export is added)
8
+
Metadata-protocol: none
9
+
10
+
A same-origin `type: 'api'` action is forwarded by the action doors (`POST /actions/:object/:action` and MCP `run_action`) with the caller's own credentials, so its target authorizes the same principal the door admitted. An identity route (under `/api/v1/auth/`, for example the `sys_user` Ban User action's target) reads its caller only from its own signed-in session and admits no API key. So a caller the door admitted by API key reached it as nobody, and got the identity route's anonymous `401` ("Sign in first") although it had authenticated.
11
+
12
+
-**Now.** Both doors refuse that call before the target is called, with `401` and the standard `UNAUTHENTICATED` code, and a sentence naming the action, its identity endpoint, and that the endpoint accepts a signed-in session only, not an API key. Nothing is run.
13
+
-**Unchanged.** Which credentials an identity route accepts. A signed-in session (session token or cookie) is forwarded to it through the door exactly as before, and an API-key caller is still forwarded to every other target (the data door, a declared endpoint) and served.
Copy file name to clipboardExpand all lines: content/docs/ai/actions-as-tools.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -113,7 +113,7 @@ through the metadata API.
113
113
|:---|:---|:---|
114
114
|`script`|`IDataEngine.executeAction(object, target, ctx)` — the same call Studio makes | open (MCP) + ObjectOS |
115
115
|`flow`| automation flow runner — `execute(target, automationContext)` (caller identity forwarded so `runAs` engages) | open (MCP) + ObjectOS; needs the `automation` service registered |
116
-
|`api`| same-origin `target` (a path starting with `/`): forwarded in-process through the action door to that target, after the door's gates, with the caller's own credentials; a credential rotation the target emits comes back as the bearer-lane headers only (`set-auth-token` / `set-admin-session-token`), never a `Set-Cookie` line | open (MCP) + ObjectOS; an `api` action whose `target` is on another origin stays browser-only and is not listed |
116
+
|`api`| same-origin `target` (a path starting with `/`): forwarded in-process through the action door to that target, after the door's gates, with the caller's own credentials; a credential rotation the target emits comes back as the bearer-lane headers only (`set-auth-token` / `set-admin-session-token`), never a `Set-Cookie` line. A caller authenticated with an API key is refused `401` before the forward when the `target` is an identity route (`/api/v1/auth/…`), which reads a signed-in session only| open (MCP) + ObjectOS; an `api` action whose `target` is on another origin stays browser-only and is not listed |
117
117
118
118
Console-only types (`url`, `modal`, `form`) are always skipped.
0 commit comments