Metadata-Version: 2.5
Name: presidio-audit
Version: 1.0.0
Summary: Audited wrapper around Presidio's anonymizer: every decision logged, exportable as compliance evidence.
Project-URL: Repository, https://github.com/cheneeheng/presidio-compliance-stack
Project-URL: Issues, https://github.com/cheneeheng/presidio-compliance-stack/issues
License: Apache-2.0
License-File: LICENSE
Keywords: audit,compliance,gdpr,pdpa,pii,presidio
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.11
Requires-Dist: presidio-anonymizer>=2.2.363
Provides-Extra: dev
Requires-Dist: build; extra == 'dev'
Requires-Dist: mypy; extra == 'dev'
Requires-Dist: pre-commit; extra == 'dev'
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest-randomly; extra == 'dev'
Requires-Dist: pytest-repeat; extra == 'dev'
Requires-Dist: ruff==0.16.1; extra == 'dev'
Requires-Dist: twine; extra == 'dev'
Description-Content-Type: text/markdown

# presidio-audit

Presidio tells you what it found and hands you the redacted text. It does not keep
a record. `presidio-audit` is a drop-in wrapper that logs every anonymization
decision to a tamper-evident local file and exports it as a compliance artifact.

Country-neutral: the evidence problem is the same under GDPR, HIPAA, CCPA or
Malaysia's PDPA, and nothing in this package is Malaysia-specific.

```bash
pip install presidio-audit
```

## Use

```python
from presidio_analyzer import AnalyzerEngine
from presidio_audit import AuditedAnonymizerEngine, JsonlAuditStore, export_report

analyzer = AnalyzerEngine()
store = JsonlAuditStore("./audit.jsonl")
engine = AuditedAnonymizerEngine(store)

results = analyzer.analyze(text=text, language="en")
safe = engine.anonymize(text, results, source_ref="ticket-42")

export_report(store, start="2026-07-01", end="2026-07-31", fmt="md", out_path="july.md")
```

`anonymize()` takes and returns exactly what Presidio's `AnonymizerEngine.anonymize()`
does. The only new argument is `source_ref`, your identifier for the document or
request being processed.

## Reversible pseudonymization

Encrypt instead of destroy, so an external service's reply can be rehydrated locally:

```python
from presidio_audit import AuditedDeanonymizeEngine, reversible_operators

safe = engine.anonymize(text, results, operators=reversible_operators(key, ["MY_NRIC"]))
reply = call_your_llm(safe.text)
restored = AuditedDeanonymizeEngine(store).deanonymize(reply, key=key)
```

By default `deanonymize()` scans for ciphertext rather than requiring you to have
kept the spans: tokens that decrypt cleanly are restored, everything else is left
untouched. Pass `entities=` if you did keep them. Both directions are audited —
re-identification is a compliance event in its own right.

## CLI

```bash
presidio-audit export --store ./audit.jsonl --from 2026-07-01 --to 2026-07-31 \
                      --format md --out july.md
presidio-audit export --store ./audit.jsonl          # whole store, JSON to stdout
presidio-audit verify --store ./audit.jsonl          # chain check only
```

Exit codes: `0` fine, `1` bad arguments or unreadable store, `2` some lines were
unreadable (report still produced), `3` hash chain broken (report still produced).
Run `verify` from cron — tamper evidence nobody looks at is not evidence.

## What the store guarantees

Records hold offsets and metadata, **never** the matched value, the ciphertext or
the key. Each record hashes its own fields plus the previous record's hash, so an
in-place edit or a mid-file deletion is detectable and `verify_chain()` says where.
Appends are atomic per record (`O_APPEND` on POSIX, a `FILE_APPEND_DATA` handle on
Windows, where the C runtime's `O_APPEND` is not atomic between processes).

Read [COMPLIANCE.md](https://github.com/cheneeheng/presidio-compliance-stack/blob/main/COMPLIANCE.md)
for the explicit non-claims — particularly that the chain does not detect tail
truncation and does not prevent anything.

## Dependency note

`presidio-anonymizer` pulls in `cryptography`, and its version bounds have moved
repeatedly (`<44.1` → `>=46.0.4` → `<47` → `<49` inside two months). If you pin
`cryptography` yourself you can hit a resolver conflict that looks like it comes
from this package but does not: it comes from the bounds `presidio-anonymizer`
declares. `presidio-audit` adds no crypto dependency of its own.

Floor is `presidio-anonymizer>=2.2.363`, not lower: Presidio swapped `pycryptodome`
for `cryptography` in 2.2.358, and supporting both sides of that change would mean
claiming support for an Encrypt operator implementation nobody tested here.

Release history: [CHANGELOG.md](https://github.com/cheneeheng/presidio-compliance-stack/blob/main/packages/presidio_audit/CHANGELOG.md).

Apache-2.0 licensed.
