跳到主要内容

高危操作核验

在线演示

体验高危操作 Step-up

输入 DeviceId,模拟一笔转账的 Step-up 核验。

1
输入 DeviceId,发起请求
2
真人 PalmxApp 刷掌
3
拿 Assertion JWT

⚡ 演示沙箱,数据不持久化。

身份验证只解决「是不是这台 Agent」,不代表真人同意了这一笔操作。转账、提现等高危动作必须走 Step-up,让真人当场刷掌。

Step-up Level

Level含义行为
1仅异步告知立即 approved + 立即签 Assertion JWT(同时推送通知真人)
2 / 3真人必须刷掌确认pending,等真人在 PalmxApp 刷掌 approved / denied 后才出 JWT

Step 1 · 业务方发起核验

POST /palm/openai/x/agent/stepup/create
Authorization: Bearer <api_key>
Content-Type: application/json

{
"DeviceId": "trader-001",
"ChallengeId": "550e8400-e29b-41d4-a716-446655440000", // 你生成的 UUID v4,幂等键
"Action": "transfer_money",
"ActionDetail": "{\"amount\":\"1000 USD\",\"to\":\"alice\"}", // 透传 JSON,≤512B,会展示给真人
"Level": 3,
"NonBlocking": false
}

响应(Level 2/3):

{
"code": 0,
"data": {
"ChallengeId": "550e8400-...",
"ExpiresAt": 1717488300,
"Status": "pending"
}
}

响应(Level 1,立即放行): Status: "approved" 且直接带 AssertionJwt

幂等:同一 ChallengeId 重复提交不会创建新核验,返回已有状态。

Step 2 · 真人 PalmxApp 刷掌

服务端推送通知到真人 owner 的 PalmxApp,真人看到操作详情(ActionDetail)后刷掌确认或拒绝。这一段由 PalmxApp 完成,业务方无需实现。

Step 3 · 业务方轮询拿 Assertion JWT

POST /palm/openai/x/agent/stepup/query
Authorization: Bearer <api_key>
Content-Type: application/json

{ "ChallengeId": "550e8400-..." }

响应:

{
"code": 0,
"data": {
"Status": "approved", // pending / approved / denied / expired
"AssertionJwt": "eyJhbGciOiJIUzI1Ni...." // approved 时一次性返回
}
}
Status含义业务方动作
pending等待真人确认继续轮询(建议 2s)
approved真人已确认取出 AssertionJwt完成校验后执行业务
denied真人拒绝停止,拒绝该操作
expired超时未响应停止,提示超时可重试

Step 4 · 校验 Assertion JWT(放行前必做)

Assertion JWT 是放行高危操作的最终凭证,算法 HS256,密钥为 assertion_jwt.secret(平台下发,env-only)。

JWT Payload:

{
"iss": "trpc.x.oauth.OAuth",
"aud": "agent:{app_id}:{device_id}", // 受众:本次核验关联的 Agent
"sub": "{owner_open_id}", // 真人 owner
"iat": 1717488000,
"exp": 1717488300, // 默认 +5min
"jti": "{uuid v4}", // 防重放
"act": "transfer_money", // action
"adh": "sha256_hex(action_detail)", // action_detail 哈希
"lvl": 3,
"chl": "{challenge_id}"
}

业务方必须完整校验(顺序很重要):

def execute_dangerous_action(jwt_str, app_id, device_id, action, action_detail_bytes):
# 1. 验签 + 解析(自动校 exp)
try:
claims = jwt_decode(jwt_str, secret=ASSERTION_SECRET,
algorithms=["HS256"], options={"verify_aud": False})
except (InvalidSignature, ExpiredToken):
return reject("invalid assertion")

# 2. 校 typ(JWT header)
if jwt_header(jwt_str).get("typ") != "palmauth-assertion+jwt":
return reject("wrong typ")

# 3. 校 aud 指向预期的 agent
if claims["aud"] != f"agent:{app_id}:{device_id}":
return reject("aud mismatch")

# 4. 校 action 完全等于本次操作
if claims["act"] != action:
return reject("action mismatch")

# 5. 校 action_detail 没被篡改 ←← 防中间人篡改金额/收款人的核心检查
# 必须用收到 Agent 请求时的原始 bytes 算 hash,不要信任 JWT 解出后再序列化的 JSON
if claims["adh"] != sha256_hex(action_detail_bytes):
return reject("action_detail tampered")

# 6. jti 防重放(同一 JWT 不能用第二次)
if redis.set_nx(f"used:{claims['jti']}", "1", ex=300) is False:
return reject("replayed")

do_transfer(...) # 通过!执行业务

⚠️ 第 5 步最关键:必须用收到 Agent 请求时的原始 action_detail 字节算 hash,防止金额 / 收款人被篡改。

⚠️ JTI 去重是业务方的责任:JWT 一旦发出,5min TTL 内若不做去重,攻击者抓到可反复使用。

⚠️ fail-closed:Palm 不可用时高危操作必须拒绝,不能放行。