高危操作核验
在线演示
体验高危操作 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 不可用时高危操作必须拒绝,不能放行。