--- summary: '安全:modlens 会运行什么、恢复出的图片如何受保护、图片内容属于不可信输入' read_when: - 审查这个工具会在你的机器上做什么 - 在共享机器上恢复粘贴的图片 - 判断视觉引擎的报告可信到什么程度 --- # 安全 [English](security.md) | 中文 ## 恢复出的图片是私有的 从会话存储里取出的图片以 0600 权限写入 0700 目录。默认这个目录是每次运行新建的、不可预测的 `/modlens-paste-*`,共享机器上没人能预先创建一个已知路径来读取字节(`recursive` 的 mkdir 不会改动已存在目录的权限模式)。一张粘贴的截图里可能有任何东西。显式传入的 `--out-dir` 会被接受,但不安全时会被拒绝:它必须是真实目录,不是符号链接,归你所有,组和其他用户无任何权限。 恢复同样限定在单个项目内:检查的是 transcript 里记录的工作目录,不只是目录名,因为目录 slug 会撞车(`/tmp/a.b` 和 `/tmp/a-b` 生成同一个 slug)。相邻项目的图片绝不会被交出去。 ## 传给引擎的权限 ModLens 调用 `agy` 时带上 `--dangerously-skip-permissions`,因为某些环境下 prompt 模式不带它就会失败。prompt 把 agent 限制为只读交给它的那一张图片,并指示它把图片内容严格当作数据。 `claude-cli` provider 只带 `--allowedTools Read` 运行,因此它能读本地文件,别的什么都做不了。`kimi-cli` 没法这样收窄(它的 CLI 没有对应参数),所以在两处区别对待:它只在被点名时运行,绝不作为故障转移的备选,因为它花的是订阅;运行时把 skill 发现指向空目录,否则 kimi 会找到 modlens skill、通过运行 modlens 来读图,也就是 modlens 自己调自己。它的子进程还带一个标记,让嵌套的 modlens 拒绝再次启动 kimi。 这些子进程 provider 还都运行在一个用完即弃的目录里,每次调用新建,结束后删除。本地图片时,目录里只有那张图片的一份私有副本,别无他物,而且是真实拷贝,绝不用硬链接,provider 往自己的临时路径写东西也碰不到原文件。远程图片时目录是空的,agent 把文件下载进去。没有这层隔离,图片里的文字就可能引导一个权限宽泛的 agent 去读原图旁边的文件,或读调用者恰好所在的项目。传 `--workdir` 即放弃这层隔离,在你指定的位置运行。 这是在缩小暴露面,不是操作系统沙箱:agent 仍然能读绝对路径、访问网络、启动进程。把它当作更收紧的默认值,而不是安全边界。不信任的图片优先用 `-p gemini-api`,它自己下载字节,不运行任何本地 agent。远程 URL 本来就优先走内联这一侧:远程 URL 的故障转移链先试内联的 API provider,agent 排在最后。远程 URL 实际由谁抓取因 provider 而异,而只有本地下载能被本地防护覆盖: | Provider | 远程 URL 由谁抓取 | 本地防护 | | :-- | :-- | :-- | | `gemini-api` | modlens 自己下载,字节内联发送 | 私有地址防护、文件头魔数(magic bytes)检查、25 MB 上限 | | `openai`、`anthropic` | URL 传给厂商,由厂商抓取 | 本地无,适用厂商自己的抓取策略 | | `antigravity-cli`、agent CLI | agent 自行抓取 | 本地无 | 所以私有地址防护、文件头魔数检查和大小上限保护的恰好是 modlens 亲自下载的路径:每一次本地文件读取,加上 gemini-api 的远程抓取。显式 `-p` 钉死单个 provider,覆盖整条链。 ## 图片内容是不可信输入 图片里的文字是不可信的,和网页一样。一张截图可能带着写给任何读取者的指令。prompt 里明确说了这一点,但那是缓解措施,不是保证:只分析你愿意打开的图片,图片来自别处时优先用沙箱化的工作目录。 ## 证据,而非编造 引擎读不出来的内容会进入 `uncertainty`,而不是被脑补出来。v2 彻底去掉了像素坐标和置信度分数,因为这两个字段正是模型编造得最像真的。