DeepSeek Harness 医疗数据批量匿名化插件:规则识别 +…
31.5/100
综合分
2
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/11/3 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add wld007/dsh-medical-anonymizer该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · other
装得上吗
本站尚未做安装检查
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 1 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 63 处,展示前 52 条)
- 写入剪贴板client.js:1051
await navigator.clipboard.writeText(current?.runDir ?? '') - 读取本地文件lib/service.js:96
const info = await stat(absoluteInput).catch(() => null) - 写入本地文件lib/service.js:114
await mkdir(outputRoot, { recursive: true }) - 写入本地文件lib/service.js:178
await mkdir(join(outputRoot, 'runs', runId), { recursive: true }).catch(() => {}) - 写入本地文件lib/service.js:179
await writeFile(join(outputRoot, 'runs', runId, 'report.json'), JSON.stringify(failed, null, 2), { mode: 0o600 }).catch(() => {}) - 读取本地文件lib/service.js:215
const raw = await readFile(join(root, 'runs', id, 'report.json'), 'utf8') - 读取本地文件lib/service.js:228
const parsed = JSON.parse(await readFile(join(candidate, 'index.json'), 'utf8')) - 读取本地文件lib/service.js:264
const markdown = await readFile(join(root, 'runs', id, 'report.md'), 'utf8') - 读取本地文件lib/pipeline.js:48
const realRoot = await realpath(root).catch(() => root) - 读取本地文件lib/pipeline.js:52
const real = await realpath(candidate).catch(() => resolve(candidate)) - 读取本地文件lib/pipeline.js:81
const entries = await readdir(dir, { withFileTypes: true }) - 读取本地文件lib/pipeline.js:95
const info = await stat(root) - 读取本地文件lib/pipeline.js:194
names = await readdir(root) - 读取本地文件lib/pipeline.js:202
const info = await stat(dir) - 删除本地文件递归lib/pipeline.js:207
await rm(dir, { recursive: true, force: true }) - 写入本地文件lib/pipeline.js:218
await writeFile(join(dir, STAGE_OWNER), String(process.pid), { mode: 0o600 }) - 写入本地文件lib/pipeline.js:433
await mkdir(runDir, { recursive: true }) - 写入本地文件lib/pipeline.js:435
await writeFile(run.paths.mapFile, JSON.stringify({ runId, prefixes: pathMapper.prefixes(), files: pathMapper.entries() }, null, 2), { mode: 0o600 }) - 删除本地文件递归lib/pipeline.js:551
await rm(folder, { recursive: true, force: true }) - 写入本地文件lib/pipeline.js:552
await rename(packed, folder) - 删除本地文件递归lib/pipeline.js:566
if (stageRoot) await rm(stageRoot, { recursive: true, force: true }).catch(() => {}) - 写入本地文件lib/pipeline.js:586
await mkdir(runDir, { recursive: true }) - 写入本地文件lib/pipeline.js:587
await writeFile(join(runDir, 'report.json'), JSON.stringify(run, null, 2), { mode: 0o600 }) - 读取本地文件lib/pseudonym.js:62
raw = await readFile(this.file, 'utf8') - 写入本地文件lib/pseudonym.js:159
await mkdir(dirname(this.file), { recursive: true }) - 写入本地文件lib/pseudonym.js:178
await writeFile(tmp, body, { mode: 0o600 }) - 写入本地文件lib/pseudonym.js:179
await rename(tmp, this.file) - 写入本地文件lib/pseudonym.js:180
await chmod(this.file, 0o600) - 发起网络请求lib/llm.js:88
const response = await fetch(`${baseURL}${path}`, { - 发起网络请求lib/llm.js:169
const response = await fetch(`${baseURL}/models`, { - 读取本地文件lib/formats.js:126
const buffer = await readFile(path) - 读取本地文件lib/formats.js:129
const raw = await readFile(path, 'utf8') - 读取本地文件lib/dicom/index.js:73
file.bytes = (await stat(path)).size - 写入本地文件lib/dicom/index.js:136
await mkdir(dirname(target), { recursive: true }) - 读取本地文件lib/dicom/index.js:142
for await (const chunk of createReadStream(path, { start: pixel.start, end: pixel.end - 1 })) { - 写入本地文件lib/dicom/index.js:149
await pipeline(body(), createWriteStream(temp, { mode: 0o600 })) - 写入本地文件lib/dicom/index.js:159
await rename(temp, target) - 删除本地文件lib/dicom/index.js:161
await rm(temp, { force: true }).catch(() => {}) - 读取本地文件lib/dicom/index.js:178
for await (const chunk of createReadStream(path, { start, end: end - 1 })) hash.update(chunk) - 写入本地文件lib/zip.js:482
await mkdir(root, { recursive: true }) - 读取本地文件lib/zip.js:483
const realRoot = await realpath(root) - 写入本地文件lib/zip.js:488
await mkdir(dir, { recursive: true }) - 读取本地文件lib/zip.js:490
if (!inside(await realpath(dir), realRoot)) throw new Error(`解压目标目录越界,已拒绝:${dir}`) - 写入本地文件lib/zip.js:503
await writeFile(target, entry.data, { mode: 0o600, flag: 'wx' }) - 删除本地文件lib/zip.js:664
if (!ok) await unlink(tmpPath).catch(() => {}) - 写入本地文件lib/zip.js:667
await rename(tmpPath, zipPath) - 删除本地文件lib/zip.js:669
await unlink(tmpPath).catch(() => {}) - 读取本地文件lib/zip.js:687
for (const dirent of await readdir(rel ? join(root, rel) : root, { withFileTypes: true })) { - 读取本地文件lib/zip.js:707
const { mtime } = await stat(abs) - 读取本地文件lib/zip.js:708
items.push({ name: rel, mtime, load: () => readFile(abs) }) - 读取本地文件lib/guard.js:19
const real = await realpath(current) - 读取本地文件lib/guard.js:83
const info = await stat(path)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
展开完整介绍(62 字)↓
DeepSeek Harness 医疗数据批量匿名化插件:规则识别 + 可选本地/内网模型复核,稳定假名替换,输出审计报告。
README
由 DeepSeek 最新模型翻译生成dsh-medical-anonymizer
DeepSeek Harness(Cordis)插件,用来批量匿名化病历文本和表格。它找出直接标识符,用稳定假名替换,保持文件结构不变,并输出审计报告和可还原的映射库。
这是数据处理工具,不是合规认证。发布任何输出之前,请对照适用法律和本机构的政策进行审查。
特点
- 结构天然保持。 模型只负责标注文本;插件定位精确片段,只替换这些片段。
行数、CSV 列、JSON 键和 Excel 非文本单元格都不会变动。
- 规则优先,模型可选。 身份证校验位、电话/邮箱模式、带标签字段、列名/键名策略无需模型即可运行。模型轮次用来提高自由文本的召回率,并复核结果。
- 有界修复循环。 复核轮次的发现会回到发现阶段,受轮次上限约束。未解决的条目标记为人工复核,而不是被静默接受。
- 本地优先。 无遥测。插件只访问你配置的 OpenAI 兼容端点;指向本机运行时,数据就不会离开本机。
- 输出可审计。 每个文件的实体统计、结构校验、残留问题、人工复核清单,以及唯一保存原值的映射库。报告不打印标识符原值。
展开完整文档(README 剩余 1 节)
dsh-medical-anonymizer
DeepSeek Harness(Cordis)插件,用来批量匿名化病历文本和表格。它找出直接标识符,用稳定假名替换,保持文件结构不变,并输出审计报告和可还原的映射库。
这是数据处理工具,不是合规认证。发布任何输出之前,请对照适用法律和本机构的政策进行审查。
特点
- 结构天然保持。 模型只负责标注文本;插件定位精确片段,只替换这些片段。
行数、CSV 列、JSON 键和 Excel 非文本单元格都不会变动。
- 规则优先,模型可选。 身份证校验位、电话/邮箱模式、带标签字段、列名/键名策略无需模型即可运行。模型轮次用来提高自由文本的召回率,并复核结果。
- 有界修复循环。 复核轮次的发现会回到发现阶段,受轮次上限约束。未解决的条目标记为人工复核,而不是被静默接受。
- 本地优先。 无遥测。插件只访问你配置的 OpenAI 兼容端点;指向本机运行时,数据就不会离开本机。
- 输出可审计。 每个文件的实体统计、结构校验、残留问题、人工复核清单,以及唯一保存原值的映射库。报告不打印标识符原值。
功能
| 方面 | 说明 |
| 格式 | .txt、.md、.csv、.json、.xlsx;.zip 压缩包(含嵌套,见 压缩包);DICOM 影像(只处理元数据,见 DICOM 影像) |
| 文件名与目录名 | 路径中的姓名、身份证号、手机号、patient_<编号>、zyh_<住院号>、mzh_<门诊号> 换成与正文一致的假名,见 文件名与目录名 |
| 直接标识符 | 姓名、医生/职工姓名、身份证号、电话、邮箱、地址、病历号、住院号、就诊号、社保号、银行卡号、医院名称 |
| 列名/键名策略 | 姓名、身份证号、电话、住址 等表头,以及 PTT_NM、ID_CRD_NO、CTCT_TEL_NO、CURR_ADDR、_DCT_NM、MDC_ORG_NM、PID、INHOS_NO 等键整体替换为假名;PRM_KEY/RCD_NO 等记录键按记录标识符遮盖 |
| 假名 | 按值和类型稳定生成,如 化名-9F3A2C、证件-1F0B67;共用同一输出根的文件和批次中,同一个值得到同一个假名 |
| 可配置规则 | 按标识符类型选择:稳定假名(默认)、部分遮盖、类型标签或保留;可选处理准标识符(日期、年龄、性别、邮编):平移、泛化或标签——见匿名化规则 |
| 校验 | 模型复核、规则复扫、原值残留检查、结构比对 |
| 运行 | 直接写出匿名化副本,源文件只读;模型调用不设上限;多文件并发;文件多时自动分组;可取消;可撤销某批新增映射 |
| 报告 | Markdown + JSON,按文件和批次汇总,附人工复核清单 |
安装
要求 DSH >= 0.2.1(在 0.2.1-rc.5 上测试)和 Node.js >= 20。
dsh plugin --profile <profile> add github:wld007/dsh-medical-anonymizer
或在发布到 npm 之后:
dsh plugin --profile <profile> add dsh-medical-anonymizer
安装后重启 DSH:插件层在启动时组装。
设置页位于 设置 → 医疗数据匿名化。主机端激活后即可使用 /anonymize 命令和 Agent 工具。
模型配置
插件从不调用内置的云端模型,只使用你配置的 OpenAI 兼容端点。
| 运行时 | Base URL | 说明 |
| Ollama | http://127.0.0.1:11434/v1 | 推荐本地使用 |
| LM Studio | http://127.0.0.1:1234/v1 | 需开启本地服务器 |
| vLLM / llama.cpp | 你的 /v1 端点 | 任意 OpenAI 兼容的 chat 服务 |
模型名称留空则只运行确定性的规则层。配置模型后,插件会把每批文本发送到该端点进行发现和复核;如果端点在远程,文本就会离开本机。处理患者数据请使用本机或机构可控的端点。
推理模型(Qwen3 等)可能把全部 token 预算花在隐藏推理上。关闭思考模式默认开启;保持勾选,或给 /anonymize 加 --no-think。只有模型确实需要推理时才取消勾选(或传 --think)。不接受这些可选请求字段的服务器会自动去掉字段重试一次。
使用
设置 → 医疗数据匿名化(配置)
只放配置,分为 模型连接、匿名化规则、批次与界面、使用说明 几个标签页:
- 本地模型:端点、模型、API Key、关闭思考模式、单次调用超时、温度、测试连接(端点上没有该模型名时会提示)。
- 批次默认值:输出根目录、最大轮次、每组文件数、同时处理的文件数、单批 DICOM 上限。
- 映射库加密:设置/修改/清除 vaultPassphrase(两次输入确认,保存后不再显示)。改口令或取消加密后,用旧口令加密的映射库将无法读取。
- 匿名化规则:每类标识符的处理方式、准标识符开关、每类准标识符的处理方式,均附实时示例;可选识别无区号座机号。
- 界面:在左侧栏显示或隐藏 医疗数据匿名化 入口。隐藏后 /anonymize 和 Agent 工具仍可使用。
- 保存栏列出未保存的字段;保存 会写入该 profile 自己的配置层,重启和插件更新后依然保留。
API Key 只在你输入或清除时保存,之后不再显示,存放在 profile 配置文件中(权限 600)。
批次运行期间不允许保存,因为保存会重新加载插件。
左侧栏 → 医疗数据匿名化(运行批次)
1. 选择输入目录(也可直接填写单个文件或 .zip 的路径);可选覆盖输出根目录。
2. 启动批次。模型、端点和轮次取自已保存的设置。
3. 点击批次查看文件明细和报告、复制输出路径,或取消正在运行的批次。
/anonymize 命令
/anonymize /path/to/records --model qwen2.5:14b --endpoint http://127.0.0.1:11434/v1
直接写出匿名化文件(源文件只读,不会被修改)。
输入也可以是单个文件,例如 /anonymize /path/to/note.txt 或 /anonymize /path/to/记录.zip:只处理该文件,输出为 runs/<批次号>/output/note.txt,默认输出根目录在该文件旁边的 .medical-anonymizer。
选项:--rounds N、--output DIR、--no-think、--think。
智能体工具
| 工具 | 用途 |
| anonymize_start | 启动批次并返回 runId |
| anonymize_cancel | 取消正在运行的批次;已写出的文件保留 |
| anonymize_status | 进度、实体统计、未解决问题 |
| anonymize_report | 读取 Markdown 报告 |
| anonymize_runs | 列出最近的批次 |
| anonymize_undo_mappings | 撤销某批新增到映射库的对应关系(不删除输出文件) |
anonymize_start 参数:inputDir(目录、单个文件或 .zip)、outputRoot、maxRounds。
智能体工具不能选择模型端点、模型、API Key 或思考模式:这些始终取自用户已保存的设置,因此智能体无法把患者文本转发到其他服务。工具描述中写明了插件名和这一数据边界,有助于智能体在名称相近的工具中选中它。只有人工输入的 /anonymize 命令接受 --endpoint 和 --model 覆盖。
工作原理
1. 规则预扫描。 身份证号(校验位验证)、手机/座机、邮箱、银行卡(Luhn)、病历号/住院号/就诊号,以及带标签的姓名/地址/医院/医生;也识别病历模板中
F=[引用值:姓名]…[<isele 0>值<isele 1>] 这类被标记隔开的字段。
2. 列名与键名策略。 专用字段整体替换为假名,包括常见医院导出键,如 PTT_NM、 ID_CRD_NO、CTCT_TEL_NO、_ADDR、_DCT_NM、_STFF_NM、MDC_ORG_NM、 PID、INHOS_NO、_DCT_CD/_NUS_CD(工号)、_SGNT(签名),以及区域卫生平台导出中的 SLF_NM(本人姓名)、_PSN_NM(采样/审核等人员)、WRK_UNT_NM(工作单位)、可串联同一患者或就诊的 USER_ID、VST_UNQ_ID、OTPT_EMG_NO、RGST_NO,和机构代码
_ORG_CD。{"ITM_NM": "姓名", "ITM_VLU": "…"} 这类“项目名-项目值”记录按项目名判断。
描述标识符的列(ID_CRD_CGY_CD 证件类型、_TP_、_FLG 等)不算标识符列。
3. 可选的模型发现。 文本分批发送到配置的端点(附带字段名)。相同文本只发送一次;纯数字/日期值和已脱敏的值不发送。模型返回 {segment, type, text};插件在原文中定位精确文本。从不信任模型给出的坐标。
格式校验: 不像该类型的结果不替换,列入报告提示——如按字段名得到的证件号 01、
模型把科室名“内一科(肾脏病科)(11病区)”判为医院。
全文扩展: 识别出的值会在全文其他位置一并替换,但匹配不能嵌在更长的字母/数字串中(01 不会命中 2001017),少于 6 位的短编码只替换识别到的位置,不扩展。
“区属医院”“综合医院”“中医院”和单独的“医院”是机构类别或通用词,不当作医院名。
跨文件: 处理前先用字段名和规则扫一遍全部文件,建立批次词典;某个文件里识别出的姓名、证件号、住址等也会在其他文件中替换。全部文件处理完后再做一次批次复扫,补上靠后的文件(含模型)才识别出的值,报告中记为“批次复扫”。省、市、县等行政区划名不跨文件扩展。
文件超过 maxFiles(默认 200)时自动分组:组内处理完即复扫并释放内存,最后对前面各组的输出再做一次跨组复扫;同时处理 concurrency(默认 4)个文件。
JSON 里单独的 name 键只有在所在对象带年龄、性别、生日、证件号、电话、住址等个人属性时才按姓名处理(诊断 {icd10, name}、检验项目、药品不受影响);姓名列里的机构全称按医院处理。
4. 假名化。 HMAC-SHA256(salt, type + value) 为每个值和类型生成稳定标记。
只改动匹配到的片段。
5. 校验。 模型复核、规则复扫、原值残留检查(附遮盖后的原值和字段名)、假名嵌入检查(假名紧贴字母/数字说明更长的值被部分替换)、结构化数据中数字/日期值只能整体替换,以及结构比对(行数、CSV 形状/表头、JSON 键/类型结构、Excel 非文本单元格)。
6. 有界修复。 错误回到发现阶段再跑一轮,最多 maxRounds 轮。如果没有进展,该文件标记为 review 并列入人工复核清单。
匿名化规则
默认行为与早期版本一致:所有标识符替换为稳定假名,准标识符不处理。
| 标识符策略 | 示例(张三、13800138000) | 可还原 |
| pseudonym(默认,稳定假名) | 化名-9F3A2C、电话-7A8ED8 | 是,通过映射库 |
| mask(部分遮盖) | 张、1388000 | 否;保留部分原值 |
| redact(类型标签) | [姓名]、[电话] | 否 |
| keep(保留) | 不变 | — |
遮盖规则:姓名保留首字;身份证号保留前 3 位和后 4 位;电话保留前 3 位和后 4 位;银行卡和各类编号保留后 4 位;邮箱保留首字母和域名;地址保留省/市/区前缀;医院保留前 2 个字和后缀。
只有开启 quasiIdentifiers 时才处理准标识符。它们在标识符之后以确定性方式处理,不使用模型:
| 类型 | 策略(开启后第一个为默认) | 示例 |
| 日期 | shift、month、year、redact、keep | 2024-03-01 → 2024-04-07 / 2024-03 / 2024 |
| 年龄 | band10、band5、redact、keep | 45岁 → 40-49岁 / 45-49岁;90 岁及以上 → 90岁及以上 |
| 性别 | keep、redact | 男 → [性别] |
| 邮编 | prefix3、redact、keep | 100020 → 100 |
- 检测范围包括正文(2024-03-01、2024年3月1日、45岁、年龄:45、性别:男、 邮编:100020)以及 BTH_DT、_DT、出生日期、AGE、GDR_NM、SEX、ZIP、 邮编 等字段;紧凑的 YYYYMMDD 格式只在日期字段中识别。
- 日期平移在每个输出根下使用同一个偏移量(±1–365 天),由映射库的盐值派生,因此日期之间以及跨批次的间隔保持不变。偏移量不会出现在报告中。不要对平移后的输出再次平移。
- 只修改文本值:Excel 数值/日期单元格和 JSON 数字保持原样,报告中会说明这一点。
排除误判
报告里出现误判时,在 设置 → 匿名化规则 → 排除误判 中填写后重新运行(每行一项):
- allowValues(不匹配的值):永远不当作标识符,如 01、居民身份证、某个科室名。
之前误存进映射库的对应关系会在下次运行时移除。
- skipFields(不处理的字段):列名或 JSON 键名(不区分大小写),这些字段不检测、不替换、不发送给模型,原样输出,如 ADMN_BED_NO。
DICOM 影像
面向 CT 等影像的元数据去标识;像素数据逐字节原样保留,不解码、不修改,也不经过模型。
- 识别:.dcm/.dicom/.dic/.ima、无扩展名或纯数字扩展名(如 PACS 导出的 1.2.840...)的文件,按文件头的 DICM 标记判定。
- 规则:DICOM PS3.15 附录 E 基础去标识配置,加“保留纵向时间信息(日期平移)”和 “保留病人特征”两个选项:
- 病人姓名、医生/操作者姓名清空或删除;所有 PN 字段一律删除;
- 出生日期清空;地址、电话、其他 ID、检查号(Accession)、检查 ID、机构、科室、设备序列号、站点名、各类备注与申请信息删除;
- 私有字段(奇数组)、曲线、覆盖层注释、图标图像、数字签名删除;
- PatientID 换成 cuid 风格编号(如 cnbolrmxti8ztkjkphnrebnb7):同一批次内同一病人一致、不同病人不同;密钥只在本次运行的内存中,不保存,事后无法回查,也不会写入映射库。
同一病人的影像请放在同一批处理;
- 实例类 UID(检查、序列、图像、参考坐标系等)重新生成为 2.25.<十进制>,由映射库盐值派生,跨文件引用和多次运行保持一致;SOP 类、传输语法等标准 UID 不变;
- 日期按与文本相同的偏移量整体平移,时间保留;年龄保留(≥90 岁写为 090Y);性别、身高、体重保留;
- 检查描述、序列描述、协议名保留,但会用规则扫描,命中电话、证件号等即整体删除。
- 开启准标识符处理后,DICOM 跟随同一套日期/年龄/性别策略(年龄分段写为段起点,如 040Y)。
- 写入 PatientIdentityRemoved=YES、去标识方法及代码序列、LongitudinalTemporalInformationModified。
- 校验:输出头部重新解析;姓名/私有字段/原始 UID 残留检查;被删除的身份类原值(姓名、ID、出生日期、机构等)在保留字段中再次出现即列入复核;像素数据 SHA-256 与原文件比对。
- 支持隐式/显式 VR 小端及所有封装(压缩)传输语法;显式 VR 大端、Deflate 数据集和损坏/截断文件标记为失败。DICOMDIR 不输出(内含姓名索引且按字节偏移互相引用),请对输出目录重新生成。
- 单批 DICOM 上限由 maxDicomFiles 控制(默认 50000),与文本文件的分组大小 maxFiles 无关。
报告只逐一列出有问题的 DICOM 文件,其余按目录汇总。
- 不处理:图像上直接印的文字(超声、截图类常见)、人脸重建风险、NIfTI、病理切片。
输出
<outputRoot>/
vault.json # 原值 <-> 假名,权限 0600
index.json # 最近的批次 runs/<runId>/ output/... # 匿名化副本,相对目录结构不变(含标识符的文件名/目录名已替换) report.md report.json
path-map.json # 原路径 <-> 新路径,仅在有路径被替换时生成,权限 0600,与映射库同样敏感
vault.json 是还原身份的钥匙。请与输出分开存放并妥善保护。报告刻意不包含任何原值。
在设置页(批次与界面 → 映射库加密)或插件配置中设置 vaultPassphrase 可用 AES-256-GCM 加密映射库;不设置时映射库为权限 0600 的明文 JSON。
未显式指定也未配置 outputRoot 时,输出写到输入目录旁边的隐藏目录 .medical-anonymizer,因此相邻的批次(例如每位患者一个文件夹)共享同一个映射库,假名保持一致。输出根不能与输入目录相同。如果放在输入目录内部,它会被排除在扫描之外,映射库和之前的输出不会被当作病历重新读入。
扩展名不受支持的文件、无法解压的压缩包会列在报告的 注意 部分;有压缩包无法解压的批次标记为 review。
源文件保护
插件从不修改输入:
- 每次写入前解析软链接,目标必须在本批输出目录内、且不在输入目录内,否则拒绝写入;输出根目录与输入相同或包含输入时直接拒绝启动。
- 运行前记录每个源文件(含压缩包本身)的大小和修改时间,结束后复核,结果写入报告 “源文件检查”;运行期间若有源文件被其他程序改动,批次标记为 review。
- 测试对每种输入都断言运行前后输入目录逐字节一致。
撤销映射
映射库里每条新对应关系都记录了首次产生它的批次。发现规则误判时,调用 anonymize_undo_mappings(参数 runId)即可移除该批新增的映射;之后修改规则重跑会重新生成。
输出文件不会被删除。
压缩包
- .zip(含 ZIP64、Windows 生成的 GBK 文件名)解压到系统临时目录处理,结果按原包内结构重新打包到输出目录(UTF-8 文件名),临时目录在批次结束(含取消、失败)后删除。原压缩包只读。
- 嵌套压缩包最多展开 3 层;只支持存储和 Deflate 压缩,加密包、分卷包不处理。
- 安全限制:拒绝绝对路径、.. 等越界条目;默认最多 20000 个条目、解压后总计 2 GiB、单文件 500 MiB、压缩比 1000:1(大于 1 MiB 的条目),超出按疑似 zip 炸弹拒绝;逐条校验 CRC。
- 无法解压的包只在报告里写原因类别,不写包内文件名(文件名可能含姓名)。
文件名与目录名
输出路径中的标识符在写出前统一替换,与正文使用同一映射库,因此同一个人在文件名和正文中是同一个假名:
- 64_张三_<身份证号>.zip → 64_化名-XXXXXX_证件-XXXXXX.zip
- patient_573453/stays/zyh_1008593/ → patient_病历-XXXXXX/stays/zyh_住院-XXXXXX/
- visits/mzh_3444733_check.json → visits/mzh_就诊-XXXXXX_check.json
分类目录(住院、门诊、检验……)、年月目录、表名(p_doc_gehr__rcd)、院区代码(h5650)和序号保持不变。原路径与新路径的对照写入 runs/<runId>/path-map.json(权限 0600),报告和批次列表中只显示替换后的路径。
隐私与安全
- 无遥测、无统计、无隐藏的网络请求。唯一的出站请求是发往配置的 chat 端点(以及点击测试时的 /models)。
- 模型能看到它被要求检查的文本。处理患者数据请使用本机运行时或位于信任边界内的端点。
共享网络上的明文 HTTP 不是安全的传输方式。
- 报告不含标识符原值,但包含输入/输出路径;报告和映射库都应按受保护健康信息(PHI)对待。
- 插件从不改写输入文件(见源文件保护);输出写到你选择的输出根。
- 文件名和目录名中的常见标识符会被替换;与正文粘连、没有分隔符的姓名(如 张三病历.md)不会识别,发布前请检查文件名。
- 这不是合规认证。除非开启准标识符处理,日期、年龄、性别、机构和罕见诊断的组合仍可能重新识别个人;报告会说明处理了哪些准标识符。
- mask 保留部分原值且不可还原;keep 会把标识符留在输出中。两者都会在报告的警告中列出。
配置
默认值位于 cordis.patch.yml,可在 profile 的 patch 层覆盖:
- id: medical-anonymizer
config:
baseURL: http://127.0.0.1:11434/v1
model: qwen2.5:14b
outputRoot: ''
maxRounds: 3
maxFiles: 200
concurrency: 4
maxDicomFiles: 50000
timeoutMs: 120000
temperature: 0
disableThinking: true
vaultPassphrase: ''
landline: false
identifierPolicy: { name: pseudonym, phone: mask, hospital: keep } # 未列出的类型:pseudonym
quasiIdentifiers: false
quasiPolicy: { date: shift, age: band10, sex: keep, postalCode: prefix3 }
allowValues: ['01', '居民身份证']
skipFields: [ADMN_BED_NO]
- maxFiles 是自动分组的每组文件数(超出不会丢弃);concurrency 是同时处理的文件数。
模型调用次数不设上限。
- landline 开启后识别带“电话:”等标签的 7–8 位无区号座机号(医院总机号也会被替换)。
- timeoutMs 是单次模型调用的超时。
- outputRoot 为空表示 <输入目录的上级>/.medical-anonymizer。
支持的格式与限制
- 输入:.txt、.md、.csv、.json、.xlsx、.zip,以及 DICOM(只处理元数据)。
- 默认处理直接标识符;准标识符(日期、年龄、性别、邮编)需手动开启。科室、机构代码和医嘱号从不泛化。匹配 PRM_KEY/RCD_NO 的记录键按记录标识符处理;医嘱号不处理。
- 字段提示覆盖常见的中英文医院数据结构。不常见的字段名需要在 lib/formats.js (HEADER_HINTS)中添加模式。
- 不支持 Word、PDF、普通图片;DICOM 不处理像素。JSON 输出会格式化缩进;键、数组和值类型不变。
- 模型轮次是可选的,效果取决于模型。确定性规则层是基线。
开发
npm test # 字段提示、规则策略、zip、路径、端到端流程、DICOM
node scripts/build.mjs 0.7.0 # 为本地 profile 构建带版本号的 dist/
npm test 使用确定性的模拟模型,不需要网络或真实数据。本地安装仅用于开发;最终用户请从 GitHub 或 npm 安装。
故障排查
- 模型超时或返回空内容。 开启关闭思考模式、改用指令(非推理)模型,或把模型留空只依赖规则层。批次会继续运行并记录模型警告。
- 结构化文件中没有发现标识符。 检查其字段名是否在 HEADER_HINTS 中;为你的数据结构添加模式。
- 设置页不见了。 确认 profile 使用 DSH >= 0.2.1、插件包已启用,且安装后已刷新页面。
- vault.json 无法读取。 它是用 vaultPassphrase 加密的;配置相同的口令即可读取。
许可证
MIT,见 LICENSE。本包内置 SheetJS Community Edition 0.20.3(Apache-2.0),见 NOTICE 和 vendor/SheetJS-LICENSE.txt。预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
wld007/dsh-medical-anonymizer
DeepSeek Harness 医疗数据批量匿名化插件:规则识别 +…
相关插件 · 其他
模型破甲清洗HunYiSage/dsh-purge
⚔️ DeepSeek Harness 网络安全红队工具dsh-purge
★ 16,993已汉化DeepSeek 客户端
73
女娲蒸馏alchaincyf/nuwa-skill
输入名人名字,提炼其思维模型与决策方式供你调用
★ 37,308已汉化DSH
72
逆向分析桥接Fanqi89/dsh-rea
REA(Reverse Engineer Anything)MCP 桥接,用来把 REA 逆向工具接入 DSH 插件
★ 51已汉化DSH
72
语音女友beiyege-01/dsh-voice-ai-girlfriend
语音 AI 女友Voice AI girlfriend for DeepSeek Harness:Whisper…
★ 888已汉化DSH
71.6
红队越狱测试套件Minglink/dsh-infinite-gen-5
DeepSeek v4.1 flash 逆向工具无限五代 求 Star 收藏 ⭐欢迎大家提交项目的改进
★ 3,759已汉化DSH
71.4
协同办公空间dream-num/univer-workspace
一个让人类与 AI Agent 共同创作、协作和审阅的开源 Office 工作空间。
★ 2,269已汉化DSH
70.8
这是你的仓库?认领条目