OpenMed 邮件 PHI 脱敏实战:EML/MSG 解析、头部与正文清洗、附件安全重写的本地化实现
OpenMed 邮件 PHI 脱敏实战EML/MSG 解析、头部与正文清洗、附件安全重写的本地化实现【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed导读本文讲解 OpenMed 多模态子系统中针对电子邮件.eml与可选.msg的 PHI 提取与脱敏能力如何用纯 Python 标准库解析 RFC 5322/MIME 邮件、把解码后的头部与正文文本映射回源段source spans并输出一份从头部、纯文本、HTML、附件文件名到附件内容全部清洗干净的 EML。读完本文你将掌握extract_email/redact_email/redact_document的完整用法、底层脱敏管线用户检测器 确定性安全扫尾的运作方式以及 Outlook MSG 隔离桥接的合规边界可直接用于构建本地、无网络、不落盘临时文件的邮件 PHI 处理流水线。功能定位与适用范围OpenMed 可以解析 RFC 5322/MIME 格式的.eml消息将解码后的头部与正文文本映射回源段并产出一份干净的 EML——PHI 会从头部、纯文本、HTML、附件文件名以及受支持的附件内容中被移除。EML 解析仅依赖 Python 标准库因此基础安装即可使用requires_multimodalFalse见 email.py 的处理器注册只有消息中可能携带 PDF、DOCX、PPTX 或位图附件时才需要安装multimodal附加依赖。从源码结构看整个能力收敛在 email.py 一个模块中对外暴露三个核心 API模块导出列表API作用关键返回类型extract_email(source)提取解码后的头部与正文返回带字符偏移映射的归一化文档ExtractedDocumentredact_email(source, output_path..., models..., lang...)脱敏头部、正文与附件序列化干净 EMLRedactedEmailredact_document(path, ...)通用文档分发器识别.eml/.msg扩展名并路由到邮件处理器ExtractedDocument三者都接受路径、原始字节bytes/bytearray或带name属性的可寻址二进制流作为输入_read_source_bytes这与多模态子系统的通用摄取契约 ExtractedDocument 完全一致。提取归一化文本extract_email基础用法from openmed.multimodal import extract_email message extract_email(synthetic-message.eml) print(len(message.text), message.metadata[body_part_count]) for span in message.spans: print(span.start, span.end, span.metadata[block_type])返回的归一化文档包括解码后的From、To、Cc、Bcc、Reply-To、Sender、Subject、路由/引用类头部以及Date头部再加上text/plain正文和可见的text/html内容。HTML 段落的每个 span 都带有本地源码偏移html_source_start/html_source_end可用于将归一化文本中的任意字符位置映射回原始 HTML 字节位置。实际提取的头部集合源码中的_EXTRACTED_HEADERS常量email.py明确了提取范围共 13 类头部From、To、Cc、Bcc、Reply-To、Sender、Subject、Received、Return-Path、Message-ID、In-Reply-To、References、Date在归一化文档中每个头部段落的metadata携带block_type: header、header_name小写和header_index同名头部出现多次时的序号正文段落则标记为block_type: body并记录part_index与content_type_document_from_message。HTML 可见文本抽取的细节script、style、template三类标签的内容被完全排除在可见文本之外_HTML_IGNORED_TAGSemail.py块级标签p、div、table、li、h1-h6等共 37 个email.py之间插入换行分隔符保证抽取出的文本段落结构可读字符实体name;/#code;被反转义为真实字符同时记录原始字节范围确保偏移映射不因实体编码而漂移_HtmlTextParser。畸形头部的容错extract_email对损坏的邮件头不会崩溃测试test_malformed_address_header_is_extracted_and_redacted_without_crashingtests/unit/multimodal/test_email.py验证了From: Synthetic Clinic clinic这类残缺地址仍能被原样提取、参与脱敏并在最后被替换为安全的占位地址。输出脱敏 EMLredact_email安装多模态附加依赖当邮件可能携带 PDF、DOCX、PPTX 或栅格图片附件时先安装多模态附加依赖uv pip install openmed[multimodal]该附加依赖在 pyproject.toml 中声明包含pdfplumber、python-docx、python-pptx、Pillow、pikepdf、pydicom、pytesseract、easyocr、onnx等解析与 OCR 后端。基础用法from openmed import extract_pii from openmed.multimodal import redact_email result redact_email( synthetic-message.eml, output_pathsynthetic-message.clean.eml, models{detector: extract_pii}, langen, ) print(result.header_redaction_count) print(result.body_redaction_count) print([attachment.to_dict() for attachment in result.attachments])models参数接受 OpenMed PII 检测器/脱敏器对象或一个映射映射中可提供detector/extract_pii键并可选用text_redactor键_resolve_detector、_resolve_text_redactor。lang为 OpenMed 语言代码默认en。policy参数则接受策略名字符串或映射映射中的method/deidentify_method与deidentify_policy会被解析_redaction_policy。RedactedEmail返回对象的字段email.py字段含义email_bytes序列化后的干净 EML 字节document干净消息对应的归一化文档ExtractedDocumentsource_format输入格式eml或msgheader_redaction_count头部发生的脱敏/移除次数body_redaction_count正文部分发生变化的次数attachments附件处理证据元组EmailAttachmentReportoutput_path实际写出的输出路径未指定则为None用户检测器 确定性安全扫尾关键设计你提供的检测器之后总是跟着 OpenMed 的确定性安全扫尾safety sweep。即使自定义检测器漏检结构化标识符如 8 位病历号、电话号码仍会被兜底命中。测试test_supplied_detector_still_gets_deterministic_safety_sweeptests/unit/multimodal/test_email.py验证检测器返回空实体时正文中的12345678与415-555-1212依然被替换为[MEDICAL_RECORD_NUMBER]和[PHONE_NUMBER]。扫尾逻辑位于_swept_detector最终调用openmed.core.safety_sweep.safety_sweep对文本与实体做二次确定性检测。文本处理的核心入口是_TextProcessor.redact其调用链按优先级为显式text_redactor优先否则用detector检测实体并按 span 应用替换否则回退到openmed.core.pii.deidentify此时policy/method/model_name生效无论走哪条路最终都会再跑一遍 safety sweep 作为残余结构化标识符兜底。头部脱敏规则头部处理由_redact_headers完成规则可归纳为四类结构性头部原样保留content-type、content-transfer-encoding、content-disposition、content-id、content-location、mime-version_STRUCTURAL_HEADERS它们是 MIME 语义必需项直接移除认证类头部authentication-results、dkim-signature、domainkey-signature、received-spf、日期类头部date、delivery-date、orig-date、resent-date、完整性类头部content-length、content-md5以及所有arc-前缀头部仅保留白名单消息头部_PRESERVED_MESSAGE_HEADERS_EXTRACTED_HEADERS中除date外的全部其余任意自定义头部如X-Patient-Note、X-Alice-Patient-ID一律删除Message-ID 类哈希化message-id、in-reply-to、references中的...片段被替换为SHA-256 摘要前 16 位构成的伪 ID例如message-digestopenmed.invalid_redact_message_ids。地址类头部bcc/cc/from/reply-to/sender/to脱敏后若无法被标准库地址解析器接受含畸形输入一律替换为redacted-addressopenmed.invalid且任何赋值异常都会回退为安全占位值绝不把不可解析或可能私密的原始值写回干净邮件email.py。正文与 HTML 脱敏规则正文处理由_redact_body_parts完成text/plain直接走_TextProcessor.redacttext/html走专用的_HtmlRedactor核心规则是保留 HTML 标签结构只对可见文本、注释和安全属性做脱敏标签白名单_HTML_SAFE_TAGS约 70 个常见标签白名单外的标签降级为span属性白名单_HTML_SAFE_ATTRIBUTEShref、src、class、id、alt、aria-*等 19 项所有on*事件属性与srcdoc、style属性被丢弃可见文本的脱敏编辑会反向映射回原始 HTML 片段含实体转义处理因此干净 HTML 中既能看到strong[PERSON]/strong这样的占位符也能看到script/script这样的空壳标签注释!-- ... --内容同样经过脱敏DOCTYPE被归一化为!DOCTYPE html处理指令被丢弃。远程内容与不安全链接的移除HTML 清洗中还包含三类主动的安全移除_redact_attribute远程图片src属性一律移除——远程图源会泄露这封干净邮件被打开过的事实仅保留指向本地再生 Content-ID 的cid:引用未知 Content-ID 引用cid:若不在附件重映射表cid_map中该属性整体移除不安全链接协议href仅允许#、http://、https://、mailto:前缀javascript:等协议直接移除。测试夹具 synthetic_phi.eml 中就包含了远程追踪器img srchttps://tracker.example.test/opened与javascript:alert(1)链接测试断言脱敏后两者均不出现test_email.py。MIME 元数据重建_sanitize_mime_metadataemail.py对整棵 MIME 树做最终清理multipart 的 preamble/epilogue 置空消除边界外的残余文本multipart 子类型统一收敛为alternative/digest/mixed/related之一其余归并为multipart/mixed非附件的正文部分删除Content-Description、Content-Disposition、Content-LocationContent-ID统一替换为body-0000openmed.invalid形式的伪 IDMIME 边界字符串全部由标准库按新内容重新生成——原始边界名不会出现在干净输出中测试断言synthetic-outer-boundary不存在test_email.py。附件处理内存分发、失败关闭与只读证据支持范围_ATTACHMENT_MIME_SUFFIXES与_MATERIALIZABLE_ATTACHMENT_SUFFIXESemail.py共同定义了受支持的附件类型后缀内容类型说明.pdfapplication/pdf重写为纯图片型 PDF.docxOOXML Word文本脱敏 元数据擦除.pptxOOXML PPT文本脱敏 元数据擦除.jpg/.jpeg/.png/.tiff栅格图片像素脱敏 元数据剥离.emlmessage/rfc822嵌套消息递归脱敏.msgapplication/vnd.ms-outlook经 MSG 桥接后按 EML 处理全内存分发绝不落盘附件通过redact_document在内存中分发_redact_attachments每个附件被包装成带安全文件名的_NamedBytesIO可寻址缓冲区attachment-0001.pdf输出写入另一个内存缓冲区。原始消息或附件的字节永远不会写入临时文件——测试test_redact_email_redacts_headers_plain_html_and_attachment_metadata通过伪造的redact_document验证了输入流名、字节内容与lang传递test_email.py。PDF 附件通过真实 PDF 处理器输出为全新纯图片型 PDF不透明黑框直接烧入页面像素原始可搜索文本层与源元数据无法存续test_attached_pdf_is_redacted_through_real_pdf_handler断言脱敏后页面extract_text()为空test_email.pyDOCX/PPTX 附件脱敏后额外调用_scrub_office_metadata擦除 OOXML core/app/custom 属性email.py测试验证 ZIP 内任何成员都不再含 PHI 字符串test_email.py图片附件像素区域覆盖 元数据剥离PNG 测试断言脱敏后comment元数据消失、覆盖区域像素为纯黑test_email.py嵌套 EML 附件递归脱敏后仍保持可读的干净 RFC 822 消息test_email.py。失败关闭原则不支持的附件直接失败关闭fail closed而不是被原样复制到干净邮件中。当附件扩展名不在可物化白名单内时抛出UnsupportedDocumentError且错误消息不会回显附件原始文件名中的 PHI测试test_unsupported_attachment_fails_closed_without_echoing_filename断言错误信息中不含 Alice Patienttest_email.py。附件报告只含安全证据每个附件产出一条EmailAttachmentReportemail.py其to_dict()只暴露附件序号attachment_index、扩展名extension、内容类型content_type、处理器格式handler_format、检出 span 数detected_span_count以及输出 SHA-256 摘要output_sha256——不含文件名、路径或任何内容。附件自身也被整体重写为安全的最小 MIME 表示原始头部全部删除仅重建Content-Type按安全后缀映射、Content-Disposition带attachment-NNNN.ext安全文件名、可选的新Content-IDattachment-NNNNopenmed.invalid内容以 base64 编码_replace_attachment_payload。通用分发器也认识邮件扩展名除了专用 API通用文档分发器同样支持邮件格式from openmed.multimodal import redact_document document redact_document(synthetic-message.eml)redact_document依据扩展名路由.eml与.msg注册在register_handler((.eml, .msg), _email_handler, requires_multimodalFalse)email.py。因为requires_multimodalFalse纯 EML 提取不要求安装multimodal附加依赖——测试test_named_memory_stream_dispatches_eml_without_multimodal_extra在模拟缺失 pdfplumber 的情况下仍能完成 EML 提取test_email.py。分发器还支持带name属性的二进制流输入仅用该安全文件名做扩展名路由base.py。处理器_email_handler的策略policy映射中若指定了output_path/redacted_path/destination_path则自动进入脱敏模式否则退化为纯提取email.py。可选 Outlook MSG 桥接隔离子进程与许可证边界为什么是显式可选的Outlook.msg解析依赖extract-msg库该库采用GPL-3.0许可证。因此它被隔离在显式的附加依赖中只有在你确认部署环境可以接受时才安装uv pip install openmed[email-msg-gpl,multimodal]pyproject.toml中该附加依赖固定为extract-msg0.56,0.57并明确注释该显式附加依赖使 GPL 代码远离基础与通用 multimodal 环境pyproject.toml。隔离子进程桥的实现OpenMed不会把extract-msg导入自己的进程。桥接实现_convert_msg_to_eml_bytes的执行方式通过importlib.util.find_spec(extract_msg)探测依赖缺失时抛出带安装指引的MissingDependencyErroremail.py用sys.executable -I -c启动隔离的 Python 子进程桥接脚本在子进程内openMsg(BytesIO(...))解析 MSG调用asEmailMessage()转成 RFC 5322 消息MSG 原始字节通过标准输入管道送入子进程规范化后的 EML 字节经标准输出管道收回全程不创建任何携带 PHI 的临时文件测试断言synthetic-msg-bytes不会出现在命令行参数中test_email.py子进程超时上限为 60 秒_MSG_BRIDGE_TIMEOUT_SECONDS解析失败或退出码非零都会抛出UnsupportedDocumentError。MSG 输入总是输出为 EML由于extract-msg是只读解析器MSG 输入一律以 EML 形式输出source_formatmsg干净输出的文件扩展名也统一为.eml。未安装email-msg-gpl附加依赖时任何.msg输入都会抛出带可操作指引的MissingDependencyError——测试test_missing_extract_msg_dependency_has_actionable_extra验证错误信息同时包含extract-msg与openmed[email-msg-gpl]test_email.py。隐私边界全本地处理零遥测所有处理都在本地完成。OpenMed不执行任何遥测或网络调用离线运行时模型工件必须已预先就位。这与仓库的整体隐私设计一致——邮件正文、附件字节在内存中流转完毕即被替换中间产物不会以原始形式出现在磁盘、进程参数或错误消息中。端到端验证一份带 PHI 的合成邮件仓库测试夹具 tests/unit/multimodal/fixtures/synthetic_phi.eml 是一份典型的最坏情况邮件包含人名Alice Patient、邮箱、8 位病历号12345678、电话号码415-555-1212、SSN999-99-9999、自定义 PHI 头部X-Patient-Note、X-Alice-Patient-ID、认证头部、HTML 中的远程追踪图、javascript:链接、未知标签alice-patient、带私有数据的属性、脚本中的 SSN、带 PHI 文件名/描述/CID 的 PDF 附件、以及 preamble/epilogue 中的残余文本。对应的集成测试断言了完整验收标准test_email.py所有原始标识符不再出现在序列化字节与归一化文档中Authentication-Results、X-Alice-Patient-ID、Date均被移除MIME 边界被重新生成preamble/epilogue 清空HTML 保留结构strong[PERSON]/strong、空script但mailto:中的邮箱、javascript:、远程 tracker、未知标签与私有属性全部消失附件被重命名为attachment-0001.pdfContent-ID 重建Content-Description与自定义附件头部被删除载荷为干净的替换 PDF。这段测试可以作为你自己的邮件脱敏验收清单的起点。引用与进一步阅读核心实现openmed/multimodal/email.py多模态公共契约与分发器openmed/multimodal/base.py多模态导出面openmed/multimodal/init.py端到端测试tests/unit/multimodal/test_email.py 与 测试夹具附加依赖声明pyproject.toml相关多模态文档PDF 脱敏保真度、媒体类型探测、资产清单与批次【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考