
OpenMed 删除校验实战用openmed.core.deletion_verify实现 SHA-256 指纹验证的本地确定性文件删除【免费下载链接】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 提供的一个小而关键的本地安全组件——openmed.core.deletion_verify实现位于 openmed/core/deletion_verify.py。它用于在移除敏感产物如脱敏后的源文件、临时映射表时提供本地、确定性的删除守卫调用方必须显式给出文件路径与预期的 SHA-256 指纹模块先全量验证、再隔离、后删除任何环节失败都会回滚并只生成仅含计数的无 PHI 证据。读完本文你将掌握该模块的完整调用契约、fail-closed 的路径安全限制、事务式删除/回滚原理、证据格式与隐私边界以及它在 Windows 平台上的特殊恢复策略可直接在 OpenMed 的删除流程中落地使用。一、设计定位一个局部、确定性、无 PHI的删除守卫deletion_verify的定位可以从它的模块文档字符串openmed/core/deletion_verify.py中读出三个关键约束本地它从不发起网络请求只操作调用方指定的本地文件系统确定性同样的输入、同样的文件状态必然得到同样的结果并生成结构稳定、可校验的证据无 PHI证据文件与公开异常中绝不包含路径、指纹、内容、时间戳或自由格式的错误文本。同时文档明确划清了责任边界它不证明任何临床或监管义务如 HIPAA 的合规删除已被满足。它只是一个删除前确认你要删的东西确实是你验过的东西的机制。如果你需要删除前的影响面预演哪些缓存/映射/证据制品会被级联影响请配合 docs/security/deletion-plans.md 中openmed.risk.deletion_plan的非破坏性删除影响规划使用而合规审计留痕则参见 docs/security/audit-envelopes.md。本模块是执行层最后的扳机保险。二、调用契约与最小示例2.1 三个核心公开符号from openmed.core.deletion_verify import ( DeletionArtifact, # 一个待删除文件 期望指纹内存驻留repr 不泄露路径 delete_verified_artifacts, # 主入口验证并删除 fingerprint_file, # 计算一个普通文件的规范 SHA-256 指纹 )最直接的用法是先取指纹、再带指纹删除artifact redacted-source.bin fingerprint fingerprint_file(artifact) result delete_verified_artifacts( ., [DeletionArtifact(artifact, fingerprint)], evidence_pathdeletion-evidence.json, )2.2 指纹的两种合法形式fingerprint_file()返回的是规范形式sha256:64 位小写十六进制摘要而调用方传入DeletionArtifact或映射时可以写裸的 64 位 SHA-256 摘要也可以写sha256:digest规范形式。模块内部用正则^(?:sha256:)?([0-9a-f]{64})$忽略大小写归一化后统一存储为规范形式见 openmed/core/deletion_verify.py 与_normalize_fingerprint。也就是说下面两种写法等价DeletionArtifact(a.bin, 0 * 64) DeletionArtifact(a.bin, sha256: 0 * 64)2.3artifacts参数的三种输入形态delete_verified_artifacts(root, artifacts, *, evidence_pathNone)的artifacts参数见 openmed/core/deletion_verify.py接受单个DeletionArtifact一个{path: fingerprint}映射可迭代对象其元素可以是DeletionArtifact、{path: ..., fingerprint: ...}字典、或(path, fingerprint)二元组/列表。# 映射形式 delete_verified_artifacts(root_dir, {p1: fp1, p2: fp2}) # 二元组列表 delete_verified_artifacts(root_dir, [(p1, fp1), (p2, fp2)])verify_and_delete()是delete_verified_artifacts的兼容别名签名完全一致openmed/core/deletion_verify.py。2.4 返回值DeletionEvidence返回的DeletionEvidence是冻结 dataclass字段为requested_count、verified_count、deleted_count、rolled_back_count与status。result.passed属性在满足状态为completed且 请求数验证数删除数 且 回滚数为 0时返回Trueopenmed/core/deletion_verify.py。成功的空请求是一个确定性的 completed no-op所有计数为 0同样可以写证据。三、fail-closed 的路径与身份校验模块文档强调输入路径必须位于root之内不得包含./..别名不得是符号链接、目录或硬链接当请求对象存在歧义时有意失败关闭fail closed。源码中的实现细节如下均为 openmed/core/deletion_verify.py 中的真实逻辑root 解析_resolve_rootroot 本身必须是已存在的真实目录不允许是符号链接且解析后不能是文件系统根目录Path(resolved.anchor)直接拒绝防止把整个挂载点纳入操作范围路径别名_resolve_candidate路径的任意分量含.或..即抛AmbiguousPathError相对路径拼接后必须能relative_to(root)否则抛UnsafePathError符号链接逐分量检查_assert_no_symlink_components从 root 开始逐层os.lstat任一中间分量或终结点是符号链接即抛UnsafePathError打开文件时还使用O_NOFOLLOW_open_for_hash双保险防止 TOCTOU 换链身份约束_prepare_artifact与fingerprint_file目标必须是普通文件S_ISREG且st_nlink 1硬链接计数为 1否则抛UnsafePathError/AmbiguousPathError请求集合内重复路径、或(st_dev, st_ino)指向同一文件对象的重复身份都会被判定为AmbiguousPathError_prepare_all路径长度路径字符串超过MAX_PATH_LENGTH 4_096即视为非法请求InvalidDeletionRequest证据路径碰撞evidence_path不能是符号链接、不能是目录也不能与任何一个待删除产物解析到同一路径或同一文件对象_check_requested_evidence_collision与_check_evidence_collision防止证据覆盖产物这类自毁式误操作。3.1 公开异常只暴露稳定的失败码所有失败都继承自DeletionVerificationError异常文本固定为deletion verification failed: code不含任何路径或系统错误原文。完整异常层次与稳定码openmed/core/deletion_verify.py异常类稳定失败码触发场景InvalidDeletionRequestinvalid_request请求格式非法、路径过长、指纹格式错误、超过 128 个文件UnsafePathErrorunsafe_path路径在 root 外、含符号链接、指向目录或非普通文件AmbiguousPathErrorambiguous_path./..别名、重复路径、硬链接、证据与产物碰撞ArtifactNotFoundErrorartifact_not_found显式请求的文件不存在ArtifactAccessErrorartifact_access文件无法安全读取FingerprintMismatchErrorfingerprint_mismatch实际摘要与期望指纹不一致DeletionTransactionErrortransaction_failed事务无法完成或回滚失败EvidenceWriteErrorevidence_write_failed证据文件无法写入这一点在测试中有直接验证test_fingerprint_mismatch_rejects_the_whole_request断言SYNTHETIC not in str(error.value)test_hostile_path_and_iterable_errors_are_value_free断言恶意__fspath__/__iter__抛出的异常原文不会泄漏进错误信息tests/unit/core/test_deletion_verify.py。四、事务式删除流程先验证、再隔离、后删除模块文档描述的整体协议是先验证全部目标文件再移动任何文件随后使用私有的同文件系统隔离区private same-filesystem quarantine、一份独立恢复副本independent recovery copy和一个保留的只读恢复描述符retained read-only recovery descriptor完成操作。对应源码主流程delete_verified_artifactsopenmed/core/deletion_verify.py分五个阶段Preflight解析 root、归一化请求、准备证据目标并做碰撞检查任何准备失败会调用_write_rejection_evidence写出rejected状态证据requested_count为请求数、verified_count为已通过数然后原样抛出。准备Prepare_prepare_all按确定性顺序先绝对路径后相对路径再按 posix 路径排序逐个验证。每个文件的验证分三层lstat身份检查 → 以O_NOFOLLOW打开并fstat确认与打开前状态一致 → 1 MiB 分块流式计算 SHA-256_hash_descriptor_READ_CHUNK_SIZE 1024 * 1024。哈希前、哈希后各做一次fstat对比st_dev/st_ino/size/mtime_ns/ctime_ns/nlink读取期间文件状态变化立即判定DeletionTransactionError。任何一个文件指纹不匹配整个请求被拒绝任何文件都不会被移动。Staging隔离在 root 下创建tempfile.mkdtemp(prefix.openmed-deletion-)私有目录内含payload/与backup/两个子目录权限收紧为0o700_make_transaction_dirs。每个文件先os.replace移入 payload 隔离区再立即核对隔离区文件身份st_nlink 1且与打开前同一 inode随后从 payload 打开描述符复制出独立 backup 副本_copy_descriptor_to_path同样逐块写入、fsync、保留原文件 mode并再次对 payload 与 backup 分别做指纹校验backup 保持一个打开的只读描述符用于恢复recovery_descriptor。Commit提交_verify_staged_before_commit对全部隔离文件做最终指纹核对payload 与 backup 都必须匹配且不是同一 inode之后才删除 payload 中的文件_delete_payloads清理事务目录最后原子发布证据os.replace临时证据文件到目标路径关闭恢复描述符。Rollback回滚staging、删除、清理或证据发布任一环节抛错都会走_rollback_transaction按逆序对每个已隔离文件优先用 payload若其指纹匹配恢复否则用 backup若指纹匹配再不行则从只读恢复描述符重建临时文件、fsync、校验指纹后用os.replace原子放回原位随后清理隔离目录并把证据改写为rolled_back状态rolled_back_count为实际恢复数量。测试test_partial_deletion_rolls_back_all_staged_files、test_stage_failure_restores_the_file_that_was_already_moved、test_staged_payload_corruption_rolls_back_from_independent_backup、test_evidence_publish_failure_after_cleanup_restores_from_descriptor分别验证了删除中途失败、隔离中途失败、隔离文件被篡改、证据发布失败四类场景都能完整恢复原文件tests/unit/core/test_deletion_verify.py。4.1 资源上限单请求最多 128 个文件MAX_ARTIFACTS: Final 128用于约束同时打开的描述符数量_coerce_artifacts在迭代时一旦超过即抛InvalidDeletionRequest。测试test_artifact_count_is_bounded_before_any_file_is_moved通过把上限 monkeypatch 成 1 来验证超限请求在移动任何文件之前就被拒绝临时容量文件系统必须为每个产物留出一份恢复副本的临时空间backup 副本 可能的恢复临时文件容量不足时事务失败并回滚。五、证据与隐私仅含计数的原子证据文件模块文档明确DeletionEvidence.to_dict()与可选的evidence_path文件只包含——schema 版本、请求/验证/删除/回滚计数、稳定的操作状态。绝不含文件名、路径、指纹、内容、时间戳或自由格式错误。to_dict()的固定结构_EVIDENCE_SCHEMA_VERSION 1openmed/core/deletion_verify.py{ schema_version: 1, requested_count: 1, verified_count: 1, deleted_count: 1, rolled_back_count: 0, status: completed }status取值限定为completed/rejected/rolled_back_EVIDENCE_STATUSES。证据的写入策略原子写入先写临时文件tempfile.NamedTemporaryFile前缀.openmed-deletion-evidence-同目录flushfsync后用os.replace原子替换目标_write_evidence_atomicrejected失败的 preflight如指纹不匹配在提供了evidence_path时写出rejected计数记录verified_count为已通过数量rolled_back需要恢复隔离文件的事务写出rolled_back证据completed 发布时机只有所有产物与恢复路径都被移除之后completed 证据才被发布os.replace放在_cleanup_transaction之后确定性 no-op成功的空请求同样生成确定性的 completed 证据。测试test_success_is_deterministic_and_writes_counts_only_evidence直接断言证据 JSON 内容与result.to_dict()完全一致、序列化文本中不出现文件路径、不出现指纹、删除后目录中不残留任何.openmed-deletion-*临时目录tests/unit/core/test_deletion_verify.py。使用约束把返回的DeletionArtifact记录只保留在内存中不要写入日志——因为路径与指纹本身属于敏感信息证据边界之外的持久化由调用方负责。六、回滚语义与边界不是日志文件系统也不是物理擦除文档明确了两条诚实边界回滚只覆盖运行进程观察到的失败。它不是一个日志式文件系统事务journaled filesystem transaction无法保证在进程崩溃、内核崩溃、存储设备故障或断电之后还能恢复。因此它适合防止业务逻辑层面的中途失败如拷贝失败、证据写失败而不是针对硬件级灾难的持久化保证。删除只移除目录项unlink不是闪存或写时复制copy-on-write存储上的物理安全擦除secure erase承诺。若有物理擦除需求需要另行配合存储层的擦除/加密机制。这两点也决定了它应该被用在哪些位置删除本地生成的临时映射、脱敏中间文件这类可以低成本重算的产物而不是作为唯一手段去证明数据已从物理介质上消失。七、Windows 恢复路径有界内存恢复副本模块文档专门说明了 Windows 的行为差异docs/security/deletion-verification.md源码通过_USE_MEMORY_RECOVERY os.name nt与_WINDOWS_STAT两个平台开关实现原因Windows 上打开的备份句柄会阻止重命名与删除因此无法像 POSIX 那样保留打开的描述符做恢复改为经过验证的内存恢复副本recovery_bytes上限整个请求的恢复载荷合计限制为128 MiBMAX_RECOVERY_BYTES 128 * 1024 * 1024。内存快照阶段逐块累加并实时校验剩余容量超限立即抛错请求超过上限时在任何载荷被删除之前就会回滚已隔离的文件_stage_all中的容量检查 test_memory_recovery_limit_rolls_back_before_deletion测试验证生命周期恢复引用在完成或回滚后统一释放_close_recovery_descriptors清空recovery_bytes并关闭描述符二进制一致性所有平台都以二进制模式指纹与拷贝文件内容。测试test_binary_fingerprints_preserve_crlf_and_control_z专门验证包含\r\n、\x1aCtrl-Z、\x00、\xff的二进制内容指纹与删除均正确避免文本模式换行转换导致指纹错乱tests/unit/core/test_deletion_verify.py。另外Windows 的fstat与路径stat在 CPython 3.12 上对 ctime 语义存在差异路径 stat 的 ctime 暴露创建时间而描述符 fstat 暴露元数据变更时间因此_same_open_file_state在 Windows 上改而比较st_birthtime_ns出生时间同时不放松身份、大小、mtime 与链接数检查测试test_hash_accepts_windows_path_and_descriptor_creation_times与参数化的test_windows_hash_rejects_changed_identity_or_content覆盖了这一平台细节。八、测试证据与集成方式8.1 单测覆盖要点完整的单元测试位于 tests/unit/core/test_deletion_verify.py约 500 行并通过recovery_backendautouse fixture 对描述符恢复 / 内存恢复两条路径参数化双跑Windows 上自动跳过描述符恢复分支。覆盖的关键性质包括成功路径确定性与 counts-only 证据、无残留临时目录指纹不匹配 → 整批拒绝且无文件被移动符号链接逃逸symlink escape被拒绝./..别名与重复路径/重复 inode 判定为歧义证据路径不能覆盖待删除产物双向碰撞检查删除中途、隔离中途、提交前篡改、证据发布失败四类回滚场景128 文件上限、128 MiB 内存恢复上限恶意__fspath__/__iter__异常不泄漏二进制内容CRLF、Ctrl-Z指纹一致性Windows 身份/ctime/birthtime 语义差异。8.2 与删除规划模块的衔接本模块解决的是执行时的验证与回滚删除前的影响评估由 docs/security/deletion-plans.md 的openmed.risk.deletion_plan.plan_deletion_impact提供——它以artifacts清单artifact hash、kind、retention class、依赖链、owned 标记做非破坏性的 dry-run 影响预演报告按 kind 与 retention class 分组的计数并阻塞非自有产物与未解析依赖。合理的删除流程是先用deletion_plan预演影响面并确认可执行再对确认的产物逐一生成 SHA-256 指纹、调用delete_verified_artifacts完成带验证、可回滚、可留痕的删除。这样既满足删除前知情也满足删除时可控、删除后可审计。结语openmed.core.deletion_verify是一个刻意保持小的模块不做网络请求、不承诺合规、不承诺物理擦除只在本地文件系统上把删除这件事做扎实——显式路径 期望指纹、全量先验证、同文件系统隔离、独立恢复副本、只读恢复描述符、fail-closed 的路径安全检查、仅含计数的原子证据、进程内完整回滚以及 Windows 上受 128 MiB 约束的内存恢复。配合 docs/security/deletion-plans.md 的影响预演和 tests/unit/core/test_deletion_verify.py 的全面测试它可以在不引入外部依赖的前提下为 OpenMed 的敏感产物清理提供一条可验证、可回滚、可审计的确定性执行路径。【免费下载链接】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),仅供参考