Skip to content

fix(security): 按高危顺序修复 9 个安全 issue (#7#16#20#23#17#22#26#24#27)#29

Merged
Ink-dark merged 11 commits into
mainfrom
fix/security-high-priority
Jul 10, 2026
Merged

fix(security): 按高危顺序修复 9 个安全 issue (#7#16#20#23#17#22#26#24#27)#29
Ink-dark merged 11 commits into
mainfrom
fix/security-high-priority

Conversation

@Ink-dark

Copy link
Copy Markdown
Owner

概述

本 PR 按高危优先级顺序修复 9 个安全 issue:4 个 CRITICAL + 5 个 HIGH。每个 issue 一个独立 commit,commit message 均带工单号(fix(#N))并附验证通过的证明(测试输出)。本 PR 仅提交待评审,未合并。

修复清单(按高危顺序)

CRITICAL

# Issue 问题 修复
1 #7 命令超时只 kill 主进程,孤儿子进程继续运行(僵尸/资源泄漏) run_with_timeout()process_group(0) + killpg(SIGKILL) 杀整个进程组;新增测试验证孙进程确被杀死
2 #16 HTTP server 无认证,任意本机进程可读写任务/记忆 HttpServer::with_auth_token(Option<String>),支持 Bearer / Cookie / ?token= 三种凭证;None 保持旧行为(向后兼容)
3 #20 运行时白名单只匹配 basename,批准 src/main.rs 会覆盖 tests/main.rs(越权写/删) fingerprint_path() 改用完整规范化相对路径(../. 归一化),杜绝同名文件越权
4 #23 CORS 设为 Access-Control-Allow-Origin: *,任意网站可跨域读取 API 移除 cors() 与所有 .with_header(cors()),回归默认同源策略

HIGH

# Issue 问题 修复
5 #17 Unix socket 用默认 umask(常 0755/0777),同机其他用户可连入 bind 后 set_permissions(0o600),失败则清理 socket 并报错
6 #22 审批 action_id 用 SystemTime 纳秒 + AtomicU64 计数器,可推算 → 伪造 ApprovalResponse 越权 改用 uuid::Uuid::new_v4()(CSPRNG),不可预测
7 #26 IPC TCP 后端无认证/加密,任意本机进程可注入伪造触发/审批、读取全量流量 TCP 共享密钥握手:客户端首行 AUTH <secret>\n,服务端定长比较(constant_time_eq 防 timing attack)通过才进 JSON-line;Unix socket 靠 0600 保护跳过握手
8 #24 Feishu adapter 三个 Map 只增不减,超时审批/孤儿卡片永不清理 → OOM 新增 TTLMap(TTL 1h + LRU maxSize 5000 + 节流 cleanup),替换三个无界 Map
9 #27 API 错误响应 {"error":"{e}"} 泄露文件路径/DB 路径/模块结构给客户端 改返回 {"error":"internal_error","correlation_id":"<uuid v4>"},详细错误记服务端日志

设计原则

验证证明

完整 workspace 测试(cargo test --workspace --features orcha-core/llm),全绿:

orcha-cli      unittest:  20 passed | cli集成:  20 passed
orcha-core     unittest: 223 passed | m2: 6 | m3: 3 | state: 12
orcha-gateway  unittest:  67 passed
orcha-llm      unittest:  46 passed
orcha-sdk      models:    14 passed
orcha-shell    unittest:  13 passed
全部 0 failed

TS 端(#24/#26):npx tsc --noEmit 无错误;#24 TTLMap 用临时脚本跑 6 组运行时断言(TTL 过期 / LRU 淘汰 / cleanup / 超时审批孤儿映射清理)全通过(脚本验证后已删除,输出见 commit b5dc7dd message)。

各 commit 的 message 内附该 issue 对应的测试输出作为单独验证证明。

分支情况

  • 写代码前检查:本地仅 main(跟踪 origin/main,已同步)+ 本工作分支;远端仅 origin/main
  • 无未使用分支需删除main 为上游跟踪分支保留)。
  • 工作分支 fix/security-high-priority:9 commits,均带工单号。

涉及文件

Cargo.lock                                      |   2 +
packages/orcha-cli/src/main.rs                  |  12 +-
packages/orcha-core/Cargo.toml                  |   4 +
packages/orcha-core/src/tools.rs                | 169 +++++--
packages/orcha-feishu-adapter/src/feishu.ts     |  35 +-
packages/orcha-feishu-adapter/src/ipc.ts        |  25 +-
packages/orcha-feishu-adapter/src/main.ts       |  12 +
packages/orcha-feishu-adapter/src/ttl-map.ts    | 118 +++++ (新)
packages/orcha-gateway/Cargo.toml               |   2 +
packages/orcha-gateway/src/approval_hook.rs     |  39 +-
packages/orcha-gateway/src/config.rs            |  22 +
packages/orcha-gateway/src/ipc.rs               | 373 ++++++++++++++++
packages/orcha-gateway/src/lib.rs               |  26 +-
packages/orcha-gateway/src/runtime_whitelist.rs |  94 ++--
packages/orcha-shell/Cargo.toml                 |   2 +
packages/orcha-shell/src/server.rs              | 352 ++++++++++++++++--
16 files changed, 1208 insertions(+), 79 deletions(-)

注意事项

Closes #7, #16, #20, #23, #17, #22, #26, #24, #27.

@Ink-dark Ink-dark force-pushed the fix/security-high-priority branch 2 times, most recently from 0203f4c to a6e9b41 Compare July 10, 2026 01:43
Ink-dark added 9 commits July 10, 2026 01:49
旧实现 execute_run_command 超时后只 kill 直接子进程,子进程 fork 出
的孙进程(如 `sh -c "sleep 30 & wait"` 的 sleep)成为孤儿继续运行,
导致僵尸进程、端口/文件占用、超时形同虚设。

修复:
- tools.rs: 新增 run_with_timeout() helper,用 CommandExt::process_group(0)
  把子进程放入独立进程组,超时后 killpg(SIGKILL) 杀整个进程组(#7)。
  Unix 用 libc::killpg,其他平台 fallback child.kill()。
- Cargo.toml: [target.'cfg(unix)'.dependencies] 加 libc = "0.2"(仅 Unix)。
- execute_run_command 改用 run_with_timeout。
- 新增测试 run_with_timeout_kills_entire_process_group_on_unix:
  #[cfg(unix)] 编译期门控(依赖 libc::kill 和 sh,Windows 不编译),
  起 sh → sleep 30 孙进程,1s 超时后验证孙进程 pid 已被 killpg 杀掉。

  注:测试原用运行时 if cfg!(windows) { return } 门控,导致 libc::kill
  在 Windows 上仍被编译而报 E0433(libc 是 Unix-only 依赖)。改为
  #[cfg(unix)] 属性门控,整个测试在 Windows 上不编译。

验证:
$ cargo test -p orcha-core --features llm --lib
test result: ok. 223 passed; 0 failed; 0 ignored
$ cargo clippy -p orcha-core --features llm --all-targets -- -D warnings
Finished (exit 0)
CRITICAL: orcha-shell serve() 在 127.0.0.1 上零认证,任意本地用户/进程
均可读 /api/tasks/:id/memory(含 LLM 记忆与机密)等敏感端点;配合 CORS *
(#23)还可能被 DNS-rebinding 攻击。

修复(向后兼容,默认行为不变):
- HttpServer 增加 auth_token: Option<String>,通过 with_auth_token() 设置;
- 设置后 handle() 对所有请求校验三种凭证之一:
  Authorization: Bearer <token> / Cookie orcha_token=<token> / ?token=<token>,
  任一匹配放行,否则返回 401 {"error":"unauthorized"};
- ?token= 一次性引导:响应种 HttpOnly;SameSite=Strict Cookie,浏览器后续
  AJAX 自动携带,保持 Web UI 可用;
- CLI `orcha shell` 新增 --token(env ORCHA_SHELL_TOKEN),透传给 HttpServer;
- 未设置 token 时保持旧行为(无认证,单用户本机场景)。

验证证明(cargo test -p orcha-shell -p orcha-cli):
  orcha-shell: 10 passed; 0 failed(含 6 个新增认证辅助测试:
   auth_ok_accepts/rejects、bearer_token_parses、cookie_value_extracts、
   parse_query_value_decodes、http_server_with_auth_token_stores_token)
  orcha-cli: 20 passed; 0 failed
  cargo build --workspace --all-targets 通过。

Refs: #16
旧 fingerprint_path() 仅取 basename 做 key,批准 src/main.rs 后
tests/main.rs、vendor/main.rs 等同名文件均命中白名单直接放行,
导致越权写/删任意目录的同名文件。

修复:
- runtime_whitelist.rs: fingerprint_path() 改用 normalize_relative()
  生成完整规范化相对路径(统一分隔符为 '/'、消去 '.'、解析 '..',
  越界 '..' 丢弃)作为 fingerprint,杜绝同名文件越权。
- 替换既有 basename 相关测试为 full_relative_path 版本,新增
  regression: whitelist_does_not_over_match_same_basename_different_dir
  (批准 src/main.rs 后写 tests/main.rs 应被拒)。

顺带:同 crate 内 queue.rs fallback_commit_info 的 format! 多余
取址(clippy::useless_borrows_in_formatting,CI clippy 1.97 -D warnings
阻断),&slug → slug,让 CI 绿。此处非 #20 引入,属预存 lint 清理。

验证:
$ cargo test -p orcha-gateway --lib
test result: ok. 58 passed; 0 failed; 0 ignored
$ cargo clippy -p orcha-gateway --all-targets -- -D warnings
Finished (exit 0)
CRITICAL: cors() 给所有响应加 Access-Control-Allow-Origin: *,配合无认证
(#16 已修)使得恶意网页可借受害者浏览器跨域 fetch 127.0.0.1:7421 的
/api/tasks/:id/memory 等敏感端点,构成 DNS-rebinding / 数据外泄。

修复:
- 删除 cors() helper 及其在 asset()/api_json() 的全部调用;
- 不再下发任何 Access-Control-Allow-Origin 头,浏览器默认同源策略即可
  阻断跨域读取;同源 Web UI 的 AJAX 不受影响。

验证证明(cargo test -p orcha-shell):
  test result: ok. 11 passed; 0 failed
  含 api_responses_do_not_carry_cors_star_header:真实 TCP 往返,
   断言 /api/tasks 响应体不含 access-control-allow-origin 头。

Refs: #23
HIGH: UnixTransport::bind() 用默认 umask 创建 socket 文件(常为 0755/0777),
同机其他用户可连入 Gateway IPC,伪造触发/绕过审批/注入消息,等价于 TCP 无认证。

修复:
- bind 后立即 std::fs::set_permissions(path, 0o600);
- 设权限失败则删除残留 socket 文件并返回错误,避免遗留可连 socket。

验证证明(cargo test -p orcha-gateway --lib):
  test result: ok. 59 passed; 0 failed
  含 unix_socket_permissions_are_restricted:断言 bind 后 socket 文件
   mode & 0o777 == 0o600。

Refs: #17
旧实现 uuid_v4_simple() 用 SystemTime 纳秒 + AtomicU64 计数器拼接,
攻击者拿到 IPC 访问权即可推算后续 action_id,伪造 ApprovalResponse
越权批准/拒绝任意审批。改用 uuid crate 的 Uuid::new_v4()(底层
getrandom CSPRNG),不可预测。

变更:
- packages/orcha-gateway/Cargo.toml: 引入 uuid.workspace = true
  (workspace 已有 v4+serde feature)
- approval_hook.rs: uuid_v4_simple() 改为 uuid::Uuid::new_v4().to_string()
- 新增测试 uuid_v4_simple_is_cryptographically_random_v4:断言
  version 位为 Random、1000 次不碰撞、标准 8-4-4-4-12 格式

验证:
$ cargo test -p orcha-gateway --lib
test result: ok. 60 passed; 0 failed; 0 ignored
(含新增 uuid_v4_simple_is_cryptographically_random_v4 与既有
  uuid_v4_simple_is_unique / approval 流程测试全绿)
TCP 后端(默认 Windows、Unix 可配)原无任何认证,任意本机进程可连入
伪造触发消息启动任意任务、伪造 approval_response 绕过人工审批、读取
全量 IPC 流量(含代码/配置)。Unix socket 已由 #17 的 0600 权限保护,
本提交补齐 TCP 后端认证。

设计(向后兼容 Option 模式,与 #16 一致):
- config.rs: IpcConfig 增 tcp_secret: Option<String>,并提供
  effective_tcp_secret()(config 字段优先,否则查 ORCHA_IPC_TCP_SECRET
  环境变量)。None = dev 模式不认证(Gateway 启动打警告)。
- ipc.rs: IpcListener::accept_authenticated(secret) —— TCP 连接 accept
  后读首行,必须为 `AUTH <secret>`,定长比较(constant_time_eq 防
  timing attack)通过才进入 JSON-line 协议;不匹配/缺失立即断开
  (PermissionDenied)。Unix socket 跳过握手(0600 已保护)。
  AUTH 行限长 512B 防恶意大行耗内存。
- ipc.rs: connect_stream_authenticated() 客户端对应,TCP 连上后先发
  `AUTH <secret>\n`。
- lib.rs: accept_loop 改用 accept_authenticated,握手失败(密钥不匹配)
  与 accept 失败分别打日志。
- ipc.ts (TS): IpcClientOptions 增 tcpSecret,TCP connect 后在心跳/业务
  消息之前先写 `AUTH <secret>\n`。Unix socket 忽略。
- main.ts: 从 ORCHA_IPC_TCP_SECRET 读密钥传入 IpcClient。

验证(Rust):
$ cargo test -p orcha-gateway --lib
test result: ok. 67 passed; 0 failed; 0 ignored
新增 #26 握手测试 7 个:
  tcp_handshake_succeeds_with_correct_secret
  tcp_handshake_rejects_wrong_secret
  tcp_handshake_rejects_missing_auth_line
  tcp_handshake_allows_when_no_secret_configured
  unix_handshake_is_noop_regardless_of_secret
  constant_time_eq_correctness
  read_line_with_limit_rejects_oversized_line

验证(TS):
$ npx tsc --noEmit   # 无错误输出

注:本提交一并提交 Cargo.lock 中 #7(libc) 与 #22(uuid) 的依赖项条目
(这两个 commit 当时漏提交 lockfile,均为已提交代码的依赖解析结果)。
HttpFeishuClient 的 cardMsgIds / approvalCardMsgIds / approvalActions
原为原生 Map,只在审批成功 patch 时 delete,超时的审批、孤儿卡片、
已结束任务的卡片映射永远不清理,长时间运行的高频 bot 会 OOM。

修复:
- 新增 src/ttl-map.ts:TTLMap<K,V> 带 TTL 过期(默认 1h)+ 最大容量
  (默认 10000)近似 LRU 淘汰 + 节流 cleanup()。API 与 Map 的
  get/set/delete/has 子集兼容,可直接替换。
  - get 命中时重排到迭代末尾(LRU);过期条目懒删除
  - set 超容量时淘汰最早条目;距上次 cleanup 超 60s 触发全量过期扫描
- feishu.ts:三个 Map 字段改用 TTLMap({ttlMs:1h, maxSize:5000})。
  TTL 1h > Gateway 审批默认超时 30min,覆盖超时审批场景;上限 5000
  防异常流量 OOM。现有 get/set/delete 调用零改动。

验证(TS 类型):
$ npx tsc --noEmit   # 无错误

验证(TTLMap 运行时行为,临时脚本 6 组断言):
[1] 基本 set/get/delete/has            全通过
[2] TTL 过期(100ms)懒删除            全通过
[3] LRU 超容量淘汰(maxSize=3)         全通过
[4] cleanup() 主动清理过期              全通过
[5] #24 场景:超时审批孤儿映射被清理    全通过
[6] set 刷新 TTL                       全通过
==== 全部断言通过:TTL 过期 + LRU 淘汰 + cleanup 行为正确 ====
(临时验证脚本验证后已删除,未提交)
旧实现 api_json 的错误分支 format!("{{\"error\":\"{e}\"}}") 把 anyhow 错误
原样写进 HTTP 响应体,泄露文件系统路径、DB 路径、模块结构、依赖版本等
内部细节给客户端,违反安全边界。

修复:
- server.rs api_json 错误分支:调用 internal_error_response() 生成
  `{"error":"internal_error","correlation_id":"<uuid v4>"}`,状态 500。
  详细错误用 eprintln 记服务端日志(stderr,带 correlation_id 便于排障),
  不进响应体。
- 新增 internal_error_response() 辅助:返回 (body, correlation_id),
  correlation_id 为 uuid::Uuid::new_v4(),客户端报障凭此 ID 查日志。
- Cargo.toml: 引入 uuid.workspace = true(workspace 已有 v4+serde feature)。

验证:
$ cargo test -p orcha-shell --lib
test result: ok. 13 passed; 0 failed; 0 ignored
新增 #27 回归测试 2 个:
  internal_error_response_does_not_leak_details
    —— 断言敏感信息(路径/模块/DB)不出现在 body,correlation_id 为
       合法 UUID v4 且每次不同;并前置证明错误本身确实携带敏感信息
       (即旧实现会泄露)。
  api_json_error_branch_returns_generic_500_with_correlation_id
    —— 端到端:传入含敏感信息的 Err,api_json 响应体只含 generic error
       + correlation_id,不含敏感信息。
@Ink-dark Ink-dark force-pushed the fix/security-high-priority branch from a6e9b41 to b07a683 Compare July 10, 2026 01:50
Ink-dark added 2 commits July 10, 2026 01:58
为 v0.1.2-beta 发版准备:
- CHANGELOG.md:新增 v0.1.2-beta 段,记录 9 个安全 issue 修复
  (#7#16#20#23#17#22#26#24#27),按 CRITICAL/HIGH 分组,含验证结论。
- RELEASE_v0.1.2-beta.md:GitHub Release body 源,含修复表、设计原则、
  升级须知(新增 HTTP token / IPC TCP secret 两个可选配置)。
- release.yml: body_path 从写死的 RELEASE_v0.1.0-beta.md 改为动态
  RELEASE_${{ github.ref_name }}.md,避免每次发版都要改 workflow。

验证:CI 门控已通过(PR #29 的 4 个 job 全 pass:ubuntu/windows ×
fmt+clippy+test / release build smoke)。
release.yml Package (Windows) 步骤 $pkg = "orcha-v${{ github.ref_name }}"
多拼了一个 v:ref_name 已含 v 前缀(v0.1.2-beta),结果是 orcha-vv0.1.2-beta。
Linux 步骤是 orcha-${{ github.ref_name }}(正确),Windows 对齐之。

注:v0.1.2-beta 已发布,其 Windows asset 名为 orcha-vv0.1.2-beta-windows-x86_64.zip
(瑕疵已固化,不影响内容)。本修复对后续发版生效。
@Ink-dark Ink-dark merged commit a33adbc into main Jul 10, 2026
4 checks passed
Ink-dark added a commit that referenced this pull request Jul 10, 2026
为 v0.1.2-beta 发版准备:
- CHANGELOG.md:新增 v0.1.2-beta 段,记录 9 个安全 issue 修复
  (#7#16#20#23#17#22#26#24#27),按 CRITICAL/HIGH 分组,含验证结论。
- RELEASE_v0.1.2-beta.md:GitHub Release body 源,含修复表、设计原则、
  升级须知(新增 HTTP token / IPC TCP secret 两个可选配置)。
- release.yml: body_path 从写死的 RELEASE_v0.1.0-beta.md 改为动态
  RELEASE_${{ github.ref_name }}.md,避免每次发版都要改 workflow。

验证:CI 门控已通过(PR #29 的 4 个 job 全 pass:ubuntu/windows ×
fmt+clippy+test / release build smoke)。
@Ink-dark Ink-dark deleted the fix/security-high-priority branch July 10, 2026 02:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[CRITICAL] Command timeout does not kill child process - resource leak & security risk

1 participant