IOcrPort 提供多种便捷输入,但便利并不等于安全。Adapter 负责调用 Tesseract SDK 和 DTO 映射;文件授权、输入预算、SSRF、恶意图像处理与输出保留由调用方负责。
1. 真实方法表
| 方法 | 输入 | 当前基础验证 |
|---|---|---|
ExtractTextFromFileAsync | 本地路径 | 非空 |
ExtractTextFromBytesAsync | byte[] | SDK 处理 |
ExtractTextFromBase64Async | Base64 | SDK 处理 |
ExtractTextFromUrlAsync | URL 字符串 | 非空 |
RecognizeRegionAsync | 路径与矩形 | 路径非空、坐标非负、宽高为正 |
GenerateCaptcha | 宽、高、长度 | 同步 SDK 调用 |
RecognizeCaptchaAsync | byte[] | SDK 处理 |
Port 没有语言提示、MIME、最大字节、最大像素或 owner 标识。文档和示例不能虚构这些参数。
2. 推荐输入路径
优先由 owner 模块读取经过授权和扫描的文件,再向 Port 传有界字节。不要让外部请求直接选择服务器路径或 URL。
// 先建立可信调用者与文件归属边界。// ① 从认证上下文取得租户/用户,禁止客户端覆盖。FileAsset asset = await files.RequireReadableAsync( currentTenant.Id, currentUser.Id, command.FileAssetId, ct);
// ② 只允许已完成、扫描通过且在 OCR 类型/大小预算内的对象。ocrPolicy.EnsureEligible(asset);
// ③ 有界读取;当前 Port 要求 byte[],会占用连续内存。// 再把经过预算限制的字节交给 OCR Port。byte[] image = await files.ReadBytesAsync(asset, ocrPolicy.MaxBytes, ct);
// ④ Adapter 不再重复授权或扫描。Result<OcrResultDto> result = await ocr.ExtractTextFromBytesAsync(image, ct);3. 本地路径风险
ExtractTextFromFileAsync 和 RecognizeRegionAsync 接受任意路径。只验证非空意味着调用者可能读取进程可访问的配置、secret、挂载盘或符号链接目标。
若必须保留路径 API,调用层应把逻辑对象 ID 解析到受控根目录,使用 canonical path 比较,拒绝符号链接/设备文件,并在打开后校验实际文件元数据。更稳妥的业务接口只接受对象 ID。
4. URL 风险
ExtractTextFromUrlAsync 把 URL 交给 SDK,当前 Adapter 没有 scheme、域名、DNS、重定向或响应大小策略,具备 SSRF 和资源耗尽风险。
生产系统应自行下载到隔离缓冲区:每跳验证 URL,限制连接/总超时、响应字节、Content-Type、重定向数和压缩比;完成安全校验后改用 bytes 方法。
5. 字节、Base64 与图像炸弹
字节数组和 Base64 没有长度上限。Base64 还会产生额外编码和解码内存。小文件可能声明极大尺寸或恶意压缩,触发解码器 CPU/内存耗尽。
输入策略至少限制:
- 上传字节和 Base64 字符数;
- 解码后的宽、高、总像素和帧数;
- 允许的实际 magic bytes,不只信任扩展名/MIME;
- 压缩比、色深和元数据大小;
- 单租户并发、CPU 时间和队列长度;
- 已知脆弱格式和库版本。
恶意内容扫描不能只写在文档里;当前 Files finalize 也没有扫描保证,owner 必须以真实扫描状态为准。
6. 区域识别
当前只保证 x/y 非负、width/height 大于零。它不读取图像尺寸,因此没有验证矩形是否越界,也未保护 x + width 的整数溢出。
// ① 只读取受控图像头信息,不先完整解码。ImageInfo info = await imageInspector.ReadHeaderAsync(asset, ct);
// 使用 long 计算,避免 int 相加溢出。long right = (long)command.X + command.Width;long bottom = (long)command.Y + command.Height;
// ② 同时验证原点、正尺寸和图像右/下边界。if (command.X < 0 || command.Y < 0 || command.Width <= 0 || command.Height <= 0 || right > info.Width || bottom > info.Height) return OcrErrors.InvalidRegion;7. 结果语义
Adapter 在 SDK Success=false 时返回 failure;因此成功的 OcrResultDto.Success 总为 true,字段在成功分支中是冗余的。
DTO 还包含 Text、RawText、Confidence 和 Regions。它们都是供应商输出:
- 文本可能包含 PII、法律特权或商业秘密;
- RawText 可能比展示文本更敏感;
- 低置信度不是错误,需要业务阈值/人工复核;
- Regions 可泄露版面结构;
- 输出长度应限制,日志不得记录正文。
8. 异常边界
Adapter 捕获 Tesseract、Leptonica 与 IO 异常。URL、Base64、参数、HTTP、内存或其他运行时异常可能逃逸。
错误描述目前可能包含 SDK 原始消息。Endpoint 应返回稳定问题码,把详细诊断写入受限日志,并携带 correlation ID。OperationCanceledException 应保持取消语义,不映射成供应商失败。
9. 验证码同步例外
GenerateCaptcha 是唯一同步且不返回 Result<T> 的方法。Unavailable 与 Adapter 的部分失败都会抛 InvalidOperationException。
try{ // 当前签名没有 CancellationToken,也不是 fail-closed Result。 CaptchaResultDto captcha = ocr.GenerateCaptcha(width: 240, height: 80, length: 6); return Ok(captcha);}catch (InvalidOperationException ex){ // 在 owner 边界把同步异常规范为稳定错误,并保留受限诊断日志。 logger.LogWarning(ex, "OCR captcha is unavailable"); return Problem(OcrErrors.ProviderUnavailable);}长期应将该方法改为异步 Result<CaptchaResultDto> 或拆成独立服务,统一错误与容量治理。
10. 数据治理
必须明确输入和 OCR 输出的数据分类、处理目的、供应商位置、保留期、删除责任和审计。若 OCR 在本机运行,也要管控临时文件、core dump、日志和诊断采样。
不要默认持久化 RawText。优先只保存业务需要的结构化字段、置信度和来源版本;需要人工复核时记录谁查看、谁纠正以及模型/语言包版本。
11. 测试矩阵
测试应包含空/巨大 bytes、非法 Base64、伪造 MIME、截断图像、多帧/超大尺寸、解压炸弹、路径穿越/符号链接、SSRF/重定向、区域越界/溢出、取消、低置信度、敏感输出不入日志,以及 unavailable Captcha 抛错。
使用小型许可测试样本;模糊测试限制 CPU/内存并隔离进程。真实语言包应有版本固定的金丝雀样本,避免升级后静默改变识别质量。
12. 运维指标与核查
按输入类别记录排队/执行耗时、拒绝原因、成功率、低置信度率、引擎异常、内存峰值和语言包版本。标签不得包含文本、路径或 URL。
rg -n "ExtractText|RecognizeRegion|GenerateCaptcha|catch" \ src/Platform/ToolConnectors/BitzOrcas.Platform.ToolConnectors.Infrastructure/Ocr -g '*.cs'