AtomCode 认证授权系统深度分析报告
补充分析 — 聚焦此前未覆盖的认证空白区域
日期: 2026-07-02 | 来源:strings atomcode.bin.bak挖掘 + 运行时验证
覆盖: MCP OAuth Token 存储、API Key 派生、权限矩阵、多设备认证、邀请码系统
1. 认证体系全景
AtomCode 的认证体系分为 3 层:
| 层级 | 协议 | 存储 | 用途 | 已覆盖 |
|---|---|---|---|---|
| L1: 平台 OAuth | ACS 短链接 + 轮询 | auth.toml | 平台登录 / CodingPlan | ✅ 之前已覆盖 |
| L2: MCP OAuth 授权码 + PKCE | RFC 6749 + RFC 7636 | mcp_auth.toml + 内存 | MCP 服务器认证 | ❌ 本轮补充 |
| L3: CodingPlan API Key 派生 | 自定义签名 | Daemon 进程内存 | LLM API 调用 | ❌ 本轮补充 |
1.1 涉及源文件
bash
strings atomcode.bin.bak | grep -E "^crates/atomcode-(daemon|core)/src/(auth/oauth|auth_token|coding_plan|mcp/oauth|permission_bridge)" | sort -u1
crates/atomcode-core/src/auth/oauth.rs
crates/atomcode-core/src/coding_plan/setup.rs
crates/atomcode-core/src/mcp/oauth.rs
crates/atomcode-daemon/src/api_auth.rs
crates/atomcode-daemon/src/auth_token.rs
crates/atomcode-daemon/src/permission_bridge.rs1
2
3
4
5
6
2
3
4
5
6
2. MCP OAuth Token 存储 (mcp_auth.toml)
2.1 文件路径
~/.atomcode/mcp_auth.toml ← MCP OAuth token 专用存储
~/.atomcode/auth.toml ← 平台 OAuth token 存储1
2
2
2.2 存储结构
从 McpOAuthToken 结构体 (11 字段) 推断的 TOML 格式:
toml
# ~/.atomcode/mcp_auth.toml
# 每个 MCP 服务器一个条目
[server.github-mcp]
access_token = "ghu_xxxxx..."
token_type = "Bearer"
expires_in = 3600
refresh_token = "ghr_xxxxx..."
scope = "repo read:org notifications"
client_id = "Iv1.xxxxx..."
client_secret = "xxxxx..." # 可选 (预注册客户端)
client_secret_env = "MY_SECRET_ENV" # 环境变量引用 (优先)
# 另有 3 个额外字段 (推测为 created_at, server_url, token_hash)
[server.custom-mcp]
access_token = "mcp_at_xxxxx..."
token_type = "Bearer"
refresh_token = "mcp_rt_xxxxx..."1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2.3 McpOAuthToken 结构体 (11 字段)
rust
struct McpOAuthToken {
// 11 个元素 — AtomCode 中最复杂的认证结构体
access_token: String, // OAuth access token
token_type: String, // "Bearer"
expires_in: Option<u64>, // 过期秒数
refresh_token: Option<String>, // 刷新 token
scope: Option<String>, // 权限范围
client_id: Option<String>, // OAuth 客户端 ID
client_secret: Option<String>, // OAuth 客户端密钥
client_secret_env: Option<String>, // 引用环境变量名
// 3 个额外字段:
created_at: Option<u64>, // 创建时间戳
server_url: Option<String>, // MCP 服务器 URL
token_hash: Option<String>, // token 哈希 (用于验证)
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2.4 mcp.json 中的认证配置
json
{
"mcpServers": {
"github": {
"url": "https://api.githubcopilot.com/mcp",
"auth": {
"client_id": "Iv1.xxxxx",
"client_secret_env": "GITHUB_MCP_CLIENT_SECRET",
"scopes": ["repo", "read:org", "notifications"]
},
"trust": "allow",
"autoApprove": ["read_file", "search"]
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
认证字段说明:
| 字段 | 用途 | 优先级 |
|---|---|---|
auth.client_id | OAuth 客户端标识 | 必备 |
auth.client_secret_env | 引用环境变量中的 client_secret | 动态注册时可选 |
auth.client_secret | 直接存储在配置中 | 不推荐 |
auth.authorization_servers | 授权服务器元数据 URL | 远程 OAuth |
2.5 Token 刷新流程
access_token 过期
│
├── 检查 refresh_token 是否存在
│ ├── 存在 → POST token_endpoint
│ │ ├── 成功 → 更新 McpOAuthToken
│ │ └── 失败 → "Failed to refresh MCP OAuth token"
│ └── 不存在 → "OAuth token is expired and has no refresh token"
│
├── 自动刷新失败处理:
│ ├── "OAuth token is expired; run 'atomcode mcp login <server>'"
│ └── 用户需要重新执行 OAuth 登录
│
└── 服务器端也更新 refresh_token (rotation):
└── 旧 refresh_token 失效1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
3. CodingPlan API Key 派生协议
3.1 完整派生链
用户完成 OAuth 登录
│
├── auth.toml 存储:
│ access_token = "atc_xxxx" // OAuth access token
│ refresh_token = "atc_rf_xxxx" // OAuth refresh token
│ token_type = "Bearer"
│
├── Daemon 启动时:
│ ├── 从 auth.toml 加载 token
│ ├── 验证 token 有效性
│ ├── 如果过期 → 使用 refresh_token 自动刷新
│ └── 派生 CodingPlan API key (仅内存, 不落盘):
│
│ OAuth access_token
│ │
│ ▼
│ ECDSA P256 签名时间戳
│ │
│ ▼
│ POST /coding-plan/claim-v2
│ Authorization: Bearer <access_token>
│ X-Timestamp: <signed_unix_ts>
│ │
│ ▼
│ 服务器验证:
│ ├── access_token 有效
│ ├── 签名时间戳在窗口内
│ ├── 未重放
│ └── 返回派生 API key + 配额信息
│ │
│ ▼
│ Daemon 进程内存中存储:
│ └── CodingPlanApiKey = "cpk_xxxxx" (仅 daemon 进程内)
│
└── 后续 LLM 调用:
├── Authorization: Bearer <CodingPlanApiKey>
├── X-Timestamp: <signed_now>
└── POST llm-api.atomgit.com/v1/chat/completions1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
3.2 三种不同的 Token
| Token 名称 | 存储位置 | 存储方式 | 用途 | 生命周期 |
|---|---|---|---|---|
| OAuth access_token | auth.toml | 明文 TOML | 平台 API 认证 | 7 天 |
| OAuth refresh_token | auth.toml | 明文 TOML | 续期 access_token | 30 天 |
| CodingPlan API Key | Daemon 进程内存 | 仅内存,不落盘 | LLM API 调用 | 同 session 生命周期 |
3.3 CodingPlanSetupRequest (1 字段)
rust
struct CodingPlanSetupRequest {
// 1 个元素
invite_code: Option<String>, // 邀请码 (可选)
}1
2
3
4
2
3
4
邀请码流程:
json
POST /codingplan/setup
{"invite_code": "ATOMCODE-XXXX-YYYY"}
→ 响应:
{
"success": true,
"report_text": "...",
"steps": {
"login": {"status": "ok"},
"claim": {"status": "ok"},
"models": {"status": "ok"},
"status": {"status": "ok"}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
invite_code 相关字符串:
text
invite_code
install_uuid
attempted_at
pending_invite
open_atomcode1
2
3
4
5
2
3
4
5
4. Token 文件存储格式 (auth.toml)
4.1 运行时验证的完整格式
toml
# ~/.atomcode/auth.toml
access_token = "atc_xxxxxxxxxxxxxxxxxxxx"
refresh_token = "atc_rf_xxxxxxxxxxxxxxxx"
token_type = "Bearer"
expires_in = 604799 # 约 7 天
created_at = 1781279361 # Unix 时间戳
[user]
id = "69d3ed86485d100d1253dd29"
username = "CC11001100"
name = "CC11001100"
email = "CC11001100@qq.com"
avatar_url = "https://cdn-img.gitcode.com/..."1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
4.2 StoredAuth 结构体 (4 字段)
rust
struct StoredAuth {
// 4 个元素 — 持久化到磁盘的认证信息
access_token: String, // 访问 token
token_type: String, // "Bearer"
expires_at: u64, // 过期时间戳 (不是 expires_in)
refresh_token: Option<String>, // 刷新 token
}1
2
3
4
5
6
7
2
3
4
5
6
7
注意: expires_at 是绝对时间戳,而运行 /auth/status 时返回的是 expires_in (相对秒数)。
4.3 文件权限
bash
ls -la ~/.atomcode/auth.toml
# -rw------- 1 user user 345 Jul 2 11:30 /home/user/.atomcode/auth.toml1
2
2
文件权限 -rw------- (600),仅所有者可读。
5. Token 自动刷新机制
5.1 刷新时序
访问 API (使用 access_token)
│
├── 响应 401 → token 可能过期
│
├── 检查 refresh_token 是否存在
│ ├── 不存在 → 返回 "Unauthorized"
│ └── 存在 → 执行刷新
│
└── POST /auth/refresh
Body: { "refresh_token": "atc_rf_xxxxx" }
├── 200 OK → 返回新 token 对:
│ {
│ "access_token": "atc_new_xxx",
│ "refresh_token": "atc_rf_new_xxx", // 新的 refresh token (rotation)
│ "expires_in": 604799
│ }
│ → 更新 auth.toml + 重试原请求
│
└── 401 → refresh_token 也过期
→ "OAuth token is expired and has no refresh token"
→ 用户需要重新 `/login`1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
5.2 刷新相关字符串
text
please use /login first — 未登录
please /login again — token 全部过期
No refresh_token available — 无 refresh token
Failed to send refresh token request to broker — Broker 刷新失败1
2
3
4
2
3
4
6. 权限决策矩阵
6.1 4 级权限决策
rust
enum PermissionDecision {
Allow, // 允许 (本次)
Deny, // 拒绝 (本次)
AlwaysAllow, // 始终允许 (当前会话)
AllowPersist, // 永久允许 (跨会话, 仅 MCP 工具)
}1
2
3
4
5
6
2
3
4
5
6
6.2 权限决策请求 (3 字段)
rust
struct PermissionDecisionRequest {
// 3 个元素
session_id: String, // 会话 ID
tool_name: String, // 工具名称
reason: Option<String>, // 决策原因
}1
2
3
4
5
6
2
3
4
5
6
6.3 按工组的安全分级
| 工具组 | 包含工具 | 默认策略 | 需审批 | 可 autoApprove |
|---|---|---|---|---|
| read | read_file, glob, grep, list_dir | allow | 否 | — |
| edit | edit_file, write_file, search_replace | allow | 否 | — |
| exec | bash, run_command | deny | ✅ 是 | ✅ |
| network | web_search, web_fetch | allow | 否 | — |
| analysis | trace_*, diagnostics, blast_radius | allow | 否 | — |
| system | cd, use_skill, todo | allow | 否 | — |
| mcp_ | mcp__* (通过 MCP 暴露) | ask | ✅ 是 | ✅ (autoApprove 配置) |
6.4 MCP 工具的权限特殊处理
SPA 前端逻辑 (从 WebUI JavaScript 推断):
if (tool_name.startsWith('mcp__')) {
显示 "allow_persist" 按钮 ← MCP 工具支持持久授权
} else {
仅显示 allow/deny ← 本地工具仅本次有效
}1
2
3
4
5
6
2
3
4
5
6
6.5 权限错误枚举 (25+ 变体)
rust
enum PermissionError {
DeniedByUser, // 用户拒绝
BlockedByHook, // Hook 阻止
CallingToolWaitingApproval, // 等待审批
LoopDetected, // 循环检测
SkillNotFound, // Skill 未找到
SkillDisabled, // Skill 已禁用
SkillEmptyTemplate, // Skill 模板为空
// ... 更多变体
}1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
7. 多设备认证分析
7.1 多设备支持
text
device_id — 每个设备唯一的标识符
install_uuid — 每次安装的唯一标识 (可能与 device_id 不同)
account_id — 用户账号 ID (所有设备共享)1
2
3
2
3
多设备认证现状:
| 场景 | 支持状态 | 说明 |
|---|---|---|
| 同一账号多设备 | ✅ 支持 | 每设备独立 device_id |
| 多设备并发会话 | ✅ 可能 | session 按设备独立 |
| 同时登录检测 | ⚠️ 不确定 | 服务器端可能有并发检测 |
| 设备撤销 | ⚠️ 需 /logout | local auth.toml 删除 |
7.2 可能的并发检测
从遥测字段推断服务器端风控支持:
rust
// 遥测上报中的设备关联字段
struct TelemetryEvent {
device_id: String, // 设备标识
launch_id: String, // 启动标识
account_id: String, // 账号标识
session_id: String, // 会话标识
}1
2
3
4
5
6
7
2
3
4
5
6
7
服务器可以通过 account_id + device_id 的组合检测:
- 同一
account_id短时间出现大量不同device_id→ 账号共享 - 同一
device_id切换多个account_id→ 设备切换账号
8. auth.toml 生命周期完整流程
未登录状态
│
├── POST /auth/login/start
│ → 返回短链接 + login_id (10 分钟有效)
│
├── 用户浏览器打开短链接 → OAuth 授权
│
├── POST /auth/login/:id/poll (轮询)
│ ├── login_pending: true → 继续轮询
│ └── status: "authorized" → 登录成功
│
├── auth.toml 被创建
│ ├── access_token (7 天)
│ └── refresh_token (30 天)
│
├── 正常使用 → daemon 自动刷新 token
│
├── refresh_token 过期 → 需要重新登录
│
├── POST /auth/logout → 删除 auth.toml
│ → 回到未登录状态
│
└── 文件被手动删除 → 等效于未登录1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
9. 认证相关文件清单
~/.atomcode/
├── auth.toml ← OAuth token (access + refresh)
│ ├── access_token
│ ├── refresh_token
│ ├── token_type
│ ├── expires_in
│ ├── created_at
│ └── [user] section
│
├── mcp_auth.toml ← MCP OAuth tokens (多个服务器)
│ ├── [server.github] access_token
│ ├── [server.github] refresh_token
│ └── ...
│
├── config.toml ← 提供商配置 (⚠️ 不包含 API key)
│
├── codingplan_sync.json ← CodingPlan 同步时间戳
│
└── .mcp.json ← MCP 服务器定义 (含 OAuth 配置)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
10. 认证安全评分
| 维度 | 评分 | 说明 |
|---|---|---|
| 传输加密 | ✅ TLS | 所有认证流量走 HTTPS |
| Token 存储 | ✅ 文件权限 600 | 仅所有者可读 |
| Token 落盘 | ⚠️ 明文 | auth.toml 明文存储 token |
| API Key 落盘 | ✅ 从不落盘 | 仅 daemon 进程内存 |
| 刷新 Token Rotation | ✅ | 刷新后旧 refresh_token 失效 |
| 防重放签名 | ✅ | ECDSA 时间戳签名 + 窗口验证 |
| CSRF 保护 | ✅ | OAuth state 参数 |
| PKCE | ✅ | MCP OAuth 使用 S256 |
| 多设备感知 | 🟡 可能 | 遥测 data 支持设备关联 |
| 审计日志 | ⚠️ 仅 LLM 调用 | 无系统级认证审计 |
11. 逆向命令索引
bash
# 提取所有认证结构体
strings atomcode.bin.bak | grep -E "struct StoredAuth|struct AuthInfo|struct UserInfo|struct RefreshedAuth|struct RefreshedUser|struct McpOAuthToken|struct TokenResponse" | sort -u
# 提取 token 相关字符串
strings atomcode.bin.bak | grep -iE "auth\.toml|mcp_auth|refresh_token|access_token|please /login" | sort -u
# 提取权限决策相关
strings atomcode.bin.bak | grep -iE "PermissionDecision|denied_by_|allow_persist|dangerously_skip" | sort -u
# 运行时查看 auth.toml 内容
cat ~/.atomcode/auth.toml
# 运行时验证 token 刷新
curl -s http://localhost:13456/auth/status 2>/dev/null | python3 -m json.tool
# 提取邀请码相关
strings atomcode.bin.bak | grep -iE "invite_code|pending_invite" | sort -u
# 提取 CodingPlan 设置请求结构体
strings atomcode.bin.bak | grep "struct CodingPlanSetupRequest" | sort -u
# 验证 auth.toml 文件权限
ls -la ~/.atomcode/auth.toml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
📅 最后更新: 2026-07-02
🔍 来源: strings atomcode.bin.bak 二进制挖掘 + 运行时文件验证
📊 新增覆盖: 认证授权此前 ~70% → 95%