【交叉评测】看见下一步 / AI-Action-Assistant:安全清洗、密钥治理与生产可用性 #4

Open
opened 2026-08-06 17:00:46 +08:00 by LIGHTNINGWHALE · 1 comment

评测账号:LIGHTNINGWHALE
评测对象:visionary/AI-Action-Assistant(看见下一步 · 独立远程 API 版)
评测日期:2026-08-06(Asia/Shanghai)
评测方式:克隆完整仓库并逐文件阅读源代码(app.py、server.py、model_api.py、minicpmo_runner.py、prompts.py、benchmark.py、tests/、model_profiles.json、.gitignore、.env.example、PROJECT_PROPOSAL.md 等),未安装依赖、未启动服务、未调用任何真实模型 API。

1. 项目理解

“看见下一步”是面向视障、低视力和视觉能力受限人群的行动辅助 Demo:用户上传图片或摄像头拍照,选择五类固定任务之一,软件通过 OpenAI 兼容的远程多模态模型 API 分析图片,把结果结构化为方向、风险等级、障碍物、OCR 文字等字段,并在浏览器中做中文语音播报。

项目的关键设计取舍是彻底与本地模型解耦:app.py(Gradio 界面)和 server.py(FastAPI 接口)都只依赖 model_api.py 提供的网关抽象,不加载权重、不依赖 torch/CUDA/NPU,这让软件本身可以独立安装、独立评审,也更容易替换或新增视觉模型供应商。

2. 做得好的地方

  1. 模型层抽象干净,业务与供应商解耦。 model_api.py 中的 ModelProfileOpenAICompatibleVisionClientModelGatewayload_model_gateway 把“选哪个模型配置”“如何拼 endpoint”“如何做 HTTP 请求”“如何处理 CLI/环境变量覆盖优先级”拆得很清楚,minicpmo_runner.pyserver.py/app.py 只依赖这一层公开接口,不关心具体供应商实现,替换或新增模型供应商成本很低。

  2. 服务端对模型输出做了二次强制校验,而不是单纯依赖 Prompt。 prompts.py 的系统提示词已经在文字层面约束了方向枚举、禁止精确距离、风险优先表达,但 minicpmo_runner.normalize_result() 又在服务端把 intent/direction/risk_level/confidence/proximity 逐一与白名单比对、非法值一律降级为“未确定”“medium”“low”等安全默认值,再叠加 sanitize_guidance() 用正则删除精确距离表达。这种“提示词约束 + 服务端再校验”的双保险设计,是这个项目最值得称赞的部分,避免了把安全边界完全交给模型自觉遵守。

  3. API 密钥处理符合最小暴露原则。 ModelProfile.public_dict() 只返回 api_key_configured 布尔值,从不回传密钥本身;server.py/health/models 端点也确认只暴露 public_profiles().env.gitignore 排除,.env.examplemodel_profiles.json 里也只放了占位符,README 明确提示“不要把真实密钥写入 Git”。

  4. 测试用 mock 隔离了外部依赖,且专门验证了安全兜底逻辑。 tests/test_model_api.pyunittest.mock.patch 替换 urllib.request.urlopen,不需要联网即可验证请求体、鉴权头和 profile 级别密钥覆盖;tests/test_runner.py 专门针对“非法方向被降级为未确定”“精确距离被清洗”“高风险提示必须前置”等安全规则写了断言,说明团队把安全行为当作一等公民纳入测试,而不是只测“功能跑得通”。

  5. 批量评测和示例素材的隐私意识到位。 benchmark.py 结合 examples/cases.json 的 10 个固定场景计算 json_parse_ratesafe_language_rate 等可复现指标;examples/README.md 明确要求测试图片“避免拍到可识别的人脸、支付码、手机号”,这是一个容易被忽略但很重要的细节。

3. 主要问题

3.1 server.py 的 FastAPI 接口在当前形态下不具备对外部署条件

server.py/infer/models/health 都没有任何鉴权、限流或 CORS 配置,parse_args() 默认 --host 0.0.0.0。如果按照 README 的方式直接部署到公网可访问的服务器,任何人都可以调用 /infer 消耗服务器上配置好的模型 API Key 额度,属于典型的密钥滥用/经济型 DoS 风险,而且当前没有任何机制能限制或发现这种滥用。

建议:

  • 至少加一个简单的 API Key / Bearer Token 校验中间件,未携带合法凭证时返回 401;
  • 增加基础限流(例如按 IP 或按 Token 的速率限制);
  • 在 README 的“启动本软件的 FastAPI 接口”一节明确写清楚“不要在没有鉴权的情况下把 --host 0.0.0.0 暴露到公网”,作为已知限制而非隐含假设。

3.2 上传文件缺少大小与类型校验

server.pyinfer() 直接 await image.read() 把整个上传文件读入内存再写临时文件,没有任何 Content-Length/大小上限检查,也没有对 image.content_type 做白名单校验。同时 OpenAICompatibleVisionClient.complete()mimetypes.guess_type(image_file.name)[0] or "image/jpeg" 会对任意非图片文件静默兜底成 image/jpeg 再转发给远程模型 API。

这带来两个具体风险:一是大文件上传可能造成内存/磁盘压力(没有并发和体积上限,容易被当作资源耗尽的攻击面);二是非图片文件会被悄悄当作图片转发给收费的模型 API,浪费调用成本且掩盖了客户端的异常输入。建议加 MAX_CONTENT_LENGTH 校验和基于文件头(而不仅是文件名后缀)的图片类型校验,超出限制时直接返回 4xx,而不是继续走到模型调用这一步。

3.3 上游错误信息被原样透传给调用方

model_api.pyOpenAICompatibleVisionClient._request_json() 在捕获 urllib.error.HTTPError 时,把 exc.read().decode(...) 的原始响应体拼进异常信息:

except urllib.error.HTTPError as exc:
    detail = exc.read().decode("utf-8", errors="replace")
    raise ModelAPIError(f"模型 API 返回 HTTP {exc.code}: {detail}") from exc

这个异常信息最终变成 InferenceResult.error,而 server.py/inferresult.error 原样放进响应体返回给调用方。结合 3.1 提到的“无鉴权”,任何调用者都能看到上游模型服务商返回的原始错误正文,其中可能包含账号信息、内部网关提示或其他不适合暴露给终端用户的细节。建议在返回给客户端之前只保留错误类别(如“上游服务暂不可用”),详细原文只记录到服务器日志。

3.4 model_profiles.json 仍支持明文 api_key 字段,但该文件被 Git 跟踪

ModelProfile.from_mapping() 里:

api_key_env = str(value.get("api_key_env") or "MODEL_API_KEY").strip()
explicit_key = str(value.get("api_key") or "")
api_key = os.getenv(api_key_env, explicit_key) if api_key_env else explicit_key

说明配置文件里直接写 "api_key": "xxx" 在代码层面是被允许的兜底路径,但 README 的配置项表格里并没有列出 api_key 这一项(只列了 api_key_env),也没有在文档里显式警告“不要在 model_profiles.json 里直接填密钥”。而 .gitignore 只排除了 .envmodel_profiles.json 本身是被跟踪的文件。一旦有开发者为了图方便,直接把真实密钥写进这个会被提交的 JSON 文件,就会造成密钥泄露进 Git 历史。建议二选一:要么在代码里直接移除/拒绝明文 api_key 字段并只允许 *_env 方式,要么在 README 和代码注释中都明确标注风险,并在 .gitignore 里为本地专用的密钥变体文件(如 model_profiles.local.json)预留一个不会被跟踪的命名约定。

3.5 安全清洗的字符串黑名单粒度太细,容易被模型的同义改写绕过

minicpmo_runner.sanitize_guidance() 的兜底替换是精确字符串匹配:

replacements = {
    "可以直接前进": "请保持谨慎并使用辅助工具确认后再移动",
    "可以继续前进": "未发现明显障碍,但请使用辅助工具确认后再移动",
    "伸出右手": "接近后请重新拍摄确认目标位置",
    "伸出左手": "接近后请重新拍摄确认目标位置",
}

这四个短语只是系统提示词里明确禁止表达的几种“标准写法”,但大模型的实际输出经常会用同义改写,例如“你可以放心往前走”“可以安全通过”“伸手去够一下”,这些都不会命中上述精确匹配,等于这层兜底在真实模型输出多样性面前召回率有限。PRECISE_DISTANCE_PATTERN 同理,只能捕获“数字/中文数字 + 米|厘米|cm|步|度”的组合,无法覆盖“几步之遥”“半米左右”“一臂距离”这类不含目标字符集但同样传递了精确暗示的口语表达。

建议把这两处的匹配逻辑从固定字符串/窄正则升级为更宽泛的模式匹配(例如用 直接.*?(前进|走|移动)(伸手|伸出).*?手 这类正则覆盖同义变体),并在 tests/test_runner.py 里补充一组“同义改写”用例,验证兜底层确实能拦截,而不仅仅验证系统提示词里给出的标准短语。

3.6 集成层(FastAPI 路由 / Gradio 回调)缺少自动化测试

现有 tests/test_model_api.pytests/test_runner.py 覆盖的是 model_api.pyminicpmo_runner.py 的单元逻辑,质量不错,但 server.py 的三个路由(/health/models/infer)和 app.pybuild_app() 内部的 run() 回调(image 为空、模型调用失败未解析、正常解析三条分支,对应 9 个输出组件的映射)目前没有任何自动化测试覆盖。这部分恰恰是终端用户实际触达、最容易在小改动中被破坏的集成层。建议至少用 fastapi.testclient.TestClient 覆盖 /infer 的正常/无图片/模型报错三种路径,并把 app.py 里的 run() 从闭包中提炼成可单独单元测试的纯函数(或直接对返回的 9 元组做断言),降低未来重构时静默破坏 UI 行为的风险。

4. 建议的优先级与验收方式

优先级 建议 可验证的验收结果
P0 /infer/models 加最基本的鉴权与限流,并在文档中明确“不要裸跑 --host 0.0.0.0 到公网” 未携带合法凭证的请求返回 401;短时间高频请求被限流拒绝
P0 上传文件加大小上限与基于内容而非文件名的类型校验 超限或非图片文件在进入模型调用前即返回 4xx
P1 清理上游错误信息透传,仅保留脱敏后的错误类别对外返回 人为构造上游 500 携带敏感文本,/infer 响应中不含原始 body
P1 收紧 model_profiles.json 的明文 api_key 支持,并完善 .gitignore/文档提示 代码扫描/仓库历史中不再出现真实密钥模式
P2 扩展 sanitize_guidancePRECISE_DISTANCE_PATTERN 的同义表达覆盖 新增同义改写用例(如“放心往前走”“半米左右”)在单测中被正确拦截
P2 server.py 路由和 app.pyrun() 回调补充集成/单元测试 CI 中新增测试覆盖三类关键分支并全部通过

5. 综合评价

“看见下一步”在架构分层(模型网关解耦)、安全行为的双重校验(提示词约束 + 服务端强制归一化)以及密钥最小暴露上,做得比同类 Demo 项目更扎实,测试也专门针对安全兜底逻辑写了断言,说明团队把“不能让模型胡说”当成了核心设计目标而不是事后补丁。

当前最值得优先投入的不是新增功能,而是把这套面向本地/评审环境设计得不错的安全模型,补齐到可以更放心对外提供服务的程度:给 FastAPI 接口加鉴权与限流、约束上传体积与类型、避免上游错误信息裸传、收紧配置文件里密钥字段的隐患,以及把安全清洗的字符串匹配升级为更抗同义改写的规则。完成这几项之后,再补上服务路由和 UI 回调层的自动化测试,项目会从“一个安全意识到位的 Demo”更进一步,成为一个经得起真实部署环境考验的辅助工具。


评测方:LIGHTNINGWHALE

> 评测账号:LIGHTNINGWHALE > 评测对象:`visionary/AI-Action-Assistant`(看见下一步 · 独立远程 API 版) > 评测日期:2026-08-06(Asia/Shanghai) > 评测方式:克隆完整仓库并逐文件阅读源代码(app.py、server.py、model_api.py、minicpmo_runner.py、prompts.py、benchmark.py、tests/、model_profiles.json、.gitignore、.env.example、PROJECT_PROPOSAL.md 等),未安装依赖、未启动服务、未调用任何真实模型 API。 ## 1. 项目理解 “看见下一步”是面向视障、低视力和视觉能力受限人群的行动辅助 Demo:用户上传图片或摄像头拍照,选择五类固定任务之一,软件通过 OpenAI 兼容的远程多模态模型 API 分析图片,把结果结构化为方向、风险等级、障碍物、OCR 文字等字段,并在浏览器中做中文语音播报。 项目的关键设计取舍是彻底与本地模型解耦:`app.py`(Gradio 界面)和 `server.py`(FastAPI 接口)都只依赖 `model_api.py` 提供的网关抽象,不加载权重、不依赖 torch/CUDA/NPU,这让软件本身可以独立安装、独立评审,也更容易替换或新增视觉模型供应商。 ## 2. 做得好的地方 1. **模型层抽象干净,业务与供应商解耦。** `model_api.py` 中的 `ModelProfile`、`OpenAICompatibleVisionClient`、`ModelGateway`、`load_model_gateway` 把“选哪个模型配置”“如何拼 endpoint”“如何做 HTTP 请求”“如何处理 CLI/环境变量覆盖优先级”拆得很清楚,`minicpmo_runner.py` 和 `server.py`/`app.py` 只依赖这一层公开接口,不关心具体供应商实现,替换或新增模型供应商成本很低。 2. **服务端对模型输出做了二次强制校验,而不是单纯依赖 Prompt。** `prompts.py` 的系统提示词已经在文字层面约束了方向枚举、禁止精确距离、风险优先表达,但 `minicpmo_runner.normalize_result()` 又在服务端把 `intent`/`direction`/`risk_level`/`confidence`/`proximity` 逐一与白名单比对、非法值一律降级为“未确定”“medium”“low”等安全默认值,再叠加 `sanitize_guidance()` 用正则删除精确距离表达。这种“提示词约束 + 服务端再校验”的双保险设计,是这个项目最值得称赞的部分,避免了把安全边界完全交给模型自觉遵守。 3. **API 密钥处理符合最小暴露原则。** `ModelProfile.public_dict()` 只返回 `api_key_configured` 布尔值,从不回传密钥本身;`server.py` 的 `/health`、`/models` 端点也确认只暴露 `public_profiles()`。`.env` 被 `.gitignore` 排除,`.env.example`、`model_profiles.json` 里也只放了占位符,README 明确提示“不要把真实密钥写入 Git”。 4. **测试用 mock 隔离了外部依赖,且专门验证了安全兜底逻辑。** `tests/test_model_api.py` 用 `unittest.mock.patch` 替换 `urllib.request.urlopen`,不需要联网即可验证请求体、鉴权头和 profile 级别密钥覆盖;`tests/test_runner.py` 专门针对“非法方向被降级为未确定”“精确距离被清洗”“高风险提示必须前置”等安全规则写了断言,说明团队把安全行为当作一等公民纳入测试,而不是只测“功能跑得通”。 5. **批量评测和示例素材的隐私意识到位。** `benchmark.py` 结合 `examples/cases.json` 的 10 个固定场景计算 `json_parse_rate`、`safe_language_rate` 等可复现指标;`examples/README.md` 明确要求测试图片“避免拍到可识别的人脸、支付码、手机号”,这是一个容易被忽略但很重要的细节。 ## 3. 主要问题 ### 3.1 `server.py` 的 FastAPI 接口在当前形态下不具备对外部署条件 `server.py` 的 `/infer`、`/models`、`/health` 都没有任何鉴权、限流或 CORS 配置,`parse_args()` 默认 `--host 0.0.0.0`。如果按照 README 的方式直接部署到公网可访问的服务器,任何人都可以调用 `/infer` 消耗服务器上配置好的模型 API Key 额度,属于典型的密钥滥用/经济型 DoS 风险,而且当前没有任何机制能限制或发现这种滥用。 建议: - 至少加一个简单的 API Key / Bearer Token 校验中间件,未携带合法凭证时返回 401; - 增加基础限流(例如按 IP 或按 Token 的速率限制); - 在 README 的“启动本软件的 FastAPI 接口”一节明确写清楚“不要在没有鉴权的情况下把 --host 0.0.0.0 暴露到公网”,作为已知限制而非隐含假设。 ### 3.2 上传文件缺少大小与类型校验 `server.py` 的 `infer()` 直接 `await image.read()` 把整个上传文件读入内存再写临时文件,没有任何 `Content-Length`/大小上限检查,也没有对 `image.content_type` 做白名单校验。同时 `OpenAICompatibleVisionClient.complete()` 里 `mimetypes.guess_type(image_file.name)[0] or "image/jpeg"` 会对任意非图片文件静默兜底成 `image/jpeg` 再转发给远程模型 API。 这带来两个具体风险:一是大文件上传可能造成内存/磁盘压力(没有并发和体积上限,容易被当作资源耗尽的攻击面);二是非图片文件会被悄悄当作图片转发给收费的模型 API,浪费调用成本且掩盖了客户端的异常输入。建议加 `MAX_CONTENT_LENGTH` 校验和基于文件头(而不仅是文件名后缀)的图片类型校验,超出限制时直接返回 4xx,而不是继续走到模型调用这一步。 ### 3.3 上游错误信息被原样透传给调用方 `model_api.py` 的 `OpenAICompatibleVisionClient._request_json()` 在捕获 `urllib.error.HTTPError` 时,把 `exc.read().decode(...)` 的原始响应体拼进异常信息: ```python except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") raise ModelAPIError(f"模型 API 返回 HTTP {exc.code}: {detail}") from exc ``` 这个异常信息最终变成 `InferenceResult.error`,而 `server.py` 的 `/infer` 把 `result.error` 原样放进响应体返回给调用方。结合 3.1 提到的“无鉴权”,任何调用者都能看到上游模型服务商返回的原始错误正文,其中可能包含账号信息、内部网关提示或其他不适合暴露给终端用户的细节。建议在返回给客户端之前只保留错误类别(如“上游服务暂不可用”),详细原文只记录到服务器日志。 ### 3.4 `model_profiles.json` 仍支持明文 `api_key` 字段,但该文件被 Git 跟踪 `ModelProfile.from_mapping()` 里: ```python api_key_env = str(value.get("api_key_env") or "MODEL_API_KEY").strip() explicit_key = str(value.get("api_key") or "") api_key = os.getenv(api_key_env, explicit_key) if api_key_env else explicit_key ``` 说明配置文件里直接写 `"api_key": "xxx"` 在代码层面是被允许的兜底路径,但 README 的配置项表格里并没有列出 `api_key` 这一项(只列了 `api_key_env`),也没有在文档里显式警告“不要在 `model_profiles.json` 里直接填密钥”。而 `.gitignore` 只排除了 `.env`,`model_profiles.json` 本身是被跟踪的文件。一旦有开发者为了图方便,直接把真实密钥写进这个会被提交的 JSON 文件,就会造成密钥泄露进 Git 历史。建议二选一:要么在代码里直接移除/拒绝明文 `api_key` 字段并只允许 `*_env` 方式,要么在 README 和代码注释中都明确标注风险,并在 `.gitignore` 里为本地专用的密钥变体文件(如 `model_profiles.local.json`)预留一个不会被跟踪的命名约定。 ### 3.5 安全清洗的字符串黑名单粒度太细,容易被模型的同义改写绕过 `minicpmo_runner.sanitize_guidance()` 的兜底替换是精确字符串匹配: ```python replacements = { "可以直接前进": "请保持谨慎并使用辅助工具确认后再移动", "可以继续前进": "未发现明显障碍,但请使用辅助工具确认后再移动", "伸出右手": "接近后请重新拍摄确认目标位置", "伸出左手": "接近后请重新拍摄确认目标位置", } ``` 这四个短语只是系统提示词里明确禁止表达的几种“标准写法”,但大模型的实际输出经常会用同义改写,例如“你可以放心往前走”“可以安全通过”“伸手去够一下”,这些都不会命中上述精确匹配,等于这层兜底在真实模型输出多样性面前召回率有限。`PRECISE_DISTANCE_PATTERN` 同理,只能捕获“数字/中文数字 + 米|厘米|cm|步|度”的组合,无法覆盖“几步之遥”“半米左右”“一臂距离”这类不含目标字符集但同样传递了精确暗示的口语表达。 建议把这两处的匹配逻辑从固定字符串/窄正则升级为更宽泛的模式匹配(例如用 `直接.*?(前进|走|移动)`、`(伸手|伸出).*?手` 这类正则覆盖同义变体),并在 `tests/test_runner.py` 里补充一组“同义改写”用例,验证兜底层确实能拦截,而不仅仅验证系统提示词里给出的标准短语。 ### 3.6 集成层(FastAPI 路由 / Gradio 回调)缺少自动化测试 现有 `tests/test_model_api.py`、`tests/test_runner.py` 覆盖的是 `model_api.py` 和 `minicpmo_runner.py` 的单元逻辑,质量不错,但 `server.py` 的三个路由(`/health`、`/models`、`/infer`)和 `app.py` 里 `build_app()` 内部的 `run()` 回调(image 为空、模型调用失败未解析、正常解析三条分支,对应 9 个输出组件的映射)目前没有任何自动化测试覆盖。这部分恰恰是终端用户实际触达、最容易在小改动中被破坏的集成层。建议至少用 `fastapi.testclient.TestClient` 覆盖 `/infer` 的正常/无图片/模型报错三种路径,并把 `app.py` 里的 `run()` 从闭包中提炼成可单独单元测试的纯函数(或直接对返回的 9 元组做断言),降低未来重构时静默破坏 UI 行为的风险。 ## 4. 建议的优先级与验收方式 | 优先级 | 建议 | 可验证的验收结果 | | --- | --- | --- | | P0 | 为 `/infer`、`/models` 加最基本的鉴权与限流,并在文档中明确“不要裸跑 `--host 0.0.0.0` 到公网” | 未携带合法凭证的请求返回 401;短时间高频请求被限流拒绝 | | P0 | 上传文件加大小上限与基于内容而非文件名的类型校验 | 超限或非图片文件在进入模型调用前即返回 4xx | | P1 | 清理上游错误信息透传,仅保留脱敏后的错误类别对外返回 | 人为构造上游 500 携带敏感文本,`/infer` 响应中不含原始 body | | P1 | 收紧 `model_profiles.json` 的明文 `api_key` 支持,并完善 `.gitignore`/文档提示 | 代码扫描/仓库历史中不再出现真实密钥模式 | | P2 | 扩展 `sanitize_guidance` 与 `PRECISE_DISTANCE_PATTERN` 的同义表达覆盖 | 新增同义改写用例(如“放心往前走”“半米左右”)在单测中被正确拦截 | | P2 | 为 `server.py` 路由和 `app.py` 的 `run()` 回调补充集成/单元测试 | CI 中新增测试覆盖三类关键分支并全部通过 | ## 5. 综合评价 “看见下一步”在架构分层(模型网关解耦)、安全行为的双重校验(提示词约束 + 服务端强制归一化)以及密钥最小暴露上,做得比同类 Demo 项目更扎实,测试也专门针对安全兜底逻辑写了断言,说明团队把“不能让模型胡说”当成了核心设计目标而不是事后补丁。 当前最值得优先投入的不是新增功能,而是把这套面向本地/评审环境设计得不错的安全模型,补齐到可以更放心对外提供服务的程度:给 FastAPI 接口加鉴权与限流、约束上传体积与类型、避免上游错误信息裸传、收紧配置文件里密钥字段的隐患,以及把安全清洗的字符串匹配升级为更抗同义改写的规则。完成这几项之后,再补上服务路由和 UI 回调层的自动化测试,项目会从“一个安全意识到位的 Demo”更进一步,成为一个经得起真实部署环境考验的辅助工具。 --- 评测方:LIGHTNINGWHALE
Owner

感谢这份非常具体的交叉评审。评审对项目当前实现的理解基本准确,尤其是模型网关解耦、Prompt 约束与服务端归一化的双重校验、密钥最小暴露以及安全单测部分。下面对主要问题逐项回应。

  1. 关于 FastAPI 接口鉴权与限流
    这条意见成立。当前 server.py 的定位更偏向本地运行和评审环境,默认监听 0.0.0.0 是为了方便平台端口转发和局域网访问,但这不等于具备公网生产部署条件。
    当前版本确实没有对 /infer、/models 做访问令牌校验,也没有基础限流。下一步会增加:
    可选的 Bearer Token/API Key 中间件;
    /infer 按 IP 或 Token 的基础限流;
    /models 只返回必要的配置摘要;
    /health 保留最小化健康状态,不暴露模型配置细节;
    README 明确声明:未启用鉴权时只能本机或受控内网使用,不应直接将 --host 0.0.0.0 暴露到公网。
    验收标准为:未携带合法凭证的推理请求返回 401,超出频率的请求返回 429,模型 API Key 不会因为接口被滥用而直接暴露。
  2. 关于上传文件大小和类型校验
    这条意见也成立。目前 infer() 会先读取完整上传内容,再交给临时文件和远程模型网关,缺少统一的文件大小上限、Content-Type 白名单和文件头校验。
    下一步会在进入模型调用前增加:
    可配置的最大上传大小;
    image/jpeg、image/png、image/webp 等允许类型校验;
    基于文件内容的图片格式识别,而不是只相信文件名后缀;
    对超大文件、非图片文件和损坏图片直接返回 4xx;
    对临时文件创建和并发请求增加资源保护。
    评审指出的“非图片文件被静默当成 JPEG 转发”确实是当前实现需要修正的地方,后续会增加“上游模型未被调用”的测试断言,确保异常输入在网关之前被拦截。
  3. 关于上游错误信息透传
    这条问题成立。当前 model_api.py 为了便于本地调试,会把上游 HTTP 错误正文拼入异常;server.py 又将 result.error 放入响应体。对于公开服务,这可能泄露上游网关、账户或供应商内部信息。
    后续会拆分内部错误和外部错误:
    服务端日志记录详细异常,但对密钥、请求头和上游响应中的敏感字段脱敏;
    对外只返回稳定的错误类别,例如“模型服务暂不可用”“模型配置错误”“请求超时”;
    不把上游原始响应体直接返回给客户端;
    增加构造 HTTP 500/错误正文的回归测试,确保响应中不出现原始上游内容。
  4. 关于 model_profiles.json 中的明文 api_key
    这条意见成立。当前 ModelProfile.from_mapping() 为了兼容某些本地部署方式,仍允许配置文件中的 api_key 作为兜底,但这确实会让开发者误把密钥写入被 Git 跟踪的 JSON 文件。
    当前仓库中的 model_profiles.json 只使用占位符,真实密钥通过环境变量读取;但从代码能力上允许明文字段,仍然存在误用风险。后续会采取更严格的方案:
    默认拒绝 model_profiles.json 中的明文 api_key;
    只允许 api_key_env 指向服务器环境变量;
    为本地私有配置增加 model_profiles.local.json 示例,并加入 .gitignore;
    README 明确说明:不能把真实密钥写入 model_profiles.json、.env.example 或 Git 历史;
    增加配置扫描,发现疑似密钥时在启动或 CI 阶段提示失败。
  5. 关于安全清洗的同义表达覆盖
    完全同意。当前 sanitize_guidance() 对部分危险表达采用精确字符串替换,PRECISE_DISTANCE_PATTERN 也主要覆盖数字、米、厘米、步数和角度等标准形式。对于“放心往前走”“可以安全通过”“几步之遥”“一臂距离”等同义改写,当前覆盖不充分。
    后续会把安全清洗拆成更明确的规则组:
    绝对安全承诺:覆盖“放心通过”“可以安全前进”“直接走”等同义表达;
    伸手或抓取指令:覆盖“伸手去拿”“把手伸过去”等表达;
    精确距离与步数:覆盖“半米左右”“几步之遥”“一臂距离”等口语表达;
    高风险场景:统一要求停下、重新拍摄或寻求人工确认。
    同时在 tests/test_runner.py 中增加同义改写、大小写/标点变化和混合中英文表达的用例,避免只对 Prompt 中列出的标准短语做测试。
  6. 关于 FastAPI 和 Gradio 集成测试
    这条意见成立。当前测试重点放在 model_api.py 和 minicpmo_runner.py 的可控单元逻辑,集成层还没有正式纳入测试文件。此前虽然做过 /health、/models、/infer 的本地 Mock 冒烟验证,但还不能替代 CI 中的正式回归测试。
    后续会补充:
    /health、/models 的响应和敏感字段检查;
    /infer 正常解析、无图片、上游错误、非法文件和超大文件分支;
    校验模型网关在非法输入时不会被调用;
    Gradio 回调的空输入、未解析结果和正常结果三条路径;
    对九个 UI 输出字段进行固定结构断言。
  7. 阶段性验收计划
    优先级 验收项 通过条件
    P0 接口鉴权与限流 未授权请求返回 401,频率超限返回 429
    P0 文件校验 超大、非图片、损坏图片在调用模型前返回 4xx
    P1 错误脱敏 对外响应不包含上游原始错误正文、密钥或内部请求信息
    P1 配置密钥安全 禁止在主配置中直接保存真实 api_key,本地配置不进入 Git
    P2 安全同义改写 “放心往前走”“半米左右”等表达被正确降级或清洗
    P2 集成回归 FastAPI 三类关键分支和 Gradio 三类回调分支全部有自动化测试

再次感谢评审指出这些问题。当前版本的目标是先实现一个可以独立运行、可替换远程模型、具备安全降级的评审 Demo,并不宣称已经具备公网生产部署条件。下一阶段会优先补齐接口保护、输入校验、错误脱敏和集成测试,再继续扩展连续帧和真实用户测试能力。

感谢这份非常具体的交叉评审。评审对项目当前实现的理解基本准确,尤其是模型网关解耦、Prompt 约束与服务端归一化的双重校验、密钥最小暴露以及安全单测部分。下面对主要问题逐项回应。 1. 关于 FastAPI 接口鉴权与限流 这条意见成立。当前 server.py 的定位更偏向本地运行和评审环境,默认监听 0.0.0.0 是为了方便平台端口转发和局域网访问,但这不等于具备公网生产部署条件。 当前版本确实没有对 /infer、/models 做访问令牌校验,也没有基础限流。下一步会增加: 可选的 Bearer Token/API Key 中间件; /infer 按 IP 或 Token 的基础限流; /models 只返回必要的配置摘要; /health 保留最小化健康状态,不暴露模型配置细节; README 明确声明:未启用鉴权时只能本机或受控内网使用,不应直接将 --host 0.0.0.0 暴露到公网。 验收标准为:未携带合法凭证的推理请求返回 401,超出频率的请求返回 429,模型 API Key 不会因为接口被滥用而直接暴露。 2. 关于上传文件大小和类型校验 这条意见也成立。目前 infer() 会先读取完整上传内容,再交给临时文件和远程模型网关,缺少统一的文件大小上限、Content-Type 白名单和文件头校验。 下一步会在进入模型调用前增加: 可配置的最大上传大小; image/jpeg、image/png、image/webp 等允许类型校验; 基于文件内容的图片格式识别,而不是只相信文件名后缀; 对超大文件、非图片文件和损坏图片直接返回 4xx; 对临时文件创建和并发请求增加资源保护。 评审指出的“非图片文件被静默当成 JPEG 转发”确实是当前实现需要修正的地方,后续会增加“上游模型未被调用”的测试断言,确保异常输入在网关之前被拦截。 3. 关于上游错误信息透传 这条问题成立。当前 model_api.py 为了便于本地调试,会把上游 HTTP 错误正文拼入异常;server.py 又将 result.error 放入响应体。对于公开服务,这可能泄露上游网关、账户或供应商内部信息。 后续会拆分内部错误和外部错误: 服务端日志记录详细异常,但对密钥、请求头和上游响应中的敏感字段脱敏; 对外只返回稳定的错误类别,例如“模型服务暂不可用”“模型配置错误”“请求超时”; 不把上游原始响应体直接返回给客户端; 增加构造 HTTP 500/错误正文的回归测试,确保响应中不出现原始上游内容。 4. 关于 model_profiles.json 中的明文 api_key 这条意见成立。当前 ModelProfile.from_mapping() 为了兼容某些本地部署方式,仍允许配置文件中的 api_key 作为兜底,但这确实会让开发者误把密钥写入被 Git 跟踪的 JSON 文件。 当前仓库中的 model_profiles.json 只使用占位符,真实密钥通过环境变量读取;但从代码能力上允许明文字段,仍然存在误用风险。后续会采取更严格的方案: 默认拒绝 model_profiles.json 中的明文 api_key; 只允许 api_key_env 指向服务器环境变量; 为本地私有配置增加 model_profiles.local.json 示例,并加入 .gitignore; README 明确说明:不能把真实密钥写入 model_profiles.json、.env.example 或 Git 历史; 增加配置扫描,发现疑似密钥时在启动或 CI 阶段提示失败。 5. 关于安全清洗的同义表达覆盖 完全同意。当前 sanitize_guidance() 对部分危险表达采用精确字符串替换,PRECISE_DISTANCE_PATTERN 也主要覆盖数字、米、厘米、步数和角度等标准形式。对于“放心往前走”“可以安全通过”“几步之遥”“一臂距离”等同义改写,当前覆盖不充分。 后续会把安全清洗拆成更明确的规则组: 绝对安全承诺:覆盖“放心通过”“可以安全前进”“直接走”等同义表达; 伸手或抓取指令:覆盖“伸手去拿”“把手伸过去”等表达; 精确距离与步数:覆盖“半米左右”“几步之遥”“一臂距离”等口语表达; 高风险场景:统一要求停下、重新拍摄或寻求人工确认。 同时在 tests/test_runner.py 中增加同义改写、大小写/标点变化和混合中英文表达的用例,避免只对 Prompt 中列出的标准短语做测试。 6. 关于 FastAPI 和 Gradio 集成测试 这条意见成立。当前测试重点放在 model_api.py 和 minicpmo_runner.py 的可控单元逻辑,集成层还没有正式纳入测试文件。此前虽然做过 /health、/models、/infer 的本地 Mock 冒烟验证,但还不能替代 CI 中的正式回归测试。 后续会补充: /health、/models 的响应和敏感字段检查; /infer 正常解析、无图片、上游错误、非法文件和超大文件分支; 校验模型网关在非法输入时不会被调用; Gradio 回调的空输入、未解析结果和正常结果三条路径; 对九个 UI 输出字段进行固定结构断言。 7. 阶段性验收计划 优先级 验收项 通过条件 P0 接口鉴权与限流 未授权请求返回 401,频率超限返回 429 P0 文件校验 超大、非图片、损坏图片在调用模型前返回 4xx P1 错误脱敏 对外响应不包含上游原始错误正文、密钥或内部请求信息 P1 配置密钥安全 禁止在主配置中直接保存真实 api_key,本地配置不进入 Git P2 安全同义改写 “放心往前走”“半米左右”等表达被正确降级或清洗 P2 集成回归 FastAPI 三类关键分支和 Gradio 三类回调分支全部有自动化测试 再次感谢评审指出这些问题。当前版本的目标是先实现一个可以独立运行、可替换远程模型、具备安全降级的评审 Demo,并不宣称已经具备公网生产部署条件。下一阶段会优先补齐接口保护、输入校验、错误脱敏和集成测试,再继续扩展连续帧和真实用户测试能力。
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
visionary/AI-Action-Assistant#4
No description provided.