# Pii Safe Documents

> Processes sensitive local documents through PII Guard and a local Ollama model into a reversible redacted copy, without letting the main agent read the original or restored contents.

- Skill: `danyuchn/pii-safe-documents` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add danyuchn/pii-safe-documents`
- Raw SKILL.md: https://api.skillmd.com/api/skills/danyuchn/pii-safe-documents/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: danyuchn (https://skillmd.com/u/danyuchn)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/danyuchn/pii-safe-documents

---


# 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:

1. Read, preview, search, summarize, diff, upload, attach, or otherwise inspect the original file.
2. Use `cat`, `sed`, `head`, `tail`, `grep`, `rg`, `strings`, Python, a document reader, a browser, or any other tool on the original file.
3. Read or reveal `mapping.private.json`, private worker files, raw logs, or the restored output.
4. Pass the original path to a subagent, cloud model, MCP server, website, or third-party API.
5. Run PII Guard directly. Its libraries may echo source text in warnings; only the bundled wrapper may invoke it.
6. Debug a failure by opening the original, mapping, worker output, or restored document.
7. Run `git diff`, content scans, or indexing over a directory that contains the original or restored output.
8. Reach the annotation page, run the `review` subcommand, or read a term file the user wrote for `mask`. 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：

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
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 mode `0600`.
- 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: true` means 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.

