Files
workpod/docs/04-审核/04-Shell脚本质量审核.md
T

19 KiB
Raw Blame History

Shell 脚本质量审核报告

审核日期:2026-04-07 审核范围:WorkPod Alpine(含 Test 变体)全部 Shell 脚本 审核人:Shell 脚本专家


脚本清单

脚本 行数 用途 复杂度
entrypoint.sh 81 Alpine 版容器入口,启动 SSH + ttyd + 开发工具检测
entrypoint-test.sh 129 Test 容器入口,额外创建 developer 用户 + Claude 全权限配置 中高
ttyd-session.sh 63 tmux 会话管理,URL 参数解析 + 交互式菜单
合计 273

逐脚本审核

entrypoint.shAlpine 版)

文件路径: E:/wk-lab/workpod/entrypoint.sh

健壮性

# 问题 严重度 说明
R1 SSHD_PID 未赋值(与 Ubuntu 版相同的问题) cleanup()kill "$SSHD_PID" 因 PID 始终为 0 而跳过。/usr/sbin/sshd 在第42行前台启动(无 &),实际上 sshd 作为守护进程运行后返回,但未捕获其 PID。应改为 /usr/sbin/sshd & SSHD_PID=$! 或在 cleanup 中用 pkill sshd
R2 sshd 未使用 -D 前台模式 第42行直接调用 /usr/sbin/sshd,它会 fork 为守护进程。如果后续命令失败导致脚本退出,sshd 会成为孤儿进程。建议用 -D 参数保持前台或正确追踪 PID。
R3 sleep 1 硬编码等待时间 与 Ubuntu 版相同,仅等待 1 秒判断 ttyd 存活。慢速系统上可能不够。
R4 dev-tools.sh 中的 ls -d 可能匹配多个目录 第38行 ls -d /root/rust-*/bin 如果存在多个 rust 版本目录,head -1 只取第一个。行为可预测但隐式依赖排序。
R5 cleanup 中 wait 无参数 第11行 wait 2>/dev/null 等待所有后台子进程。由于只跟踪了 TTYD_PID 和 SSHD_PID(后者实际为 0),行为基本正确但语义不精确。

可移植性

# 问题 严重度 说明
P1 shebang 使用 bash 但环境为 Alpine/musl #!/bin/bash 要求安装 bash 包。Dockerfile 第31行已包含 bash 依赖,所以当前可工作。但如果未来精简镜像可能出问题。脚本中未使用任何 bashism(数组、[[ ]] 等),理论上可改用 #!/bin/sh
P2 pidof 替代 pgrep 正面 第55行使用 pidof sshd 而非 pgrep -x sshd,这是正确的 POSIX 兼容选择,在 BusyBox 环境下可靠工作。
P3 gvm source 命令 dev-tools.sh 第34行 source /root/gvm/scripts/gvm 依赖 gvm 的初始化脚本格式。如果 gvm 不存在该路径会静默失败(有 2>/dev/null)。

安全性

# 问题 严重度 说明
S1 ttyd 密码过于简单 ada:123 [已改进] 原第45行 -c ada:123 已改为从 ${TTYD_CREDENTIALS:-jc:1234567} 环境变量读取,不再硬编码在脚本中。密码强度可通过环境变量灵活配置。
S2 硬编码密码明文出现在脚本和日志中 [已改进] 同上,密码已改为通过环境变量 TTYD_CREDENTIALS 传入,不再明文出现在脚本源码中。
S3 dev-tools.sh 使用单引号 heredoc(安全) 正面 第32行 << 'PROFILE' 阻止变量展开,防止注入。这是正确的做法。
S4 无用户隔离 信息 Alpine 默认版直接以 root 运行一切,没有像 Test 版那样创建 developer 用户。作为开发环境可接受。

代码质量

# 问题 严重度 说明
Q1 开发工具检测逻辑完善 正面 覆盖 cargo/rustup/gvm/pyenv/go/rust独立安装/local bin 共 7 种开发工具路径。
Q2 双轨 PATH 设置 正面 既在当前 shell 设置 export(第19-29行),又写入 /etc/profile.d/dev-tools.sh(第32-40行)确保新会话生效。设计合理。
Q3 启动信息展示完整 正面 展示 Node/npm/Claude/Rust/Go/Python 版本 + 连接方式,便于快速确认环境状态。
Q4 版本检测容错好 正面 所有 --version 命令都有 2>/dev/null || echo '未安装' 保护。
Q5 cleanup 函数与 Ubuntu 版完全重复 cleanup/trap 模式在三份 entrypoint 中复制粘贴。

代码质量评分:7.5 / 10

扣分项:SSHD_PID 未赋值(-1)、密码过弱(-0.5)、sshd 前台模式(-0.5)、代码重复(-0.5)


entrypoint-test.shTest 容器入口) [归档]

此文件已移至 _archive/,以下问题仅作历史记录。

文件路径: E:/wk-lab/workpod-alpine/entrypoint-test.sh(已归档至 _archive/

健壮性

# 问题 严重度 说明
R1 SSHD_PID 未赋值(继承自 Alpine 版) 同 entrypoint.sh 的 R1 问题。
R2 adduser -D 无密码设置 第32行 adduser -D -s /bin/bash developer 创建用户时未设置密码。developer 用户只能通过 sudo 操作,不能直接 SSH 登录(除非配置了密钥认证)。这可能是故意的,但应明确注释说明。
R3 sudoers 文件写入无原子性保护 第34行直接 echo ... > /etc/sudoers.d/developer。如果脚本在中途被中断,可能留下不完整的 sudoers 文件导致 sudo 不可用。建议先写临时文件再 mv。
R4 chown -R 递归操作范围大 第37行 chown -R developer:developer /home/developer 对整个 home 目录递归修改所有权。如果有其他进程正在向该目录写入文件,可能出现竞态。但在容器启动阶段风险极低。
R5 settings.json 双引号 heredoc 安全 正面 第41行和第51行都使用 << 'SETTINGS' 单引号 heredocJSON 内容不会被 shell 解释。做法正确。

可移植性

# 问题 严重度 说明
P1 adduser -D 是 Alpine 特有 信息 -D 标志表示"不分配密码",是 BusyBox adduser 的扩展。本脚本专用于 Alpine,无移植需求。
P2 bash 依赖同 Alpine 版 同 entrypoint.sh 的 P1 问题。

安全性

# 问题 严重度 说明
S1 Claude Code settings.json 全权限 allow:["*"] 严重 [归档] 第43-47行和第53-57行为 root 和 developer 用户都设置了 "allow": ["*"],意味着 Claude Code 可以执行任意文件读写、命令执行等操作而无需用户确认。虽然这是开发环境的故意设计(沙箱用途),但应在文档中明确标注此安全策略的影响范围。归档文件中的配置,不影响当前版本。
S2 .bashrc 中环境变量传递方式 第67-70行使用双引号 heredoc << BASHRC 并在其中引用 ${ANTHROPIC_AUTH_TOKEN} 等变量。这里变量会在 heredoc 写入时展开——即使用 entrypoint 进程的环境变量值。这意味着:(1) 如果环境变量未设置,会写入空字符串;(2) 如果值包含特殊字符(如 $`` ),可能被二次解释。当前用法基本安全但需注意边界情况。
S3 NOPASSWD:ALL sudo 权限 高 [归档] 第34行给 developer 用户无密码完整 sudo 权限。配合 Claude Code 全权限配置,developer 用户等同于 root。这是有意的设计决策(全权限沙箱),但安全边界完全消失。同上,归档文件中的配置。
S4 .bashrc 中 PATH 包含 $PATH 正面 第66行 export PATH="/usr/local/bin:/usr/bin:/bin:\$PATH" 使用 \$PATH 在双引号 heredoc 中转义 $,确保写入文件的是字面量 $PATH 而非展开后的值。这个转义是正确的。

代码质量

# 问题 严重度 说明
Q1 功能模块划分清晰 正面 按顺序分为:开发工具检测 -> 用户创建 -> Claude 配置(root) -> Claude 配置(developer) -> 别名配置(root) -> 启动服务 -> 信息展示。结构清晰。
Q2 root 和 developer 配置对称处理 正面 settings.json 和 .bashrc 分别为两个用户配置,且 chown 所有权正确。
Q3 alias 区分 root/developer 参数 正面 root 用 --allow-dangerously-skip-permissions(第76行),developer 用 --dangerously-skip-permissions(第65行)。准确反映了 Claude Code 对 root 用户的限制。
Q4 与 entrypoint.sh (Alpine) 大量重复 高 [归档] 开发工具检测(第19-29行 vs 第19-29行完全相同)、dev-tools.sh 写入(第80-88行 vs 第32-40行完全相同)、cleanup 函数(第7-14行相同)、服务启动和检查(第90-106行几乎相同)。唯一差异是用户创建和 Claude 配置部分(第31-78行)。重复率约 65%。因归档而不再是活跃问题。
Q5 环境变量默认值处理良好 正面 第69-70行 ${API_TIMEOUT_MS:-3000000}${CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:-1} 提供了合理的默认值。
Q6 端口/标题/密码硬编码 7682、2223、"WorkPod Test"、jc:1234567 全部硬编码。(注:此为已归档的 entrypoint-test.sh 中的历史值;当前基础实例已使用 2222/7681 端口,凭据通过环境变量注入)

代码质量评分:7 / 10 (历史评分,文件已归档)

扣分项:与 entrypoint.sh 高度重复(-1.5)、SSHD_PID 未赋值(-1)、全权限安全策略未文档化(-0.5)


ttyd-session.shtmux 会话管理)

文件路径: E:/wk-lab/workpod/ttyd-session.sh

健壮性

# 问题 严重度 说明
R1 set -e 缺失 脚本没有 set -e。如果中间某条命令失败(如 tmux list-sessions),脚本会继续执行可能导致错误状态扩散。考虑到脚本的交互性质(需要 read 输入),set -euo pipefail 更合适但需注意 read 在 set -e 下的行为(read 到 EOF 返回非零不会终止脚本,因为它是条件上下文的一部分)。
R2 SESSION_NAME 初始为空字符串 第4行 SESSION_NAME="" 初始化为空。如果 TTYD_QUERY_STRING 解析也得到空值(第9行的 grep 无匹配),则进入交互菜单分支。流程正确但变量生命周期不够明确。
R3 tmux session 名过滤后可能为空 第55行 tr -cd 'a-zA-Z0-9_\-' 过滤后如果 SESSION_NAME 变成空字符串(例如原始输入全是特殊字符),第58行 tmux new-session -t "" 会创建一个名为空字符串的 session。虽然 tmux 允许这样做但不推荐。应在过滤后检查是否为空并赋予默认值 "default"。
R4 sed -n "${CHOICE}p" 未做范围校验 第45行 sed -n "${CHOICE}p" 直接将用户输入的数字传给 sed。虽然前面有 grep -qE '^[0-9]+$' 校验了纯数字,但没有检查数字是否在有效范围内(1 到 session 数量之间)。超出范围的 CHOICE 会导致 sed 输出空行,SESSION_NAME 被设为空字符串,然后触发 R3 的问题。
R5 EXISTING 变量的 word splitting 第20行 tmux list-sessions 输出类似 my_session: (attached) 格式,awk 提取第一列后得到 my_session:(带冒号)。第24行 for s in $EXISTING 依赖 word splitting 来遍历 session 名。如果 session 名包含空格(虽然 tmux 不允许),会出错。实际风险低。
R6 exec tmux attach/new 替换进程 正面 第59-61行使用 exec 替换当前 shell 进程为 tmux,避免多余的 shell 进程残留。这是正确的做法。

可移植性

# 问题 严重度 说明
P1 BusyBox awk 兼容性(已修复) 已解决 历史上使用 grep -oPPerl 正则)解析 query string,在 BusyBox 中不可用。当前版本第9行改用 tr '\&' '\n' | grep '^session=' | cut -d= -f2 的管道链,完全兼容 BusyBox。
P2 tr -cd 字符类兼容性 第55行 tr -cd 'a-zA-Z0-9_\-' 中的 \- 在 GNU tr 和 BusyBox tr 中都能正确解释为字面量连字符。POSIX 标准中字符类内的 - 放在首位或末位或转义均可。
P3 printf 格式化 正面 第25行 printf "║ [%d] %-33s║\n" "$I" "$s" 使用标准 printf 格式,跨实现兼容。
P4 clear 命令 信息 第51行 clear 依赖 terminfo 数据库。在 Docker 的 ttyd 环境中通常可用。
P5 grep -qE 正面 第44行使用 grep -qE(扩展正则),BusyBox awk 支持此选项。

安全性

# 问题 严重度 说明
S1 TTYD_QUERY_STRING 注入防护 正面 第9行通过管道链 tr | grep | cut 解析,最终结果经过第55行 tr -cd 过滤为安全字符集。即使攻击者构造恶意的 query string(如 session=;rm -rf /),也会被过滤掉。安全性良好。
S2 用户输入 SESSION_NAME 注入防护 正面 第55行将用户交互输入同样通过 tr -cd 过滤,tmux session 名被限制在 [a-zA-Z0-9_-] 字符集中。无法注入 shell 命令。
S3 无外部命令拼接 正面 所有变量传递给 tmux 时都加了双引号("$SESSION_NAME"),防止 word splitting 和 globbing。

代码质量

# 问题 严重度 说明
Q1 UI 设计精美 正面 使用 Unicode 制表符绘制边框,视觉效果专业。编号选择 + 自定义名称的双重输入方式用户体验好。
Q2 双模式设计合理 正面 URL 带 ?session=xxx 直接进入(自动化场景),不带参数显示菜单(手动场景)。覆盖了两种主要使用方式。
Q3 默认值 fallback 正面 第39-41行用户输入空时默认为 "default"。
Q4 缺少 set -e/o pipefail 如 R1 所述。对于这种交互式脚本,建议至少加 set -uo pipefail(不含 -e 以免影响 read)。
Q5 冒号未从 session 名中去除 第20行 awk {print $1} 提取的是 session_name:(带尾部冒号)。这个带冒号的名字会被用于显示(第25行)和选择(第45行 sed 取行),最终传入 tmux 的也是带冒号的名字。tmux 本身允许 session 名包含冒号,所以功能不受影响,但显示上不够干净。可以用 tr -d ':' 清理。
Q6 代码简洁高效 正面 仅 63 行实现了 query string 解析、交互式菜单、session 创建/附加的完整功能。没有冗余逻辑。

代码质量评分:8 / 10

扣分项:缺少 set -euo pipefail-0.5)、session 名空值/超范围未防护(-1)、冒号未清理(-0.5)


跨脚本问题

# 问题 影响范围 说明
X1 entrypoint.sh 与 entrypoint-test.sh(归档) 高度重复 2 个文件(其中1个已归档) 开发工具检测(12行)、dev-tools.sh 写入(8行)、cleanup函数(8行)、trap注册(1行)、SSH启动+检查(10行)、ttyd启动+检查(8行)、信息展示(18行)、exec sleep(1行) —— 约 66 行完全相同,占总代码量 66/210 = 31%(不计公共部分则重复率更高)。优先级大幅降低(entrypoint-test.sh 已归档)。
X2 三个版本的 cleanup 函数逐字相同 3 个文件 Ubuntu/Alpine/Test 三份 entrypoint 的 cleanup 函数(第7-14行)完全一致。这是典型的 DRY 违反。
X3 ttyd 认证密码三套不同策略 3 个文件 Ubuntu版无密码 / Alpine版 ada:123 / Test版 jc:1234567。安全级别不一致,容易在部署时混淆。[已修复] — 当前版本统一从 ${TTYD_CREDENTIALS} 环境变量读取。
X4 dev-tools.sh 内容在 Alpine 和 Test 版中逐字相同 2 个文件 entrypoint.sh 第80-88行与 entrypoint-test.sh 第32-40行的 heredoc 内容完全一致。
X5 SSHD_PID 跟踪在所有版本中都无效 3 个文件(其中1个已归档) 三个版本的 entrypoint 都声明了 SSHD_PID=0 且从未赋值,cleanup 中的 kill "$SSHD_PID" 分支永远不会执行。这是一个系统性缺陷。(低优先级,未修复)
X6 端口号/标题/密码散落在各脚本中 3 个文件(其中1个已归档) 修改端口或密码需要在多个文件中同步修改,遗漏任一文件会导致配置不一致。[已改善] — 当前版本已集中到 docker-compose.yml 的 environment 配置中管理。

Top 10 改进建议

优先级 建议 涉及脚本 收益
P0 重构 entrypoint 公共框架(配置驱动) entrypoint.sh (x2, 其中1个已归档) 消除 ~66 行重复代码,新增容器变体只需配置文件(优先级降低:entrypoint-test.sh 已归档)
P1 修复 SSHD_PID 追踪(统一方案) entrypoint.sh (x3) 实现 sshd 优雅关闭,防止孤儿进程
P2 ttyd-session.sh 添加 set -uo pipefail + 输入校验 ttyd-session.sh 防止空 session 名和越界访问
P3 统一 ttyd 认证策略,密码从环境变量读取 entrypoint.sh (x3) 消除安全配置不一致,支持灵活部署(已完成
P4 ttyd-session.sh 清理 session 名尾部冒号 ttyd-session.sh UI 显示更干净,避免意外行为
P5 entrypoint-test.sh sudoers 文件写入增加原子性 entrypoint-test.sh 防止中断导致 sudo 不可用
P6 document 全权限安全策略的影响范围 entrypoint-test.sh 让使用者明确了解 allow:["*"] + NOPASSWD:ALL 的安全含义
P7 端口/标题/密码等配置外部化为环境变量 entrypoint.sh (x3) docker-compose.yml 集中管理,消除散落配置
P8 考虑 entrypoint.sh shebang 改为 /bin/sh entrypoint.sh (Alpine x2) 减少 bash 依赖,Alpine 环境更轻量(需确认无 bashism)
P9 sshd 启动增加 -D 模式或 PID 文件追踪 entrypoint.sh (Alpine x2) 更精确的进程生命周期管理

附录:历史 Bug 回顾

Bug 影响 当前状态
grep -P 在 BusyBox 不可用 Alpine 环境 TTYD_QUERY_STRING 解析失败 ttyd-session.sh 第9行已改用 tr & '\n' | grep '^session=' | cut -d= -f2 管道链,完全兼容 BusyBox
heredoc 单引号阻止变量展开 .bashrc 中 ANTHROPIC_AUTH_TOKEN 等环境变量无法传递到 developer 用户 entrypoint-test.sh 第64行已改用 << BASHRC 双引号 heredoc,变量在写入时正确展开;\$PATH 转义确保字面量输出
dev-tools.sh 反斜杠转义问题 profile.d 脚本中 \$PATH\\ 导致语法错误 当前版本 dev-tools.sh 使用单引号 heredoc << 'PROFILE',内容原样写入无需转义,已修复