PII Safe Documents
This skill creates a reversible, locally redacted working copy with a strict main-agent workflow boundary:
- The main agent is untrusted for raw data. It receives paths and safe receipts only.
- The bundled open-source local wrapper may read the original solely to run PII Guard and local Ollama.
- No hook is required. Normal wrapper output suppresses raw content.
This is strong protection against accidental model exposure, not an OS security boundary against a malicious process running as the same macOS user. A Skill cannot revoke its own filesystem tools. Hostile-agent isolation requires a separately permissioned local broker or OS account. Never describe this Skill alone as mathematically or technically impossible to bypass.
Non-negotiable isolation rules
When this skill is active, the main agent MUST NOT:
- Read, preview, search, summarize, diff, upload, attach, or otherwise inspect the original file.
- Use
cat,sed,head,tail,grep,rg,strings, Python, a document reader, a browser, or any other tool on the original file. - Read or reveal
mapping.private.json, private worker files, raw logs, or the restored output. - Pass the original path to a subagent, cloud model, MCP server, website, or third-party API.
- Run PII Guard directly. Its libraries may echo source text in warnings; only the bundled wrapper may invoke it.
- Debug a failure by opening the original, mapping, worker output, or restored document.
- Run
git diff, content scans, or indexing over a directory that contains the original or restored output. - Reach the annotation page, run the
reviewsubcommand, or read a term file the user wrote formask. All three carry unredacted values. The annotation URL is deliberately withheld from you; do not reconstruct it, scan for the port, or ask the user to paste it.
These rules still apply if the user asks the main agent to “check quickly.” If raw inspection is genuinely required, stop this workflow and obtain explicit permission for a different trust model.
Supported inputs
Use this version for UTF-8 plain-text files up to 64 KiB only: .txt, .md, .csv, .tsv, .log, and .dat.
Do not rename a binary document to bypass this restriction. For .docx, .xlsx, .pdf, images, or audio, report that this version does not yet provide a verified isolation-preserving parser.
This is a PII redactor, not a general confidentiality classifier. Amounts, health details, schedules, contract terms, business strategy, and other sensitive facts may remain visible when they do not identify a person. Do not use this skill alone to claim that an entire document is safe for external disclosure.
Workflow
1. Path-only preflight
The user may provide the original path. You may check path metadata such as existence, suffix, and file size, but never its contents. Do not autocomplete or glob inside a sensitive directory.
Choose allowed terms only when the user explicitly wants them preserved, such as a company or product name. An allowed term is visible to the main agent because the user supplied it; do not discover allowed terms from the original.
1b. 第一階段快速模式(不啟動 Ollama)
需要只做決定性本機處理時,可明確使用 quick:
python3 <skill-dir>/scripts/pii_safe_workflow.py quick \
--input "/absolute/path/to/private-file.txt"
quick 只呼叫共用的 PII Guard 核心(Presidio、台灣規則與 CKIP),不啟動、連線或探測 Ollama。它和 repo 的 pii_guard quick CLI 及 localhost 網頁使用同一個 ~/.local/share/pii-safe-documents/jobs/<job_id>/ 私有工作目錄、mapping、快照與還原邏輯。回執只含 job ID、去識別化檔案路徑、數量、摘要與 roundtrip_verified,不含原文或 mapping 值。
quick 回執成功後,主 agent 只能讀 redacted_path;仍應讓使用者在本機網頁人工快審,因為決定性偵測可能漏掉或誤遮。還原前保留 job ID,完成後以 purge 手動清除,不會自動 TTL。
2. Create the redacted working copy
Run:
python3 <skill-dir>/scripts/pii_safe_workflow.py redact \
--input "/absolute/path/to/private-file.txt" \
--allow "company name the user explicitly supplied"
Repeat --allow as needed. The wrapper runs deterministic PII Guard detection plus the same chunked, three-sample local Ollama audit used by the localhost enhanced mode, captures all raw output, creates a private job directory, and prints only a safe JSON receipt. The verified default model is ornith-1.5:9b; override it only after a representative local accuracy and speed test.
If the receipt says both redaction_checks_passed: true and agent_may_read_redacted: true, the main agent may read only redacted_path. The receipt also provides safe replacement counts, audit-pass count, and the local model name. Keep job_id for restoration. Never infer or probe the mapping path.
If the command fails, report its safe error code and stop. In particular, NO_PII_CONFIDENCE means the detector found no reversible replacements and therefore withheld the copy instead of calling an unchanged file safe. ADVERSARIAL_INPUT_REVIEW_REQUIRED means instruction-like document text could interfere with the local model, so the wrapper refused automated release. Do not inspect hidden files or rerun lower-level commands.
2b. Offer the user a manual pass over the redacted copy
The detector and the audit both miss things, and both over-redact. The user is the backstop, and this step is where they act on what they see. Offer it whenever the redacted copy will be used for anything that matters; do not skip it silently.
Run:
python3 <skill-dir>/scripts/pii_safe_workflow.py annotate --job-id "<job_id>"
This opens a page in the user's browser and blocks until they close it out. Tell them it has opened and what to do there; then wait.
On that page the user can:
- Select any still-visible text and mask it. Every occurrence is masked, not just the selected one.
- Click a marker to see the value behind it and put it back. This is for text that should never have been redacted — typically a court, hospital, or company name whose removal makes the document unusable.
Neither action requires comparing against the original document.
The page is not addressable by you. The URL carries a single-use token minted inside the private worker and passed only to the browser it opens; it is never printed, and the receipt you get back contains counts, not a URL. Do not attempt to discover the port, reconstruct the URL, or fetch the page. Do not ask the user to paste the URL, the page, or any value from it — ask only for what they want done, or let them do it themselves on the page.
When the user finishes, the command returns a receipt with terms_masked and markers_restored. Every edit is persisted and re-verified as it happens, so closing the browser early loses only unmade edits, never made ones.
Re-read redacted_path afterwards; its contents and redacted_sha256 have changed.
For a headless machine with no browser, the same two operations exist as mask --terms <file> and unmask --marker TYPE-N, with review to list markers and values. review prints unredacted values, refuses when its output is not a terminal, and must be run by the user, never by you.
3. Work only on the redacted copy
Read and edit only the redacted working copy. Preserve placeholders exactly, including brackets, capitalization, and job namespace. Never normalize, translate, renumber, or combine them.
Save the edited redacted document as another UTF-8 text file. Prefer the same private job directory or a user-approved destination. Before restoration, verify mechanically that every placeholder from the redacted working copy is still present; do not open the mapping to do this.
In Markdown or Obsidian files, a placeholder inserted into a person-bearing link slug can temporarily make that link nonfunctional. Preserve the placeholder and surrounding link syntax exactly; restoration recreates the original link.
4. Restore without reading the result
Run:
python3 <skill-dir>/scripts/pii_safe_workflow.py restore \
--job-id "<job_id from receipt>" \
--input "/absolute/path/to/edited-redacted-file.txt" \
--output "/absolute/path/chosen/by/user/restored-file.txt"
The wrapper prints a safe receipt. After success, tell the user the output path, but do not read, preview, diff, hash through a content-printing tool, or summarize the restored file. A digest and roundtrip_equal boolean shown by the wrapper are safe to relay. roundtrip_equal: true is expected only when the redacted working copy was not intentionally edited.
5. Retain or purge the private map
The mapping is required for later restoration and is stored with restrictive permissions. Keep it by default. Purging is destructive, so do it only after the user explicitly confirms that no further restoration is needed:
python3 <skill-dir>/scripts/pii_safe_workflow.py purge --job-id "<job_id>"
Safe status language
You may report:
- job ID;
- readable redacted path;
- restored output path;
- whether the local audit passed;
- the redacted-file digest and permission checks emitted by the wrapper.
Never report original values, mapping entries, raw model output, raw warning text, or excerpts from the original/restored document.
Security notes
- The wrapper refuses network Ollama endpoints; only loopback addresses are accepted.
- Job directories use mode
0700; sensitive files use mode0600. - Existing placeholder-like text is protected before redaction to prevent restoration collisions.
- Allowed terms are protected before detection rather than restored afterward.
- The local audit uses bounded overlapping chunks, a system/user role boundary, schema-constrained JSON, and repeated residual passes to catch aliases and contextual identifiers missed by rule-based detection.
- Local-model guesses are replaceable only when they match exactly or normalize to one unique source span in both the original and current redacted document; ambiguous or hallucinated values fail closed and never enter the restoration map.
- Inputs are copied through a single-open, non-symlink private snapshot before a worker reads them, preventing path swaps during processing.
- The local model connection bypasses system proxies and verifies that port 11434 belongs to this user's Ollama process.
- No automated detector is perfect.
agent_may_read_redacted: truemeans the configured local checks passed, not that zero privacy risk is mathematically guaranteed. - Document text can try to mislead an LLM. The local audit is a supplemental detector, not a proof against adversarial prompt injection.