Safe LLM usage by automatically masking PII/PFI before it leaves your system.
Project description
pii-shield 🔒
Safe LLM usage by automatically masking PII/PFI before it leaves your system.
PII Shield intercepts sensitive data, replaces it with typed UUID tokens (<<EMAIL_3f2a1b>>), sends the clean text to your LLM, then restores original values in the response — all transparently.
Installation
pip install pii-shield # core (regex detection only)
pip install "pii-shield[nlp]" # + spaCy NER for names & orgs
pip install "pii-shield[openai]" # + OpenAI wrapper
pip install "pii-shield[anthropic]" # + Anthropic wrapper
pip install "pii-shield[all]" # everything
Quick Start
Basic masking & restoration
from pii_shield import PIISession
with PIISession() as session:
masked, mapping = session.mask(
"Hi, I'm Alice Smith. Reach me at alice@acme.com or 415-555-0199."
)
print(masked)
# → "Hi, I'm Alice Smith. Reach me at <<EMAIL_d3a1f9>> or <<PHONE_7b2c04>>."
# (Names need spaCy NER; emails & phones detected by regex)
llm_response = your_llm_call(masked) # send safe text to LLM
clean = session.restore(llm_response) # PII restored in response
print(clean)
# Session auto-saved to encrypted vault on context manager exit
With the built-in LLM wrapper (OpenAI)
import openai
from pii_shield import PIISession
from pii_shield.llm_client import LLMClient
client = LLMClient(
session=PIISession(),
backend="openai",
openai_client=openai.OpenAI(),
model="gpt-4o",
system_prompt="You are a helpful customer support agent.",
)
result = client.chat(
"Please look up account for john@corp.com, SSN 123-45-6789."
)
print(result.restored_text) # PII restored
print(result.masked_prompt) # What was actually sent to OpenAI
With Anthropic Claude
import anthropic
from pii_shield import PIISession
from pii_shield.llm_client import LLMClient
client = LLMClient(
session=PIISession(),
backend="anthropic",
anthropic_client=anthropic.Anthropic(),
model="claude-sonnet-4-20250514",
)
result = client.chat("Summarise the risk profile for card 4111111111111111.")
print(result.restored_text)
Multi-turn conversation
client = LLMClient(session=PIISession(), backend="openai", openai_client=..., model="gpt-4o")
r1 = client.chat("My name is Bob and my email is bob@corp.com.")
r2 = client.chat("What was the email I just gave you?")
# History is maintained; PII masked/restored across all turns
Entity Types Detected
| Entity | Method | Example |
|---|---|---|
EMAIL |
Regex | alice@acme.com |
PHONE |
Regex | +1 415-555-0199 |
SSN |
Regex | 123-45-6789 |
CREDIT_CARD |
Regex + Luhn | 4111 1111 1111 1111 |
ADDRESS |
Regex | 123 Main St, NY 10001 |
IP_ADDRESS |
Regex | 192.168.1.100 |
PASSPORT |
Regex | A12345678 |
DOB |
Regex | 01/15/1990 |
NAME |
spaCy NER* | Alice Smith |
ORG |
spaCy NER* | Goldman Sachs |
*Requires pip install "pii-shield[nlp]" + python -m spacy download en_core_web_sm
Token Format
<<ENTITY_TYPE_shortid>>
Examples:
<<EMAIL_d3a1f9>>
<<PHONE_7b2c04>>
<<SSN_1a9e3c>>
<<CREDIT_CARD_f00b12>>
- Deterministic within a session: same raw value → same token (LLM output stays coherent)
- Cross-session isolated: different sessions never share tokens
- Type-preserving: LLM sees the entity category without the actual data
Vault & Key Management
Session mappings are persisted in an AES-encrypted file (Fernet / AES-128-CBC + HMAC-SHA256).
On first use, a key is auto-generated and written to .env:
PII_SHIELD_KEY="base64url-encoded-32-byte-key"
Keep this key safe — losing it means mappings in the vault cannot be decrypted.
To use a custom vault path or bring your own key:
from pii_shield import PIIVault, PIISession
vault = PIIVault(
vault_path="/secure/path/pii.vault.enc",
env_path="/secure/path/.env",
)
session = PIISession(vault=vault)
Advanced Usage
Restrict entity types
from pii_shield import PIISession, EntityType
session = PIISession(entity_types=[EntityType.EMAIL, EntityType.CREDIT_CARD])
Resume a named session
# Session 1 — mask and save
s = PIISession(session_id="order-12345")
masked, _ = s.mask("Card: 4111111111111111")
s.save()
# Later — restore using the same session
s2 = PIISession(session_id="order-12345")
print(s2.restore(masked)) # → "Card: 4111111111111111"
Custom LLM backend
def my_llm(prompt: str) -> str:
# Call your own model
return my_model.generate(prompt)
client = LLMClient(session=PIISession(), backend="custom", custom_fn=my_llm)
Architecture
Input Text
│
▼
[PIIDetector] regex patterns + optional spaCy NER
│
▼
[PIIReplacer] value → <<TYPE_shortid>> (deterministic, collision-safe)
│
▼
Masked Text ──→ LLM API (OpenAI / Anthropic / custom)
│
▼
LLM Response (may echo tokens back)
│
▼
[PIIRestorer] <<TYPE_shortid>> → original value
│
▼
Restored Output
[PIIVault] ←→ AES-encrypted session mappings on disk
Running Tests
pip install pytest
pytest tests/ -v
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pii_shield_llm-1.0.0.tar.gz.
File metadata
- Download URL: pii_shield_llm-1.0.0.tar.gz
- Upload date:
- Size: 18.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a479ee89b35d7923f8638b21251228b598a87e3f6073e6e4357f1680cf1e591
|
|
| MD5 |
b3db908c7504fae55c158183a9453a61
|
|
| BLAKE2b-256 |
69a186c9904a27c1ee3dbb7c12a002ce83360b83ee47556b62f4a0ca9a360c22
|
Provenance
The following attestation bundles were made for pii_shield_llm-1.0.0.tar.gz:
Publisher:
publish.yml on bharatjarvis/pii-shield-llm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pii_shield_llm-1.0.0.tar.gz -
Subject digest:
7a479ee89b35d7923f8638b21251228b598a87e3f6073e6e4357f1680cf1e591 - Sigstore transparency entry: 1293640001
- Sigstore integration time:
-
Permalink:
bharatjarvis/pii-shield-llm@3fca3585e3b2f91a34ed0514a01fff6b8aaf582e -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/bharatjarvis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fca3585e3b2f91a34ed0514a01fff6b8aaf582e -
Trigger Event:
release
-
Statement type:
File details
Details for the file pii_shield_llm-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pii_shield_llm-1.0.0-py3-none-any.whl
- Upload date:
- Size: 16.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
658f9af8c8697f56712995be6575af632e7640451625bd910f6f5518f0f479ee
|
|
| MD5 |
cc34092534656e3794d525f8be6fc3a4
|
|
| BLAKE2b-256 |
a5ca742cbe94c8c94c51325f80b4c803edcb782ca0fbcd17e9326b6c9395df3c
|
Provenance
The following attestation bundles were made for pii_shield_llm-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on bharatjarvis/pii-shield-llm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pii_shield_llm-1.0.0-py3-none-any.whl -
Subject digest:
658f9af8c8697f56712995be6575af632e7640451625bd910f6f5518f0f479ee - Sigstore transparency entry: 1293640008
- Sigstore integration time:
-
Permalink:
bharatjarvis/pii-shield-llm@3fca3585e3b2f91a34ed0514a01fff6b8aaf582e -
Branch / Tag:
refs/tags/v1.0.0 - Owner: https://github.com/bharatjarvis
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3fca3585e3b2f91a34ed0514a01fff6b8aaf582e -
Trigger Event:
release
-
Statement type: