Metadata-Version: 2.4
Name: mloda-pii
Version: 0.5.0
Summary: Claude Code hook and mloda FeatureGroup that pseudonymize PII before the model sees it
Author-email: Tom Kaltofen <info@mloda.ai>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://mloda.ai
Keywords: mloda,mloda-plugin,pii,privacy,redaction,anonymization,claude-code,hook
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <3.15,>=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: mloda[pyarrow]<0.18.0,>=0.17.0
Requires-Dist: tomli>=1.0.0; python_version < "3.11"
Provides-Extra: hook
Requires-Dist: mloda-enterprise[anonymizer]>=0.11.0; extra == "hook"
Provides-Extra: dev
Requires-Dist: mloda-testing[binary-model]>=0.11.0; extra == "dev"
Requires-Dist: mloda-enterprise>=0.11.0; extra == "dev"
Requires-Dist: tox; extra == "dev"
Requires-Dist: tox-uv; extra == "dev"
Requires-Dist: uv>=0.13.4; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: bandit; extra == "dev"
Provides-Extra: presidio
Requires-Dist: presidio-analyzer>=2.2.364; extra == "presidio"
Dynamic: license-file

[![PyPI](https://img.shields.io/pypi/v/mloda-pii.svg)](https://pypi.org/project/mloda-pii/)
[![Python](https://img.shields.io/badge/python-3.10--3.14-blue.svg)](https://www.python.org/)
![License](https://img.shields.io/badge/license-proprietary-lightgrey.svg)
[![mloda](https://img.shields.io/badge/built%20with-mloda-blue.svg)](https://github.com/mloda-ai/mloda)

# mloda-pii

mloda-pii is a Claude Code hook that replaces
identifiers (emails, phone numbers, IBANs, IP addresses, your own names and ID formats) in tool results with tokens
such as `PII_EMAIL_ADDRESS_...` before the model sees them. Detection runs on Microsoft Presidio, with no spaCy model. Your local tools and Claude's replies on your screen keep the real
values. Every token is a keyed HMAC-SHA256, computed through [mloda](https://github.com/mloda-ai/mloda). It guards
against accidental exposure and does not see everything the model gets; see Limits below.

The hook's steps ship as mloda FeatureGroups, so a pipeline can detect, tokenize and restore text columns the same way.

## Setup

```bash
uv tool install "mloda-pii[hook,presidio]"   # or pip install it into a virtualenv that stays in place
cd your-project
mloda-pii init                      # paste your invite when asked
```

Then restart Claude Code. On a terminal `init` asks for the invite with hidden input; scripts pass the invite file, `-`
for stdin, or the token itself. `init` creates the master key, stores and verifies the license, runs the hook once on a
test value, and only then registers it in `.claude/settings.local.json`. It also creates `.mloda-pii/config.toml`, kept
out of git: names (`[deny]`) and ID formats (`[patterns]`) to detect, and values to keep in clear (`[allow]`, loopback
addresses by default; also the presets `documentation` and `private_networks`). Unknown entries are rejected. Running
`init` again is safe. The first event of a session starts a warm server for the project and the session end stops it;
an event the server does not answer is blocked.

| Option | Effect |
| --- | --- |
| `--user` | Register in `~/.claude/settings.json`, for every project. |
| `--project` | Register in the committed `.claude/settings.json`; every teammate installs mloda-pii and runs `init --project`. |
| `--mode local` | No license key, see below. |
| `--key-file <path>` | Import an existing master key instead of creating one. |

Ask for an invite at info@mloda.ai or via [mloda.ai](https://mloda.ai).

## Modes

| Mode | License key | Needs |
| --- | --- | --- |
| `licensed` (default) | Required. Without a valid license every gated event blocks: tool results are withheld, tool calls denied, prompts blocked. | An invite. |
| `local` | None. | An anonymizer-rs `anonymizer` binary on `PATH` or in `ANONYMIZER_BINARY`; ask at info@mloda.ai. |

Both modes give the same tokens from the same master key; there is no fallback between them.

## Back up your master key

The master key lives in `~/.config/mloda-pii/master.key`. Back it up: every token depends on it, and a lost key
changes all of them. `init` never overwrites an existing key.

## Everyday commands

- `mloda-pii status`: the key, the license and its expiry, where the hook is registered, the last events. Exits 1 when
  something is missing.
- `mloda-pii uninstall`: removes the hook entries (same `--user` / `--project` flags). `--purge` also deletes the
  master key and the stored license, after asking (`--yes` skips it).
- `mloda-pii license install | status | remove`: manage the license alone.
- `mloda-pii stats`: per-day counts from the audit log, which never holds values.

A prompt that contains raw identifiers is blocked and a pseudonymized copy is shown to resubmit.

| Variable | Effect |
| --- | --- |
| `MLODA_PII_KEY_FILE` | Master key file (default `~/.config/mloda-pii/master.key`). |
| `MLODA_PII_CONFIG` | The project config (default `.mloda-pii/config.toml`). |
| `MLODA_PII_ENTITIES` | Presidio labels to detect (default `EMAIL_ADDRESS,PHONE_NUMBER,IBAN_CODE,IP_ADDRESS`). |
| `MLODA_PII_WRAP_BASH` | `1` routes failed Bash output through the hook too. |
| `MLODA_PII_TOKEN_STYLE` | `hex` (default, `PII_EMAIL_ADDRESS_<32 hex>`) or `readable` (`PII_EMAIL_ADDRESS_1`, numbered per project). |
| `MLODA_PII_FAIL_CLOSED` | On by default. `0` lets an action pass when the hook itself fails; a license block always blocks. |
| `MLODA_PII_REVERSE_MCP` | `1` also puts real values back into MCP tool input; a remote MCP server then receives them. |
| `MLODA_PII_AUDIT_LOG` | Audit log path (default `~/.local/state/mloda-pii/audit.jsonl`); `off` disables it. |
| `MLODA_LICENSE_FILE`, `MLODA_LICENSE_KEY` | Take precedence over the stored license; `init` checks the one in use and self-tests with it. |

Command options: `mloda-pii <command> --help`.

## The FeatureGroups

```python
from mloda.user import Feature, Options

feature = Feature("email__pii_anonymized", Options(context={"pii_key_file_env": "KEY_FILE"}))

envs = {"pii_key_file_env": "KEY_FILE", "pii_vault_path_env": "VAULT_PATH"}
text = Feature("notes__pii_marked_hash_pseudonymized", Options(context=envs, cascade_context_keys=frozenset(envs)))
```

`<column>__pii_anonymized` returns the 64-hex keyed HMAC-SHA256 token of a string column (or of each element of a
list<string> column). `pii_key_file_env` names the environment variable that holds the path to the master key file. It
needs the anonymizer-rs binary, like the local mode.

`<column>__pii_<marked_hash|counter>_pseudonymized` replaces the identifiers in a text column with tokens, as the hook
does; `<column>__pii_<strategy>_restored` maps them back from the vault named by `pii_vault_path_env`.
`<column>__pii_spans` returns the detected spans. Detection needs the `presidio` extra. `pii_config_env` names the
variable that holds the path of a `config.toml` (the hook's project config: allow list, deny words, patterns); without
it the defaults apply.

## Limits

- The hook guards against accidental exposure, not a model that tries to recover values: a Bash command gets the real
  value and can return it disguised, which detection misses.
- Hooks never see `@file` mentions, CLAUDE.md and memory files, the client's own reminders, images or compaction
  summaries; those reach the model unchanged.
- A crashed or timed-out hook on a tool result lets the original output through.
- The license gate is a speed bump: the local mode makes the same tokens without one, by design.
- Tokens are still personal data under GDPR; whoever holds the key can recompute them.
- The local vault under `~/.local/state/mloda-pii/` (or `$XDG_STATE_HOME`) maps tokens back to raw values; nothing expires it, and
  `uninstall --purge` leaves it in place.

## License

mloda-pii is proprietary and source-available, under the mloda-pii License in the `LICENSE` file of the package. Use
in your daily work is permitted with a license key from mloda, also when the key is free, or in the local mode with an
anonymizer binary mloda provided. The license text is binding over this summary.

## Related

- [mloda](https://github.com/mloda-ai/mloda): declarative data and feature access, the framework mloda-pii runs on.
- [mloda-registry](https://github.com/mloda-ai/mloda-registry): plugins and guides for mloda.
