真实鉴权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.
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.
resource_access_policy=
ResourceAccessPolicyBase
选填optional
决定调用方能用到哪些被共享的资源,多租户影响面由此收口。Decides which shared resources a caller may use. The multi-tenant blast radius lives here.
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
# 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
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 0the 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 200policy wired: auditor reads owner-b's agent with editable=False, PATCH -> 403 for the auditor and 200 for the ownerthe 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.