【交叉评测】看见下一步 / AI-Action-Assistant:安全清洗、密钥治理与生产可用性 #4
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
1. 项目理解
“看见下一步”是面向视障、低视力和视觉能力受限人群的行动辅助 Demo:用户上传图片或摄像头拍照,选择五类固定任务之一,软件通过 OpenAI 兼容的远程多模态模型 API 分析图片,把结果结构化为方向、风险等级、障碍物、OCR 文字等字段,并在浏览器中做中文语音播报。
项目的关键设计取舍是彻底与本地模型解耦:
app.py(Gradio 界面)和server.py(FastAPI 接口)都只依赖model_api.py提供的网关抽象,不加载权重、不依赖 torch/CUDA/NPU,这让软件本身可以独立安装、独立评审,也更容易替换或新增视觉模型供应商。2. 做得好的地方
模型层抽象干净,业务与供应商解耦。
model_api.py中的ModelProfile、OpenAICompatibleVisionClient、ModelGateway、load_model_gateway把“选哪个模型配置”“如何拼 endpoint”“如何做 HTTP 请求”“如何处理 CLI/环境变量覆盖优先级”拆得很清楚,minicpmo_runner.py和server.py/app.py只依赖这一层公开接口,不关心具体供应商实现,替换或新增模型供应商成本很低。服务端对模型输出做了二次强制校验,而不是单纯依赖 Prompt。
prompts.py的系统提示词已经在文字层面约束了方向枚举、禁止精确距离、风险优先表达,但minicpmo_runner.normalize_result()又在服务端把intent/direction/risk_level/confidence/proximity逐一与白名单比对、非法值一律降级为“未确定”“medium”“low”等安全默认值,再叠加sanitize_guidance()用正则删除精确距离表达。这种“提示词约束 + 服务端再校验”的双保险设计,是这个项目最值得称赞的部分,避免了把安全边界完全交给模型自觉遵守。API 密钥处理符合最小暴露原则。
ModelProfile.public_dict()只返回api_key_configured布尔值,从不回传密钥本身;server.py的/health、/models端点也确认只暴露public_profiles()。.env被.gitignore排除,.env.example、model_profiles.json里也只放了占位符,README 明确提示“不要把真实密钥写入 Git”。测试用 mock 隔离了外部依赖,且专门验证了安全兜底逻辑。
tests/test_model_api.py用unittest.mock.patch替换urllib.request.urlopen,不需要联网即可验证请求体、鉴权头和 profile 级别密钥覆盖;tests/test_runner.py专门针对“非法方向被降级为未确定”“精确距离被清洗”“高风险提示必须前置”等安全规则写了断言,说明团队把安全行为当作一等公民纳入测试,而不是只测“功能跑得通”。批量评测和示例素材的隐私意识到位。
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 风险,而且当前没有任何机制能限制或发现这种滥用。建议:
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(...)的原始响应体拼进异常信息:这个异常信息最终变成
InferenceResult.error,而server.py的/infer把result.error原样放进响应体返回给调用方。结合 3.1 提到的“无鉴权”,任何调用者都能看到上游模型服务商返回的原始错误正文,其中可能包含账号信息、内部网关提示或其他不适合暴露给终端用户的细节。建议在返回给客户端之前只保留错误类别(如“上游服务暂不可用”),详细原文只记录到服务器日志。3.4
model_profiles.json仍支持明文api_key字段,但该文件被 Git 跟踪ModelProfile.from_mapping()里:说明配置文件里直接写
"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()的兜底替换是精确字符串匹配:这四个短语只是系统提示词里明确禁止表达的几种“标准写法”,但大模型的实际输出经常会用同义改写,例如“你可以放心往前走”“可以安全通过”“伸手去够一下”,这些都不会命中上述精确匹配,等于这层兜底在真实模型输出多样性面前召回率有限。
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. 建议的优先级与验收方式
/infer、/models加最基本的鉴权与限流,并在文档中明确“不要裸跑--host 0.0.0.0到公网”/infer响应中不含原始 bodymodel_profiles.json的明文api_key支持,并完善.gitignore/文档提示sanitize_guidance与PRECISE_DISTANCE_PATTERN的同义表达覆盖server.py路由和app.py的run()回调补充集成/单元测试5. 综合评价
“看见下一步”在架构分层(模型网关解耦)、安全行为的双重校验(提示词约束 + 服务端强制归一化)以及密钥最小暴露上,做得比同类 Demo 项目更扎实,测试也专门针对安全兜底逻辑写了断言,说明团队把“不能让模型胡说”当成了核心设计目标而不是事后补丁。
当前最值得优先投入的不是新增功能,而是把这套面向本地/评审环境设计得不错的安全模型,补齐到可以更放心对外提供服务的程度:给 FastAPI 接口加鉴权与限流、约束上传体积与类型、避免上游错误信息裸传、收紧配置文件里密钥字段的隐患,以及把安全清洗的字符串匹配升级为更抗同义改写的规则。完成这几项之后,再补上服务路由和 UI 回调层的自动化测试,项目会从“一个安全意识到位的 Demo”更进一步,成为一个经得起真实部署环境考验的辅助工具。
评测方:LIGHTNINGWHALE
感谢这份非常具体的交叉评审。评审对项目当前实现的理解基本准确,尤其是模型网关解耦、Prompt 约束与服务端归一化的双重校验、密钥最小暴露以及安全单测部分。下面对主要问题逐项回应。
这条意见成立。当前 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 不会因为接口被滥用而直接暴露。
这条意见也成立。目前 infer() 会先读取完整上传内容,再交给临时文件和远程模型网关,缺少统一的文件大小上限、Content-Type 白名单和文件头校验。
下一步会在进入模型调用前增加:
可配置的最大上传大小;
image/jpeg、image/png、image/webp 等允许类型校验;
基于文件内容的图片格式识别,而不是只相信文件名后缀;
对超大文件、非图片文件和损坏图片直接返回 4xx;
对临时文件创建和并发请求增加资源保护。
评审指出的“非图片文件被静默当成 JPEG 转发”确实是当前实现需要修正的地方,后续会增加“上游模型未被调用”的测试断言,确保异常输入在网关之前被拦截。
这条问题成立。当前 model_api.py 为了便于本地调试,会把上游 HTTP 错误正文拼入异常;server.py 又将 result.error 放入响应体。对于公开服务,这可能泄露上游网关、账户或供应商内部信息。
后续会拆分内部错误和外部错误:
服务端日志记录详细异常,但对密钥、请求头和上游响应中的敏感字段脱敏;
对外只返回稳定的错误类别,例如“模型服务暂不可用”“模型配置错误”“请求超时”;
不把上游原始响应体直接返回给客户端;
增加构造 HTTP 500/错误正文的回归测试,确保响应中不出现原始上游内容。
这条意见成立。当前 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 阶段提示失败。
完全同意。当前 sanitize_guidance() 对部分危险表达采用精确字符串替换,PRECISE_DISTANCE_PATTERN 也主要覆盖数字、米、厘米、步数和角度等标准形式。对于“放心往前走”“可以安全通过”“几步之遥”“一臂距离”等同义改写,当前覆盖不充分。
后续会把安全清洗拆成更明确的规则组:
绝对安全承诺:覆盖“放心通过”“可以安全前进”“直接走”等同义表达;
伸手或抓取指令:覆盖“伸手去拿”“把手伸过去”等表达;
精确距离与步数:覆盖“半米左右”“几步之遥”“一臂距离”等口语表达;
高风险场景:统一要求停下、重新拍摄或寻求人工确认。
同时在 tests/test_runner.py 中增加同义改写、大小写/标点变化和混合中英文表达的用例,避免只对 Prompt 中列出的标准短语做测试。
这条意见成立。当前测试重点放在 model_api.py 和 minicpmo_runner.py 的可控单元逻辑,集成层还没有正式纳入测试文件。此前虽然做过 /health、/models、/infer 的本地 Mock 冒烟验证,但还不能替代 CI 中的正式回归测试。
后续会补充:
/health、/models 的响应和敏感字段检查;
/infer 正常解析、无图片、上游错误、非法文件和超大文件分支;
校验模型网关在非法输入时不会被调用;
Gradio 回调的空输入、未解析结果和正常结果三条路径;
对九个 UI 输出字段进行固定结构断言。
优先级 验收项 通过条件
P0 接口鉴权与限流 未授权请求返回 401,频率超限返回 429
P0 文件校验 超大、非图片、损坏图片在调用模型前返回 4xx
P1 错误脱敏 对外响应不包含上游原始错误正文、密钥或内部请求信息
P1 配置密钥安全 禁止在主配置中直接保存真实 api_key,本地配置不进入 Git
P2 安全同义改写 “放心往前走”“半米左右”等表达被正确降级或清洗
P2 集成回归 FastAPI 三类关键分支和 Gradio 三类回调分支全部有自动化测试
再次感谢评审指出这些问题。当前版本的目标是先实现一个可以独立运行、可替换远程模型、具备安全降级的评审 Demo,并不宣称已经具备公网生产部署条件。下一阶段会优先补齐接口保护、输入校验、错误脱敏和集成测试,再继续扩展连续帧和真实用户测试能力。