Skip to main content

piihush

CI License: MIT Sponsor

Fast PII and secret masking for AI applications. piihush is a Rust extension for Python that removes sensitive data from your text before it reaches an external model provider, in RAG pipelines, AI agents, chatbots, logs and data pipelines.

import piihush

piihush.mask("Call 555-123-4567 or email jane@example.com")
# 'Call [PHONE_REDACTED] or email [EMAIL_REDACTED]'
  • Fast: about 26 µs for a 1 KiB chunk, roughly 10 times faster than re.
  • Safe on untrusted input: matching runs in linear time, so a crafted document or pattern can't trigger catastrophic backtracking.
  • Context-aware: values next to a label such as Password: or Date of Birth: are masked while the label stays readable, and cards, IBANs and IPv6 addresses are validated rather than just pattern-matched.
  • Built for pipelines: it releases the GIL, and works with threads, fork() and pickling.
  • Local: no network calls, no models and no runtime dependencies in the default install. Better name detection is an optional add-on.

piihush reduces what reaches a model provider, but it does not catch everything. Read Limitations and Accuracy before you rely on it.

Contents

Where to use it

Use case What piihush protects
RAG pipelines Documents, chunks and questions before embedding or generation
AI agents Tool results, memory and retrieved data before they reach the model
Chatbots and support assistants Customer messages, with real values restored in the reply
LLM observability Prompts and replies before they reach logs or tracing tools
Application logs Emails, tokens and keys before logs are stored or shipped
Training data Datasets before fine-tuning, labelling or sharing with vendors
Data pipelines and analytics Free-text fields in ETL jobs and warehouses
Document processing Text extracted from tickets, emails, contracts and PDFs
Developer tools Code, configs and stack traces before they reach an AI assistant

Install

pip install piihush

Optional add-ons:

Install Adds Works on
pip install "piihush[llm]" Private chat with any LLM provider Everywhere piihush does
pip install "piihush[ner]" Name and place detection Everywhere piihush does
pip install "piihush[ner-fast]" The same, about 3 times faster Where onnxruntime is available

piihush[llm] also adds LLM detection, for catching more PII at a much higher cost per record.

Wheels cover Python 3.11+ on Linux (glibc and musl), macOS and Windows, on x86-64 and ARM64. You don't need a Rust toolchain.

To build the latest code from GitHub instead, install Rust first:

pip install git+https://github.com/aziaya/piihush

Quick start

For a one-off call, use the module-level functions. They use every built-in detector with the default settings.

import piihush

piihush.mask("Password: hunter2 and SSN 123-45-6789")
# 'Password: [PASSWORD_REDACTED] and SSN [SSN_REDACTED]'

piihush.mask_batch(["Mail a@b.io", "No PII here"])
# ['Mail [EMAIL_REDACTED]', 'No PII here']

piihush.detect("Mail jane@example.com")
# [Match(entity="EMAIL", start=5, end=21, text=<16 chars>)]

In an application, create one Masker and reuse it. Creating it compiles the patterns, which takes milliseconds. Using it is cheap and thread-safe.

from piihush import Masker

masker = Masker()
masker.mask("Reach me at +44 20 7946 0958")
# 'Reach me at [PHONE_REDACTED]'

Choosing a mode

Masker NerMasker
Install pip install piihush pip install "piihush[ner]"
Best for Structured PII and secrets at high volume Free text containing names and places
Time per short record About 20 µs About 160 ms, or 55 ms with ner-fast
Memory About 16 MB About 950 MB
Download None 433 MB model on first use
PII masked in testing 48.6% 69.7%

Start with Masker unless your text contains names you must protect. You can also use both: run Masker on everything, and NerMasker only on documents that need it.

What gets detected

Run piihush.ENTITIES to list these names in code.

Entity Covers Example input How it's confirmed
EMAIL Email addresses, including non-ASCII jane@example.com Format
PHONE US and Canadian numbers, international numbers, and national numbers with separators (415) 555-2671 Digit count and shape
SSN US SSNs with dashes, spaces or dots 123-45-6789 Reserved ranges
CREDIT_CARD Visa, Mastercard, Amex, Discover, JCB, UnionPay, Diners 4111 1111 1111 1111 Luhn checksum
IBAN International bank account numbers DE89 3704 0044 0532 0130 00 Mod-97 checksum
IP_ADDRESS IPv4 and IPv6 192.168.1.1 Address parsing
API_KEY AWS, GitHub, GitLab, Slack, Stripe, Google, OpenAI, Anthropic, Hugging Face, npm, PyPI AKIAIOSFODNN7EXAMPLE Prefix and format
JWT JSON Web Tokens eyJhbGci... Format
PRIVATE_KEY PEM private keys, including ones cut off by a chunk boundary -----BEGIN PRIVATE KEY----- Format
ADDRESS Street names, UK postcodes, ZIP+4, and labelled cities and ZIP codes 1669 Moxby Lane Format or label
ID_NUMBER Account, tax, passport, license and ID numbers Account Number: 19359789181296 Label required
PASSWORD Passwords and PINs Password: Zh-8 Label required
DATE_OF_BIRTH Dates of birth born on April 3rd, 1997 Label required
PERSON Names after a title, greeting or label Dr. Le Grand, Hi Neslihan Label required
USERNAME Usernames Username: kleppi1989 Label required

When a label is required, only the value is masked, so the text still makes sense:

piihush.mask("Date of Birth: 17/10/1944")
# 'Date of Birth: [DATE_OF_BIRTH_REDACTED]'

Configuring a Masker

Choose entities

Pass the entities you want. The default is all of them.

masker = Masker(["EMAIL", "API_KEY"])
masker.mask("a@b.io called 555-123-4567")
# '[EMAIL_REDACTED] called 555-123-4567'

To turn off one or two, filter the full list:

masker = Masker([e for e in piihush.ENTITIES if e != "IP_ADDRESS"])
masker.mask("from 10.0.0.1, a@b.io")
# 'from 10.0.0.1, [EMAIL_REDACTED]'

Add your own patterns

custom_patterns maps an entity name to a regular expression. Names use uppercase letters, digits and underscores.

masker = Masker(custom_patterns={"EMPLOYEE_ID": r"EMP-\d{6}"})
masker.mask("Badge EMP-123456")
# 'Badge [EMPLOYEE_ID_REDACTED]'

Patterns use Rust regex syntax. It has no lookaround or backreferences, which is what keeps matching linear-time.

Mask specific values

deny_list maps an entity name to exact values, such as customer names or specific email addresses. Matching ignores ASCII case and respects word boundaries, so Ann doesn't match inside Annual.

masker = Masker(deny_list={"CUSTOMER": ["Acme Corp", "Ann"]})
masker.mask("ACME CORP and Ann sent the Annual report")
# '[CUSTOMER_REDACTED] and [CUSTOMER_REDACTED] sent the Annual report'

Keep specific values

allow_list keeps values that a detector would otherwise mask, such as your own support address. A value must match the whole detected text. Case is ignored.

masker = Masker(allow_list=["support@ourcompany.example"])
masker.mask("Write to support@ourcompany.example, not a@b.io")
# 'Write to support@ourcompany.example, not [EMAIL_REDACTED]'

Change the placeholder

template sets the replacement text. {entity} becomes the entity name.

Masker(template="<{entity}>").mask("a@b.io")
# '<EMAIL>'

Masker(template="***").mask("a@b.io")
# '***'

Using piihush in a RAG pipeline

Mask at each point where text leaves your control.

Mask before you chunk

A value split across two chunks can't be detected, so mask the whole document first.

document = "Contact jane@example.com about the refund."

safe_document = masker.mask(document)
chunks = [safe_document[i : i + 500] for i in range(0, len(safe_document), 400)]

If you already have chunks, mask_batch is the fastest option. It keeps their order.

chunks = ["First chunk, a@b.io", "Second chunk"]
safe_chunks = masker.mask_batch(chunks)

Mask the user's question too

Text retrieved from your own store is only part of the prompt. Mask the question as well, and do it before any call to an external service, including embedding APIs.

question = "Why was the card 4111 1111 1111 1111 declined?"
safe_question = masker.mask(question)

Use it with LangChain

piihush works with plain strings, so it needs no special integration:

from langchain_core.documents import Document

documents = [Document(page_content="Mail a@b.io", metadata={"source": "crm"})]
safe_documents = [
    Document(page_content=text, metadata=doc.metadata)
    for doc, text in zip(documents, masker.mask_batch(d.page_content for d in documents))
]

Metadata isn't masked in this example. If yours contains PII, mask it too.

Using piihush with AI agents

An agent passes far more data to its model than the user typed: tool results, database rows, search hits, files and memory. Mask that data before it enters the agent's context.

def lookup_customer(customer_id):
    return "Customer: Jane Doe, jane@example.com, Card 4111 1111 1111 1111"

def safe_tool(tool, *args):
    return masker.mask(tool(*args))

safe_tool(lookup_customer, 42)
# 'Customer: [PERSON_REDACTED], [EMAIL_REDACTED], Card [CREDIT_CARD_REDACTED]'

When the agent must act on a real value, such as sending an email, use a PrivacyGuard. The model works with [EMAIL_1], and your code restores the real value at the moment a tool runs:

from piihush.llm import PrivacyGuard

guard = PrivacyGuard()
context = guard.mask("Reply to jane@example.com")  # goes to the model
tool_argument = "[EMAIL_1]"                        # the model's tool call
guard.restore(tool_argument)
# 'jane@example.com'

Treat tool arguments from a model as untrusted. A malicious document can try to steer an agent into using a restored value somewhere it shouldn't, so limit what each tool can do.

Chat with any LLM privately

PrivateChat masks your message, sends it to a provider, and restores the real values in the reply. The provider only ever sees tokens such as [EMAIL_1].

pip install "piihush[llm]"
from piihush.llm import PrivateChat

chat = PrivateChat("anthropic", model="your-model-name")  # reads ANTHROPIC_API_KEY
reply = chat.send("Write a reminder to email jane@example.com")
# The provider receives: Write a reminder to email [EMAIL_1]
# You receive: a reply containing jane@example.com

for text in chat.stream("Now make it shorter"):
    print(text, end="")
Provider Name API key variable
OpenAI openai OPENAI_API_KEY
Claude anthropic ANTHROPIC_API_KEY
Gemini gemini GEMINI_API_KEY
DeepSeek deepseek DEEPSEEK_API_KEY
Grok grok XAI_API_KEY
Ollama ollama None
Ollama Cloud ollama-cloud OLLAMA_API_KEY
llama.cpp llamacpp None

Any other OpenAI-compatible service works through base_url:

chat = PrivateChat(base_url="http://localhost:1234/v1", model="my-model")
  • Options: extra arguments go to the provider, as in chat.send(prompt, temperature=0, max_tokens=200).
  • Better detection: pass a guard, as in PrivateChat(..., guard=PrivacyGuard(NerMasker())).
  • Your own client: use guard.mask and guard.restore around any SDK, and guard.restore_stream for streaming.
  • Keep guards separate: use one per conversation or user. A guard remembers what it masked, so sharing one could reveal one user's values to another.

Tested with real models: OpenAI, Ollama Cloud and llama.cpp. The others use each provider's documented address and have only run against a simulated server.

Concurrency

Threads

A Masker is immutable, so one instance can be shared across threads. Scanning releases the GIL, so threads run in parallel.

asyncio

There is no async API, because masking is CPU work with nothing to wait for.

  • Short text: call mask directly. It takes microseconds, and handing off to a thread would cost more than it saves.
  • Large documents or batches: use asyncio.to_thread, so the event loop stays responsive.
import asyncio

async def handle(chunks):
    return await asyncio.to_thread(masker.mask_batch, chunks)

asyncio.run(handle(["a@b.io"]))
# ['[EMAIL_REDACTED]']

Batches

mask_batch uses every CPU core once a batch reaches 256 KiB, and a single thread below that. Its threads exist only during the call, so there is no pool to tune.

Multiple processes

A Masker can be pickled for multiprocessing, and mask_batch is safe after fork(), so it works with Celery and gunicorn workers.

Better name detection

The default Masker only finds a name or city when something points to it, such as a title or label. To find them in free text, install the optional model:

pip install "piihush[ner]"
from piihush.ner import NerMasker

ner_masker = NerMasker()  # takes the same options as Masker
ner_masker.mask("Neslihan Kaya wrote from Berlin")
# '[PERSON_REDACTED] wrote from [ADDRESS_REDACTED]'

NerMasker runs all the Masker rules, then adds what the model finds. Once it finds a name, it masks that name everywhere in the same text.

  • Model: the first run downloads dslim/bert-base-NER (MIT, 433 MB), pinned to an exact revision and cached by Hugging Face.
  • Privacy: after that download, the model runs on your machine and your text never leaves it.
  • Offline use: once the model is cached, set HF_HUB_OFFLINE=1.
  • Speed: the model runs on NumPy, about 160 ms per short record, so it installs everywhere. With pip install "piihush[ner-fast]" it uses onnxruntime instead, at about 55 ms with identical results. onnxruntime isn't available on every platform.
  • Memory: about 950 MB.
  • Other models: pass any callable that returns (entity, start, end) tuples, where the offsets are Python string indices:
def my_recognizer(text):
    start = text.find("Project Falcon")
    return [("PROJECT", start, start + 14)] if start >= 0 else []

custom = NerMasker(recognizer=my_recognizer)
custom.mask("Status of Project Falcon")
# 'Status of [PROJECT_REDACTED]'

LLM detection

LlmRecognizer asks an LLM to find PII that the rules miss. Use it with NerMasker, which keeps all the rules running alongside it. The model can be local or hosted.

from piihush.llm import LlmRecognizer
from piihush.ner import BertRecognizer, NerMasker, combine

llm = LlmRecognizer("ollama", model="your-model-name")

rules_and_llm = NerMasker(recognizer=llm)
strongest = NerMasker(recognizer=combine(BertRecognizer(), llm))

Found values are labelled PII, so they become [PII_REDACTED].

Before you use it:

  • The LLM sees your raw text. Use a model or service you trust with that data.
  • A document can talk it out of reporting. In testing, one "ignore your instructions" sentence stopped the LLM reporting anything. The rules and NER model aren't affected, so you fall back to their results. Don't use an LLM on its own.
  • It costs far more. Expect seconds per record, plus any provider fees.
  • It only adds masking. A value counts only if it really appears in the text. If a request fails, it raises LLMError instead of letting text through unchecked.

API reference

Module functions

These use a shared Masker with default settings, created on first use.

Function Returns
piihush.mask(text) The masked str
piihush.mask_batch(texts) A list[str], in input order
piihush.detect(text) A list[Match], sorted by position
piihush.ENTITIES The names of the built-in entities
piihush.__version__ The installed version

Masker

Masker(entities=None, *, custom_patterns=None, deny_list=None, allow_list=None, template=None)
Argument Type Default Meaning
entities Sequence[str] All built-in entities Built-in detectors to enable. [] enables none
custom_patterns Mapping[str, str] None Entity name to regular expression
deny_list Mapping[str, Sequence[str]] None Entity name to exact values to mask
allow_list Sequence[str] None Exact values to keep
template str "[{entity}_REDACTED]" Replacement text

A bad configuration raises ValueError: an unknown entity, an invalid pattern, an invalid entity name, or an empty deny-list value.

Method Returns Notes
mask(text) str Returns the same object if nothing was found
mask_batch(texts) list[str] Accepts any iterable of str, including generators
detect(text) list[Match] Doesn't change the text

Passing something other than str raises TypeError, and so does passing a single str to mask_batch.

Match

Attribute Type Meaning
entity str Entity name
start, end int Python string indices, so text[start:end] is the value
text str The matched value

repr() hides the value so it doesn't end up in your logs, but match.text still holds it. Take care not to log that attribute.

piihush.ner.NerMasker

Takes the same arguments as Masker, plus recognizer, and has the same mask, mask_batch and detect methods. Unlike Masker, it can't be pickled, and mask_batch handles one text at a time. Without the extra packages, it raises ImportError with the install command.

Troubleshooting

A value wasn't masked. Check whether it matches a format in What gets detected. Add known values to deny_list, add a format to custom_patterns, or use NerMasker for names and places.

Too much was masked. Add the value to allow_list, or leave out the entity that matched it. Use detect to see which one that was:

[m.entity for m in piihush.detect("Hi Team, mail a@b.io")]
# ['PERSON', 'EMAIL']

A card number wasn't masked. Card numbers must pass the Luhn checksum, so made-up test numbers are often skipped. Real card numbers pass. Use custom_patterns if you want every 16-digit number masked.

ValueError: invalid pattern. Rust regex has no lookaround or backreferences.

ImportError from NerMasker. Run pip install "piihush[ner]".

CERTIFICATE_VERIFY_FAILED from PrivateChat. Python has no trusted certificates, which is common with the python.org installer on macOS. Run pip install "piihush[llm]".

Limitations

piihush is one layer of a privacy strategy. It does not make text anonymous, and it doesn't by itself satisfy GDPR, HIPAA or any other regulation.

  • Nothing here catches everything. In testing, about 3 in 10 labelled values got past the best mode. Review the output before relying on it for sensitive data.
  • The default mode finds patterns, not meaning. A name or city with no title, greeting or label around it isn't detected, and neither are medical details. Use deny_list for known values, or the optional model for free text.
  • Unlabelled numbers are mostly left alone. A bare 9-digit SSN or account number looks like any other number. Use custom_patterns if you need them masked.
  • Harmless text is sometimes masked. Context rules can over-match. In testing, 1.8% of ordinary words were lost with Masker and 3.4% with NerMasker.
  • Split values can't be detected. Mask documents before you chunk them. A private key whose BEGIN line lands in an earlier chunk is the same case.
  • Obfuscated text isn't detected, such as jane at example dot com or full-width digits. Apply unicodedata.normalize("NFKC", text) first if your input may contain those.
  • mask is one-way. Original values can't be restored from its output. Use PrivacyGuard when you need them back.
  • English is the tested language. Other languages haven't been measured, and the model and most labels are English.

Accuracy

Tested on 3,000 English records of the independent ai4privacy/pii-masking-400k dataset, kept apart from the data used to build the rules. Each tool ran with its defaults on the same machine. "Masked" means the exact value no longer appears in the output.

Tool PII masked Ordinary words kept Time per record
piihush.ner.NerMasker 69.7% 96.6% About 160 ms (55 ms with ner-fast)
Presidio 2.2 (en_core_web_lg) 64.8% 94.8% 15 ms
piihush.Masker 48.6% 98.2% About 20 µs
scrubadub 2.0 13.8% 98.3% 1.2 ms
pyveil 0.3 13.1% 100% 0.3 ms
pii-veil 0.1 10.7% 100% 0.2 ms
Masked Masker NerMasker Presidio
Email 100% 100% 100%
Phone number 95% 95% 63%
Given name / surname 37% / 38% 89% / 92% 66% / 64%
City 17% 86% 71%
Street 84% 97% 71%
ZIP or postcode 47% 50% 35%
Social security number 90% 90% 94%
Tax number 83% 83% 96%
Credit card number 29% 29% 84%
Driver's license 51% 51% 84%
Account number 43% 43% 74%
Date of birth 36% 36% 84%
Username 22% 26% 33%

Read these results with care:

  • Presidio leads on many number types. It masks many more unlabelled numbers, and loses more ordinary text doing so.
  • Random card numbers count against piihush. The dataset's card numbers are random, so most fail the checksum piihush uses to avoid false positives.
  • The data is not everything. It has no API keys, so it doesn't reflect piihush's secret detection, and pii-veil is built mainly for Polish data.
  • The test data was used more than once. NerMasker was scored on it twice, because two leaks were fixed after the first run. Both were found by hand, on other text, and its score rose by 0.1 points.

To reproduce these results, see the instructions in benches/accuracy.py.

LLM detection results

Measured with Qwen 2.5 1.5B running locally on 200 unused records from the same dataset (benches/llm_detection.py). Other models will score differently.

Setup PII masked Ordinary words kept Time per record
Masker 41.7% 99.1% 0.1 ms
Masker + LLM 56.8% 95.3% 2.2 s
NerMasker 61.8% 96.6% About 55 ms
NerMasker + LLM 71.9% 93.3% About 2.2 s
  • Best coverage: NerMasker + LLM masked the most, at about 40 times the cost of NerMasker alone, and it replaced more ordinary text.
  • Prompt injection: one injected sentence took the LLM from finding 40 values in 50 records to finding 0.
  • Small sample: with 200 records, treat small differences as noise.

Performance

Measured with benches/bench.py on a 2016 Intel Core i7-6920HQ (8 threads) with all detectors on, using generated text with a PII value about every 40 words. The re column is a smaller, roughly equivalent set of 6 patterns. Your results will vary, so run the script on your own hardware and data.

Input piihush Python re
256 B 7.2 µs 52 µs
1 KiB 26 µs 271 µs
4 KiB 95 µs 1,049 µs
1 MiB 25 ms 221 ms
  • Batch throughput: 145 MB/s for mask_batch, compared with 29 MB/s for a single-threaded loop.
  • Worst case: the slowest hostile 1 MiB input tested took 841 ms.

Where a pattern has a literal to search for, the Rust regex, aho-corasick and memchr crates use SIMD (AVX2, SSSE3 or NEON), selected at runtime. Detectors also skip the regex entirely when a required byte such as @ is absent.

How it works

  1. Each detector scans the text. Candidates that fail validation are rejected, and the scan resumes inside the rejected candidate so it can't hide a real match.
  2. Overlapping matches merge into one span, so no fragment of either survives.
  3. The spans are replaced with placeholders. Text with nothing to mask is returned as the same object, without copying.

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md, and report security issues as described in SECURITY.md. Use only made-up data in tests, issues and pull requests.

Sponsor

If piihush is useful to you, you can support its development through GitHub Sponsors.

License

MIT

Metadata

Release files for piihush 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for piihush 0.2.1
File Size Uploaded Provenance Actions
piihush-0.2.1.tar.gz 99.1 kB With provenance Details for piihush-0.2.1.tar.gz

Built distributions (wheels)

Table of built distributions (wheels) for piihush 0.2.1
File Actions
piihush-0.2.1-cp311-abi3-win_arm64.whl CPython 3.11 abi3 Windows ARM64 Details for piihush-0.2.1-cp311-abi3-win_arm64.whl
piihush-0.2.1-cp311-abi3-win_amd64.whl CPython 3.11 abi3 Windows x86-64 Details for piihush-0.2.1-cp311-abi3-win_amd64.whl
piihush-0.2.1-cp311-abi3-musllinux_1_2_x86_64.whl CPython 3.11 abi3 Linux musl 1.2+ x86-64 Details for piihush-0.2.1-cp311-abi3-musllinux_1_2_x86_64.whl
piihush-0.2.1-cp311-abi3-musllinux_1_2_aarch64.whl CPython 3.11 abi3 Linux musl 1.2+ ARM64 Details for piihush-0.2.1-cp311-abi3-musllinux_1_2_aarch64.whl
piihush-0.2.1-cp311-abi3-manylinux_2_28_x86_64.whl CPython 3.11 abi3 Linux glibc 2.28+ x86-64 Details for piihush-0.2.1-cp311-abi3-manylinux_2_28_x86_64.whl
piihush-0.2.1-cp311-abi3-manylinux_2_28_aarch64.whl CPython 3.11 abi3 Linux glibc 2.28+ ARM64 Details for piihush-0.2.1-cp311-abi3-manylinux_2_28_aarch64.whl
piihush-0.2.1-cp311-abi3-macosx_11_0_arm64.whl CPython 3.11 abi3 macOS 11.0+ ARM64 Details for piihush-0.2.1-cp311-abi3-macosx_11_0_arm64.whl
piihush-0.2.1-cp311-abi3-macosx_10_12_x86_64.whl CPython 3.11 abi3 macOS 10.12+ x86-64 Details for piihush-0.2.1-cp311-abi3-macosx_10_12_x86_64.whl

Total release size: 6.5 MB

Release files / piihush-0.2.1.tar.gz

Download URL piihush-0.2.1.tar.gz
Size 99.1 kB
Tags Source
SHA-256 checksum
How to use checksums
a1c22fe294fdfe3ccd3a6315d140baa2685c3a7934562b7cc96dc6d0ef4dbe76
BLAKE2b-256 checksum
How to use checksums
82df7b2c6b37a977424ee560c50eabe6e96f30a12144fb160699b190c0b7445b
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-win_arm64.whl

Download URL piihush-0.2.1-cp311-abi3-win_arm64.whl
Size 644.9 kB
Tags CPython 3.11 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
d4890b84f4ce1675f940f8cb9785bd851e7b59a5d2a05a26def4bf83fb79a442
BLAKE2b-256 checksum
How to use checksums
de87452b0ae3c3bc10dc00f4d9a71e5544cd61d7c17bb35bc2bc2ce71a3c59d0
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-win_amd64.whl

Download URL piihush-0.2.1-cp311-abi3-win_amd64.whl
Size 688.5 kB
Tags CPython 3.11 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
bd73dd98b2bf4000e5f9445378b9f902ad792fad3d5f754cf40ee8eecd0033a9
BLAKE2b-256 checksum
How to use checksums
a1de80332b88340ebce305a858cc8d60f6b29662bb70ceeca15d260fa36f30be
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-musllinux_1_2_x86_64.whl

Download URL piihush-0.2.1-cp311-abi3-musllinux_1_2_x86_64.whl
Size 1.0 MB
Tags CPython 3.11 Linux musl 1.2+ x86-64 abi3
SHA-256 checksum
How to use checksums
9227211872dceefcc90fb5c8e59e3c1956c796069a005db0ca67d45525c94b05
BLAKE2b-256 checksum
How to use checksums
8330f45fc28bc7d2da4b8ac74c5ef402a8ff6f0f62d08107e4124cbc05785354
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-musllinux_1_2_aarch64.whl

Download URL piihush-0.2.1-cp311-abi3-musllinux_1_2_aarch64.whl
Size 951.8 kB
Tags CPython 3.11 Linux musl 1.2+ ARM64 abi3
SHA-256 checksum
How to use checksums
7449fe3852b302183a7fe8e22775c5fe5a701ab3beb5068dda2c5ca977f993b7
BLAKE2b-256 checksum
How to use checksums
f03df66285ff29cfe6d49b761fe7c9784b094cee21faa6984319f64445cf31f1
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-manylinux_2_28_x86_64.whl

Download URL piihush-0.2.1-cp311-abi3-manylinux_2_28_x86_64.whl
Size 817.3 kB
Tags CPython 3.11 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
10d82b7010615674cf6639604af5cf8909b16035a740782a2b6a69304f4bc45e
BLAKE2b-256 checksum
How to use checksums
dddf1ba669c3f4f524dc0821fe66c6d807d3c1ca4f7452962446a7e8a8810ef7
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-manylinux_2_28_aarch64.whl

Download URL piihush-0.2.1-cp311-abi3-manylinux_2_28_aarch64.whl
Size 773.2 kB
Tags CPython 3.11 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
c073c7de5c169ff7afc0e071be68e9f5c075cf09f38e9b55b99551b29bbb4095
BLAKE2b-256 checksum
How to use checksums
050cd9a2540326e18bc77a90691710feb99b026bbb2c2fc92a585c71feb25a1a
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-macosx_11_0_arm64.whl

Download URL piihush-0.2.1-cp311-abi3-macosx_11_0_arm64.whl
Size 698.3 kB
Tags CPython 3.11 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
a51277b113726dcaebfa02fe42a9e8522a080079b86d0a7bee2c78bb6f19a29d
BLAKE2b-256 checksum
How to use checksums
10439408733296afc35805b08d913a483a8f471357d036df4b444da7ce678a71
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 Nov 1, 2026.

Transparency log

Release files / piihush-0.2.1-cp311-abi3-macosx_10_12_x86_64.whl

Download URL piihush-0.2.1-cp311-abi3-macosx_10_12_x86_64.whl
Size 756.9 kB
Tags CPython 3.11 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
bd73053255ba9c31953a4242189eb552dd8c91f5c321b671c5b3b5dda6a31d28
BLAKE2b-256 checksum
How to use checksums
dbba30fd442a7a227927ef1c6b76a9466204655c67cb05bb8926989a8a10e899
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 Nov 1, 2026.

Transparency log

Security for piihush 0.2.1

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 9 files in this release have been uploaded with provenance metadata.

Select from 9 files

Attestation for piihush-0.2.1.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 Nov 1, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page