Use when the user asks to strip personal data, names, positions, or company names from a Word file before sharing ("обезличить", "удалить личные данные", "убрать названия компаний"). Goal: no residual identifier anywhere in the package, structure and formatting intact.
Visible text is only part of what leaks. Scan every part of the package, not just word/document.xml:
| Where | What lives there |
|---|---|
word/document.xml |
body paragraphs, tables, text boxes |
word/headerN.xml, word/footerN.xml |
running company/title lines (one footer per section — a doc can have 20+ identical footers, all must be edited) |
docProps/core.xml |
dc:creator, cp:lastModifiedBy — often the real author's email and full name |
docProps/app.xml |
<Manager> — often another email |
customXml/*.xml |
metadata items |
The header/footer and docProps leaks are the ones people forget. A footer that reads " ООО «»" repeats company branding on every page; each footerN.xml is a separate file carrying its own copy.
.xml for the terms to remove (company name in all grammatical forms, well-known surnames, Архидинамика-style unit names). Print each hit with ~120 chars of context so you can see what is actually there. Also regex the whole package for [\w.+-]+@[\w.-]+\.\w+ (emails) — emails hide inside docProps, not the visible text.docx_read.py --images DIR (or unzip word/media/*) and check each with vision_analyze for logos, faces, signatures, company names. Pure technical/diagram images are clean and need no edit. EMF/WMF logos will not decode as images — rasterize via PowerShell System.Drawing.Image → Bitmap.Save(png) before viewing.w:t, group runs by their owning w:p, join the run texts into the paragraph string, apply exact-match and regex substitutions to that string, then write the result into the first run and blank the rest. This is the same collapse as docx_edit.py replace but lets you batch many rules across body, tables, and all headers/footers in one pass.core.xml / app.xml (empty the creator/manager elements).___ / ________), not with a fake name — it makes the redaction visible and keeps table columns from collapsing. Confirmed as the preferred shape for agreement/approval tables and signature blocks.ООО «<Company>» contains guillemets and a space; a bare-company-name regex will not match it. Handle ООО\s*«\s*Company\s*» first, then the bare name.document.xml alone leaves the company name on every page.docx_validate.py on the ORIGINAL first to capture its pre-existing issues as a baseline; then run it on the redacted file — the issue set should be identical. A file can be ok: true even with unresolved image references present in the original; do not "fix" pre-existing noise.cp:lastModifiedBy and <Manager> frequently hold the person's corporate address instead of a display name.A different job from redacting. The document is already redacted; the question is what is still visible before it goes to an external model or into a report read outside the team. The deliverable is a line for the report header, not an edited file. Run scripts/scan_open_pii.py <file.docx> — it prints what is closed and what is open, by category, with the finds themselves.
Two marker conventions exist, and both must be recognised.
| Convention | Looks like | Why it is used |
|---|---|---|
| Bracketed tokens | [АДРЕС-003], [ЗАКАЗЧИК-1] |
token substitution — the value stays recoverable from a lookup table |
| Underscore runs | «________», ______ |
in-place overwrite — the structure of forms and tables does not shift |
A checker that knows only one convention is worse than none: on a file redacted the other way it reports "no markers found", concludes the copy is the original, and stops. Recognise both, and treat "no markers at all" as the only real stop condition — not "no tokens".
Rules for the check itself:
headerN.xml / footerN.xml, docProps. Contract content lives in table cells, and a paragraphs-only pass returns nothing, which reads as "all clean"..lower()), never through the shell: grep -i does not fold Cyrillic in a Windows shell and the count silently comes back zero.--summary (counts, no values) whenever an agent will read the result; the full report is for a human looking at the document directly. Verified on a sample: the full run printed the company name, the signatory, the phone and the postal index — i.e. the very values being redacted..xml parts.[А-ЯЁ][а-яё]+\s+[А-ЯЁ]\.\s?[А-ЯЁ]\.), full-name triples, emails, and phone numbers — expect empty sets. Long runs of spaces from table layout will false-positive a naive three-word regex; anchor the name pattern on initials or a known surname list instead.docx_validate.py out.docx → ok: true with the same issues as the baseline.