AS AgentScopeAPI 手册 / API Reference
MODULE C8 · gatekeeper · 220 XP · 35 MIN

真实鉴权Real authentication

先说问题The problem

打开 agentscope/app/deps.py,身份来源写着:x_user_id = Header(...),注释自己写着「Temporary header-based identity; will be replaced by JWT auth」。也就是说 2.0.11 的服务里,任何人在请求头写 X-User-ID: 别人 就能读别人的会话。这不是假想威胁,是源码里的 TODO。

Open agentscope/app/deps.py and identity is x_user_id = Header(...), with a comment that reads 'Temporary header-based identity; will be replaced by JWT auth'. So in 2.0.11 anyone can put another user's id in a header and read their sessions. This is not a hypothetical threat; it is a TODO in the source.

先动手Do it first

先在页面上接线——页面只懂规则。然后跑下面那条判题,那才是真会失败的东西。Wire it on the page — the bench only knows the rules. Then run the check below; that is the part that can actually fail.

入口闸门Entry gates extra_middlewares= list[Middleware] 选填optional

ASGI 中间件:每个请求在 agent 存在之前都要经过的这一层。身份要在这里被校验,而不是被相信——这也是伪造 X-User-ID 唯一能被拦下的位置。ASGI middlewares, the layer every request passes through before an agent exists. This is where identity gets verified instead of believed — the one place a forged X-User-ID can be stopped.

Empty: fine for now空着:以后再插也行
资源访问策略Access policy resource_access_policy= ResourceAccessPolicyBase 选填optional

决定调用方能用到哪些被共享的资源,多租户影响面由此收口。Decides which shared resources a caller may use. The multi-tenant blast radius lives here.

Empty: fine for now空着:以后再插也行
你拼出来的东西What you assembled
create_app(…)

为什么这样设计Why it is built this way

01先确认边界Confirm the boundary first

本地单用户跑没关系。一旦监听非 127.0.0.1,就必须假设任何人都能冒充任何用户。

Local single-user runs are fine. The moment you bind beyond 127.0.0.1, assume anyone can impersonate every user.

02中间件补身份层Middleware supplies the identity layer

create_app 接受 extra_middlewares:在这一层校验 JWT / session cookie,把可信的 user id 注入,而不是信任客户端头。

create_app takes extra_middlewares. Verify JWT or a session cookie there and inject a trusted user id, instead of trusting a client-supplied header.

03越权测试当验收Cross-tenant probes are the acceptance test

断言:用户 A 拿自己的 token 访问用户 B 的 session_id,必须 403/404。这条断言在 C8 之前必然失败——那就是这个模块存在的理由。

Assert that user A, with a valid token, hitting user B's session_id gets 403/404. Before C8 that assertion must fail — which is the whole reason this module exists.

用到的 APIAPIs in play

代码The code

C8 · Python
# create_app takes ASGI middlewares; the identity layer is one of them
app = create_app(..., extra_middlewares=[Middleware(TrustedIdentityMiddleware)])

class TrustedIdentityMiddleware:                 # plain ASGI: (app, scope, receive, send)
    async def __call__(self, scope, receive, send):
        verified = TOKENS.get(_bearer(scope))     # 401 when the token is missing or unknown
        if verified is None:
            return await _reject(send, 401, ...)
        scope["headers"] = [...("x-user-id", verified)]   # overwrite the client's claim
        await self.app(scope, receive, send)

# now the header a client typed is discarded; deps.get_current_user_id reads what you wrote
assert client_b.get(f"/sessions/{session_of_a}/messages").status_code == 404
assert (await policy.can_edit("auditor", ResourceKind.AGENT, "owner-b", "agent-1", None)) is False

判题The check

<span class="ui-zh">运行这条</span><span class="ui-en">Run this</span>
python lab.py run c8

它断言了什么What it asserts

  • X-User-Id alone, no token: read -> 200 with 1 victim row(s), PATCH -> 200; an unshared id gets 0
  • the framework says so itself: Return the caller's user ID from the ``X-User-ID`` request header.
  • tokenless X-User-Id -> 401, rejected claim recorded as 'owner-b'
  • valid token for owner-a + header claiming owner-b -> the header is discarded, the victim's session route reads back 404 where the owner gets 200
  • policy wired: auditor reads owner-b's agent with editable=False, PATCH -> 403 for the auditor and 200 for the owner
  • the share list is per-owner: owner-a sees 0 agent(s), not owner-b's

会踩的坑Where it hurts

  • 把服务直接部署到公网「先内部用用」。X-User-ID 可伪造 + 沙箱能执行命令,等于把远程代码执行挂在了一个不需要密码的端口上。

    Deploying the service publicly because it is 'internal for now'. A forgeable X-User-ID plus a sandbox that executes commands is remote code execution on a passwordless port.

费曼闸门Say it back

为什么框架宁可留一个可伪造的身份头,也不在核心里塞一套认证?这个取舍在什么场景下会伤害你?

Why would a framework ship a forgeable identity header instead of baking in auth? In what setting does that trade-off hurt you?

页面只存通行码的哈希。判题没真跑过,这里就解不开。The page stores a hash, not the code. Nothing unlocks until the command really ran.

基于官方固定版本源码静态生成Generated from pinned official sources · Python 2.0.11 · Java 2.0.4
签名与文档为静态抽取;如有疑问请以源码为准。Signatures and docs are extracted statically; verify against the source when in doubt.