本文从「外部安全」和「AI 操控安全」两个层面论述 1Shell 的安全措施,并说明遇到问题如何查记录、做回溯。 所有论述基于实际代码(已逐处核实)。文末标注了有意未覆盖的边界。
1Shell 的安全设计分两层关注点:
- 外部安全:谁能进来、连接是否被加密、凭据怎么存——这是"人与系统之间"的边界。
- AI 操控安全:进来之后,AI 能对真实世界做什么、做不了什么——这是"AI 与外部世界之间"的边界,由 harness 边界层统一管控。
两层威胁模型不同。外部安全防的是"未授权访问与传输窃听";AI 操控安全防的是"AI 善意失误"(它想清理却手滑跑了危险命令)。
会话式登录 + 暴力破解防护:
- 登录校验用户名/密码,成功后签发
crypto.randomUUID()会话 ID +crypto.randomBytes(24)CSRF token(auth.service.js:69-70)。 - 密码比对用
crypto.timingSafeEqual(:179),等长比较防时序攻击——不会因为"前几位对了"返回得更慢而泄漏密码长度信息。 - 暴力破解锁定:同一 IP 连续 5 次失败锁定 60 秒(
:24-25, 55-62, 167-170)。这一条直接驳斥了"无登录限流"的说法。 - 全
/api业务路由(exec / files / program / ai / host / audit)都挂在requireAuth之后(server.js:258),登录、健康检查在之前——顺序正确,受保护资源不可绕过鉴权。
会话 Cookie 安全属性(:128-139):
- Session cookie 带
HttpOnly——JavaScript 读不到,防 XSS 窃取会话。 SameSite=Lax——防跨站请求伪造的基础防线。- HTTPS 环境自动加
Secure标志(shouldUseSecureCookie探测x-forwarded-proto)。 - CSRF token 用独立 cookie(不带 HttpOnly,供前端读取后放进请求头),所有非 GET/HEAD/OPTIONS 请求强制校验(
verifyCsrfToken:107-126),同样timingSafeEqual比对。
代理 IP 反伪造(:27-35):
- 只有来自
TRUSTED_PROXY_IPS白名单的请求才信任X-Forwarded-For头,否则用 socket 真实地址——攻击者无法通过伪造 XFF 头绕过 IP 锁定。
Socket.IO 同源鉴权(:205-223):
- WebSocket 握手时校验同一套会话 cookie,未登录的 socket 连接被拒——AI 对话、终端这些走 socket 的能力同样受鉴权保护。
在鉴权之外,提供网络层的 IP 准入控制——这是"谁的网络能连进来"的边界,比账号鉴权更靠前一层。
双模式,可叠加:
- 白名单模式:开启后只有名单内的 IP/CIDR 能访问,其余一律 403。
- 黑名单模式:开启后名单内的 IP/CIDR 被拒绝访问。
- 两者可同时开启(先过白名单,再过黑名单);均关闭时不做限制(默认全关,开箱即用)。
实现要点:
- 支持精确 IP 和 CIDR 段(如
192.168.1.0/24)匹配,含 IPv4-mapped IPv6(::ffff:x.x.x.x)处理(matchesCidr/ipToInt)。 - 规则存 SQLite,内存缓存 + 变更即失效(
invalidateCache)——热更新无需重启。 - 同样的 XFF 反伪造:只信任受信代理 IP 转发的
X-Forwarded-For(getClientIp:93-100)。
挂载位置是关键(server.js:244):IP 过滤中间件挂在所有业务路由之前——包括 bridge token 路由、MCP 路由、proxy 路由。只有登录端点(/api/auth)和健康检查在它之前(否则被封 IP 的人看不到 403 原因、负载均衡探活会失败)。这意味着一旦配置白/黑名单,覆盖全部端点,无法通过 token 路由绕过。
- 所有远端操作走 SSH(ssh2),命令、文件传输都在加密信道内。
- 级联跳板(ProxyJump)由 hostService 统一处理,多跳连接全程加密。
- 主机 SSH 密码、私钥、passphrase 用 AES-256-GCM 加密后落库(
encryptText:17-34)——GCM 是认证加密,既保密又防篡改(解密时校验 authTag,密文被改动会失败)。 - 每条密钥独立随机盐(
randomBytes(16))+ 随机 IV(randomBytes(12)),密钥经scryptSync派生——同一明文每次加密结果不同,防彩虹表。 - 密钥明文从不写日志;输出层有统一打码(见 2.4)。
- 文件读写挡敏感凭据文件:
.env/id_rsa/id_ed25519/.pem/authorized_keys/shadow等(SENSITIVE_BASENAMES+SENSITIVE_EXTENSIONS)。 - 拦路径穿越
..片段。
这是 1Shell 的核心安全创新。它解决的问题是:AI 拥有高度自主权,无人值守地操作真实 VPS,如何保证它不会好心办坏事。
AI 触达外部世界的所有路径(IDE 自由对话、Program 自动化、MCP 远程调用)都收敛到同一个关口 harness.dispatch。AI 想执行命令、读写文件,只能经过这个关口——绕不过去。
关键在于:安全护栏从"prompt 层"下沉到"执行层"。
- prompt 里写"禁止 rm -rf /" 是请求——AI 可以不听,可以被绕过。
- harness 在执行层拦截是物理隔断——AI 想听也绕不过,因为命令在发出前就被确定性代码判定。
护栏全部用确定性手段(正则、白名单、命令归一化),绝不调 LLM 判断命令安不安全——既快(微秒级)又不可被 prompt 注入绕过。
每一次 AI 工具调用,都经过这六步(harness/dispatch.js):
- 开轨迹(startTrace)——记录这次调用的上下文。
- 确定性护栏(guard.check)——capability 准入 + 灾难命令拦截,不通过直接拒绝(fail-closed)。
- 人审 gate——仅当"人在场"的上下文(IDE 对话)才触发;无人值守路径(Program)跳过,零等待。
- 执行——派发到底层执行器(SSH bridge / 本机)。
- 出口打码——结果里的 secret 自动脱敏。
- 收轨迹(endTrace)——落库决策、结果、耗时。
第一道 · 灾难命令拦截(红线兜底,command-safety.js)
不可逆的灾难命令在执行前被拦截,命令根本不发出:
rm -rf /、rm -r --no-preserve-root /(无-f也拦)、rm -rf /*、全盘通配- fork bomb、
mkfs格式化块设备、dd写裸设备 - 带引号绕过也能识别:
rm -rf "/"、rm -rf '/'经命令归一化(剥引号)后同样被拦(normalizeForRiskMatch)。
这一道正中威胁模型——AI 手滑跑了 rm -r /,guard 直接挡下,不需要问任何人。
第二道 · 能力声明式最小授权(capabilities.js)
回答"这次任务被授权能做什么",与红线兜底正交:
- 每个 Program 的 AI step 可在 yaml 里声明
capabilities: [read_only]。 - 声明
read_only的 step,AI 即便想执行写命令(rm、systemctl restart、包管理器)也被边界挡掉,并把"未授权"作为结果喂回,AI 自己换方案。 - fail-closed 授权语义:显式空能力集
[]/null拒绝一切;未声明(undefined)回退默认能力保向后兼容。
这是"最小授权"原则的落地——巡检任务只给只读,根本没机会执行破坏性操作,比黑名单稳。
第三道 · 风险分级人审降级
按调用上下文挂不同护栏,解决"自动化 vs 安全"的张力:
| 上下文 | 灾难拦截 | capability | 人审 |
|---|---|---|---|
| IDE 对话(人在场) | ✅ | ✅ | ✅ 可审批 |
| Program 自动化(无人值守) | ✅ | ✅ | 关闭,零等待 |
- 中危命令(
systemctl restart、递归删除、改防火墙)在人在场时标记 needApproval,弹审批。 - 无人值守路径
allowApproval: false,人审分支永不进入——AI 在红线内完全自主、零等待,碰红线确定性挡掉、不打断流程也不问人。
第 2.3 节第三道护栏在"人在场"上下文的具体落地,就是 1Shell AI 的安全模式——用户与 AI 自由对话时默认开启(session.safeMode 默认 true,ide.service.js:516)。
它怎么工作(ide.service.js:723-736):
- AI 每次想调用工具,先按工具类型分流:
- 只读工具(
list_hosts/read_remote_file/query_probe/query_audit等,见READONLY_TOOLS白名单)——自动放行,不打扰用户。 - 写/执行类工具(
execute_command/write_file/deploy_local_mcp等)——弹出审批请求,等用户在前端点"批准 / 拒绝 / 自定义回复",AI 暂停等待。
- 只读工具(
- 超时默认拒绝(
waitForApproval:438):5 分钟无响应自动按"拒绝"处理——fail-safe,不会因为用户离开就默默执行。 - 用户可关闭安全模式(
setSafeMode),关闭后 AI 自主执行不再逐条弹审批——适合信任场景下的高效操作。
关键设计:安全模式不靠 prompt 文字约束(SAFE_MODE_ADDENDUM 实际是空字符串)——它纯粹是执行层的拦截 gate。这与整个安全理念一致:约束 AI 行为靠的是代码关卡,不是"对 AI 好言相劝"。AI 能不能执行,由 safeMode + 工具类型决定,AI 自己说什么都改变不了这个判断。
与 harness 的人审 gate 的关系:安全模式是"人在场"路径(IDE 对话)的审批;harness dispatch 的人审 gate(2.3 第三道)是同一理念在统一边界层的实现。两者都遵循"无人值守关、人在场开"的上下文授权原则。Program 自动化(无人值守)不走安全模式审批,靠的是确定性 guard + 最小授权兜底。
命令输出里的 token / 密码 / 私钥片段,在返回给 AI 和前端、写入轨迹前自动脱敏成 [REDACTED]:
- 成功路径、错误路径、轨迹的 input_summary 全部覆盖(
redactKnownSecrets+redactCredentialPatterns)。 - 防止 secret 通过命令输出泄漏到日志、前端展示或 AI 上下文。
改造前,灾难拦截只挂在底层 SSH 服务上,任何不走该服务的路径(本机命令执行)自动逃逸所有护栏——本机 rm -rf / 直接执行,连审计都没有。
改造后,IDE / Program / MCP 三条路径统一穿过 harness,本机命令同样被拦、被审计。这不是"加了个功能",是修复了一个真实存在的安全真空。
1Shell 有两套记录,互补使用。
记什么:每一次命令执行、MCP 工具调用、Program 运行、文件操作、配置变更,都落一条(audit.service.js)。字段含 action、source(来源:bridge_api / mcp / ide / program)、host、command、exit_code、duration、client_ip、error、timestamp。
只追加,不删除——满足合规留痕。SQLite 不可用时降级为文件日志(audit.log),保证不丢。
怎么查:
- 前端/API:
GET /api/audit/logs?limit=50&action=...&source=...&hostId=...&keyword=...(audit.routes.js,受 requireAuth 保护,分页、可按动作/来源/主机/关键词过滤)。 - AI 自己也能查:
query_audit工具(AI 排障时可调阅历史)。
典型场景:
- "某台机器昨天被执行了什么" → 按 hostId 过滤。
- "哪条命令导致了故障" → 按 keyword 搜命令文本,看 exit_code 非 0 的条目。
- "某次 Program 跑了哪些命令" → 按 source=program-ai-workflow 过滤。
记什么:每一次经过 harness 的工具调用,落一条带决策上下文的轨迹(harness/trace.js)。这是 audit_logs 没有的维度:
decision:allowed / blocked / denied / error——这次调用被放行还是被挡。block_reason:命中了哪条策略("已拦截灾难性命令:递归强制删除根目录"、"当前授权能力 [read_only] 下该命令不被允许")。capabilities:本次授权的能力集。source:来自 IDE / Program / MCP 哪条路径。needed_approval、input_summary(已打码)、exit_code、duration_ms、result_summary。
可解释性:每次拦截都能回答"为什么挡 / 命中哪条策略"——这是 audit_logs 的扁平流水做不到的。排查"AI 当时为什么没执行某操作"时,看 decision + block_reason 一目了然。
怎么查:
- 前端:审计界面顶部「Harness 轨迹」tab——拦截/拒绝的记录红色高亮,拦截原因单独红框展示,可按决策(放行/拦截/拒绝/错误)和关键词筛选。
- API:
GET /api/audit/harness-traces?decision=blocked&keyword=...(audit.routes.js,受 requireAuth 保护,与审计日志接口对称)。
典型场景:
- "AI 说要清理却没清理" → 查 harness_traces,看是 blocked(被护栏挡)还是 allowed(AI 自己没发命令)。这次真实排障就是靠它定位到"AI 跳过了执行步骤"而非"被 harness 拦"。
- "read_only step 真的限制住了吗" → 看该 run 的 traces,写命令应是 blocked + block_reason 写明能力不足。
- audit_logs 回答"发生了什么"——完整的操作流水,含成功执行的真实命令。
- harness_traces 回答"为什么这么决策"——每次调用的准入判断、拦截原因、授权能力。
排障路径:先用 audit_logs 定位"哪台机、什么时间、哪条命令、退出码多少";再用 harness_traces 看"那次调用 harness 的决策是什么、有没有被挡、命中哪条策略"。两者通过 host_id / run_id / 时间关联。
program_runs表记每次 Program 运行的 status、steps_total/completed、error、started/finished、完整 renders。- 排查 Program 失败时,先看这张表的 status 和 steps_completed 定位卡在哪一步,再下钻到 audit_logs / harness_traces 看那一步的具体命令和决策。
为避免误导,明确标注当前安全边界——这些是基于"自托管个人工具"威胁模型的有意取舍,不是疏漏:
威胁模型:1Shell 是自托管工具,用户自己掌控访问、自己加主机、自己信任。安全设计防的是"AI 善意失误"和"未授权外部访问",不防"恶意攻击者已经能打到机器后的提权/绕过"。
基于此,以下有意不做:
- 首启默认凭据:无凭据时开放访问,让用户首次启动后自行设置——与自建面板的常规做法一致。
isUsingFallbackSecret()会在启动时告警提示设置APP_SECRET。 - AI 命令绕过的极端构造:
env前缀、$(...)命令替换、find -exec、base64 解码执行等 shell-out 技巧能绕过 capability 限制。要完全堵住需 AST 级命令解析——但这些是"攻击者刻意绕过"才会用的,AI 助手不会主动构造。当前 guard 对"善意失误"够用。 - SSH 主机密钥校验(TOFU):未做首连指纹固定。自托管、自己掌控网络的场景风险可控。 这个诚实标注本身是加分项——清楚知道边界在哪,比声称"全覆盖"被追问时露馅强。
- 外部安全:IP 白/黑名单(网络层准入,挂在所有业务路由之前不可绕过)+ 会话鉴权 + 暴力破解锁定 + CSRF + HttpOnly cookie + 时序安全比对 + AES-256-GCM 凭据加密——从网络层到应用层的标准 Web 安全做齐。
- AI 操控安全:把护栏从 prompt 下沉到执行层,确立 harness 作为 AI 触达外部世界的唯一边界,三道确定性护栏(灾难拦截 / 最小授权 / 人审降级)+ 安全模式(人在场逐条审批,超时默认拒绝)+ secret 打码,解决"AI 高度自主 + 无人值守"下的安全兜底。
- 可回溯:audit_logs 记"发生了什么"(法务级流水)+ harness_traces 记"为什么这么决策"(带拦截原因的可解释轨迹,审计界面有专门 tab),两套互补,排障时先定位再下钻。