Metadata-Version: 2.2
Name: pyveil
Version: 0.2.5
Summary: Local PII and secret redaction for Python LLM apps, AI agents, tool calls, and MCP
Author-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
Maintainer-email: Hyeon Sang Jeon <wingnut0310@gmail.com>
License: MIT
Project-URL: Homepage, https://hyeonsangjeon.github.io/pyveil/
Project-URL: Documentation, https://hyeonsangjeon.github.io/pyveil/manual.html
Project-URL: Repository, https://github.com/hyeonsangjeon/pyveil
Project-URL: Issues, https://github.com/hyeonsangjeon/pyveil/issues
Project-URL: Changelog, https://github.com/hyeonsangjeon/pyveil/releases
Project-URL: Security, https://github.com/hyeonsangjeon/pyveil/security/policy
Project-URL: Guides, https://hyeonsangjeon.github.io/pyveil/guides/
Project-URL: Evaluation, https://hyeonsangjeon.github.io/pyveil/evaluation.html
Project-URL: Funding, https://github.com/sponsors/hyeonsangjeon
Project-URL: Support, https://github.com/hyeonsangjeon/pyveil/discussions
Keywords: agent-native,agent-security,agents,ai-agents,ai-security,anthropic,data-protection,data-masking,de-identification,guardrails,llm,llm-privacy,llm-security,logs,masking,mcp,mcp-security,memory,observability,pii,pii-redaction,privacy,prompts,prompt-redaction,redaction,runtime-redaction,openai,anonymization,security,sensitive-data,tool-calls,traces
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Text Processing
Classifier: Typing :: Typed
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: azure-openai
Requires-Dist: openai<3.0.0,>=1.66.0; extra == "azure-openai"
Requires-Dist: python-dotenv>=1.0.0; extra == "azure-openai"
Requires-Dist: PyYAML>=6.0.0; extra == "azure-openai"
Provides-Extra: openai
Requires-Dist: openai<3.0.0,>=2.45.0; python_version >= "3.9" and extra == "openai"
Requires-Dist: python-dotenv>=1.0.0; extra == "openai"
Requires-Dist: PyYAML>=6.0.0; extra == "openai"
Provides-Extra: anthropic
Requires-Dist: anthropic<1.0.0,>=0.116.0; python_version >= "3.9" and extra == "anthropic"
Requires-Dist: python-dotenv>=1.0.0; extra == "anthropic"
Requires-Dist: PyYAML>=6.0.0; extra == "anthropic"
Provides-Extra: ollama
Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "ollama"
Requires-Dist: python-dotenv>=1.0.0; extra == "ollama"
Requires-Dist: PyYAML>=6.0.0; extra == "ollama"
Provides-Extra: dev
Requires-Dist: build>=1.2.0; extra == "dev"
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Requires-Dist: black>=23.0.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Requires-Dist: twine>=5.0.0; extra == "dev"
Requires-Dist: mypy>=1.0.0; extra == "dev"
Requires-Dist: ollama<1.0.0,>=0.6.2; extra == "dev"
Requires-Dist: openai<3.0.0,>=2.45.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: anthropic<1.0.0,>=0.116.0; python_version >= "3.9" and extra == "dev"
Requires-Dist: python-dotenv>=1.0.0; extra == "dev"
Requires-Dist: PyYAML>=6.0.0; extra == "dev"
Requires-Dist: types-PyYAML>=6.0.0; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest>=7.0.0; extra == "test"
Requires-Dist: pytest-cov>=4.0.0; extra == "test"
Requires-Dist: python-dotenv>=1.0.0; extra == "test"
Requires-Dist: PyYAML>=6.0.0; extra == "test"

# pyveil: PII and secret redaction for Python AI agents

<p align="center">
  <strong>Stop sensitive data before it reaches an LLM, tool, MCP resource, memory store, log, or trace.</strong>
</p>

<p align="center">
  <a href="https://pypi.org/project/pyveil/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/pyveil?style=flat-square"></a>
  <a href="https://github.com/hyeonsangjeon/pyveil/actions/workflows/tests.yml"><img alt="Tests" src="https://github.com/hyeonsangjeon/pyveil/actions/workflows/tests.yml/badge.svg"></a>
  <a href="https://www.python.org/"><img alt="Python 3.8 to 3.14" src="https://img.shields.io/badge/python-3.8%20to%203.14-3776AB?style=flat-square"></a>
  <img alt="Zero core dependencies" src="https://img.shields.io/badge/core%20dependencies-zero-111827?style=flat-square">
  <img alt="Typed package" src="https://img.shields.io/badge/typed-py.typed-7C3AED?style=flat-square">
  <a href="https://hyeonsangjeon.github.io/pyveil/evaluation.html"><img alt="Synthetic evaluation: 39 cases passing" src="https://img.shields.io/badge/synthetic%20evaluation-39%20cases%20passing-4ade80?style=flat-square"></a>
  <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/license-MIT-green?style=flat-square"></a>
</p>

<p align="center">
  <a href="https://hyeonsangjeon.github.io/pyveil/manual.html">Documentation</a> &middot;
  <a href="https://hyeonsangjeon.github.io/pyveil/guides/">Guides</a> &middot;
  <a href="https://hyeonsangjeon.github.io/pyveil/evaluation.html">Evaluation</a> &middot;
  <a href="https://pypi.org/project/pyveil/">PyPI</a> &middot;
  <a href="https://github.com/hyeonsangjeon/pyveil/blob/main/docs/cookbook.md">Cookbook</a> &middot;
  <a href="https://github.com/hyeonsangjeon/pyveil/blob/main/docs/redaction-reference.md">Detection reference</a> &middot;
  <a href="https://github.com/hyeonsangjeon/pyveil/discussions">Support</a> &middot;
  <a href="https://github.com/hyeonsangjeon/pyveil/security">Security</a>
</p>

`pyveil` is local, dependency-free redaction middleware for LLM applications and
AI agents. It replaces high-confidence PII and credentials with deterministic,
scoped HMAC placeholders before data crosses an application boundary.

| Raw application context | Context sent to the model |
| --- | --- |
| `Email alice@example.com` | `Email [EMAIL:a13f7c91b0d2]` |
| `api_key: sk-proj-...` | `api_key: [API_KEY:38ded98a17e7]` |
| `Authorization: Bearer ...` | `[AUTH_HEADER:4fe2926b7d20]` |

No network calls. No reversible vault. No raw values in findings by default.

## Try It

```bash
pip install pyveil
pyveil demo
# or: python -m pyveil demo
```

Or run the synthetic demo in an isolated environment:

```bash
uvx pyveil demo
```

```text
before: Email alice@example.com, call 010-1234-5678, and use API key sk-proj-...
after:  Email [EMAIL:...], call [PHONE:...], and use API key [API_KEY:...]
found:  API_KEY, EMAIL, PHONE
```

## Start With Your Integration

Choose the boundary you need and begin with a runnable example. The keyless
paths use synthetic input and stop before a provider request.

| You use | Install | Start here | Protected boundary |
| --- | --- | --- | --- |
| OpenAI Agents SDK | `pip install pyveil openai-agents` | [Pre-dispatch Runner wrapper](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/openai_agents_guardrail.py) | Input is redacted before `Runner.run`; Python 3.10+ for the current SDK |
| LiteLLM SDK or Proxy | `pip install pyveil litellm` or `pip install pyveil "litellm[proxy]"` | [SDK wrapper and Proxy pre-call hook](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/litellm_proxy_filter.py) | Messages before `completion(...)` or proxy provider dispatch; Python 3.10+ for current LiteLLM |
| OpenAI Responses API | `pip install "pyveil[openai]"` | [Keyless contract guide](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/openai.md) | Exact input before `client.responses.create(...)` |
| Anthropic / Claude | `pip install "pyveil[anthropic]"` | [Keyless contract guide](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/anthropic.md) | Exact content before `client.messages.create(...)` |
| Azure OpenAI | `pip install "pyveil[azure-openai]"` | [Environment and YAML example](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/azure_openai.py) | Prompt before the Azure Responses API request |
| Ollama | `pip install "pyveil[ollama]"` | [Local end-to-end guide](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/ollama.md) | Prompt before the local model call |
| MCP | `pip install pyveil` | [Server wrapper](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/mcp_server_wrapper.py) | Tool results and resource content before agent context |
| FastAPI, logs, or memory | `pip install pyveil` | [Cookbook](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/cookbook.md) | Request payloads, log records, and memory writes |

Before production use, confirm the supported [detection shapes](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/redaction-reference.md)
and read the [security contract](https://github.com/hyeonsangjeon/pyveil/blob/main/SECURITY.md). The examples demonstrate where
to place the boundary; they do not claim perfect PII recall or compliance.

### OpenAI Agents And LiteLLM, Side By Side

| | OpenAI Agents SDK | LiteLLM Python SDK | LiteLLM Proxy |
| --- | --- | --- | --- |
| Detection point | Before `Runner.run` | Before `litellm.completion` | `async_pre_call_hook` before provider dispatch |
| Input | String or structured agent input | Message list | `data["messages"]` when list-valued |
| Safe output | Same shape passed to `Runner.run` | Redacted message list passed to `completion(...)` | Payload copy with redacted `messages` |
| Bypass risk | Direct `Runner.run` calls | Direct `completion(...)` calls | Non-message payloads and fields |
| Example limit | Does not cover later tools, memory, logs, or traces | Does not cover other LiteLLM APIs | Only completion-style `messages` are handled |

See the [full comparison](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/openai-agents-vs-litellm.md)
for install commands, failure behavior, expected output, detector scope, and
security limitations.

## Protect An LLM Call

Put `pyveil` immediately before the provider call. The same code works with
OpenAI, Azure OpenAI, Anthropic, Gemini, LiteLLM, or an internal gateway.

```python
from pyveil import Channel, Veil

veil = Veil.high(
    secret=b"tenant-or-run-secret",
    scope="tenant/session",
)

messages = [
    {"role": "user", "content": "Email alice@example.com about my account."},
]

safe = veil.redact_data(messages, channel=Channel.PROMPT_INPUT)
response = call_llm(safe.data)  # Your provider SDK call
```

The provider receives the same list and dictionary shape, with sensitive values
replaced before serialization or transmission.

## OpenAI And Claude: Keyless Contract-Tested Templates

Install provider-specific templates without adding either SDK to pyveil's
zero-dependency core:

```bash
pip install "pyveil[openai]"     # OpenAI Responses API
pip install "pyveil[anthropic]"  # Claude Messages API
```

Both integrations redact locally at the final SDK boundary and return the exact
provider input for inspection:

```python
from pyveil.integrations.openai import ask_openai, load_settings

settings = load_settings()
result = ask_openai(
    "Write a follow-up for alice@example.com or 010-1234-5678.",
    settings,
)

print(result.redacted_input)  # exact client.responses.create(...) input
print(result.output_text)
```

```python
from pyveil.integrations.anthropic import ask_anthropic, load_settings

settings = load_settings()
result = ask_anthropic(
    "Write a follow-up for alice@example.com or 010-1234-5678.",
    settings,
)

print(result.redacted_input)  # exact client.messages.create(...) content
print(result.output_text)
```

No API key is needed to prove either boundary:

```bash
PYVEIL_SECRET=docs-demo-secret OPENAI_MODEL=gpt-5.6-luna \
  python -m pyveil.integrations.openai --dry-run

PYVEIL_SECRET=docs-demo-secret ANTHROPIC_MODEL=claude-haiku-4-5 \
  python -m pyveil.integrations.anthropic --dry-run
```

```text
sent-to-openai:    ... [EMAIL:17c25f8a4fe3] ... [PHONE:3f6dc5a3c9f3].
sent-to-anthropic: ... [EMAIL:0b77abd1b26b] ... [PHONE:ec56e2456ba2].
provider-response: skipped (--dry-run)
```

The repository also exercises the real official SDKs through local mock HTTP
transports and asserts against the serialized `/v1/responses` and `/v1/messages`
JSON bodies. These tests use no credentials, make no network requests, and
cannot incur provider spend. A live paid API call has **not** been claimed.
Historical provider models are not a free fallback and may be retired; keep the
model ID configurable and use dry-run or mock contracts for cost-free checks.

Current OpenAI and Anthropic SDKs require Python 3.9+. The pyveil core and both
keyless dry-run paths remain compatible with Python 3.8 through 3.14. Use the
checked-in [OpenAI guide](docs/integrations/openai.md) and
[Anthropic / Claude guide](docs/integrations/anthropic.md) for configuration,
offline verification, and boundary notes.

## Ollama: Local End To End

Run a local model behind the same redaction boundary. The optional integration
uses Ollama's official Python client and defaults to `qwen3.5:4b`, a Q4_K_M
4.7B model that fits comfortably on a 16GB Apple silicon Mac with a 4K context:

```bash
pip install "pyveil[ollama]"
ollama pull qwen3.5:4b
```

```python
from pyveil.integrations.ollama import ask_ollama, load_settings

settings = load_settings()  # OLLAMA_* + PYVEIL_* environment variables
result = ask_ollama(
    "Write a follow-up for alice@example.com or 010-1234-5678.",
    settings,
)

print(result.redacted_input)  # The exact prompt sent to Ollama
print(result.output_text)     # The local model response
```

Prove the boundary without loading a model:

```bash
PYVEIL_SECRET=docs-demo-secret \
  python -m pyveil.integrations.ollama --dry-run
```

```text
mode: dry-run
model: qwen3.5:4b
host: http://127.0.0.1:11434
sent-to-ollama: Write a one-sentence support follow-up for [EMAIL:71c6727a7fa2] or [PHONE:b4b889df07ce].
findings: EMAIL=1, PHONE=1
ollama-response: skipped (--dry-run)
```

For a live local call, set `PYVEIL_SECRET` and run the module. Configuration
priority is process environment, `.env`, YAML, then defaults:

```bash
PYVEIL_SECRET=a-long-random-hmac-secret \
  python -m pyveil.integrations.ollama

python -m pyveil.integrations.ollama \
  --config examples/ollama.example.yaml --env-file .env
```

The checked-in [`.env` template](examples/ollama.env.example) and
[YAML template](examples/ollama.example.yaml) expose model, host, context,
output length, temperature, timeout, and keep-alive. pyveil caps the default
context at 4096 and uses `keep_alive=0`, so one-shot calls release model memory;
set `OLLAMA_KEEP_ALIVE=5m` for faster repeated calls.

Observed on this project's M1 Mac mini with 16GB memory and Ollama 0.31.2:
`qwen3.5:4b` used about 3.2GB at a 4096-token context, a cold request took
8.1 seconds, and a warm request took 1.3 seconds. These are local measurements,
not portable performance guarantees. See the
[Ollama integration guide](docs/integrations/ollama.md) for the full setup and
memory trade-offs.

## Azure OpenAI: End To End

Install the optional Azure example dependencies, then load configuration from
environment variables, `.env`, or YAML:

```bash
pip install "pyveil[azure-openai]"
```

```python
from pyveil.integrations.azure_openai import ask_azure_openai, load_settings

settings = load_settings()  # AZURE_OPENAI_* + PYVEIL_* environment variables
result = ask_azure_openai(
    "Write a follow-up for alice@example.com or 010-1234-5678.",
    settings,
)

print(result.redacted_input)  # The exact text sent to Azure OpenAI
print(result.output_text)     # The model response
```

The integration uses Azure OpenAI's v1 endpoint and Responses API. The
deployment name is passed as `model`; pyveil redacts the prompt before
`client.responses.create(...)` runs.

Prove the boundary without an Azure request:

```bash
PYVEIL_SECRET=docs-demo-secret \
  python -m pyveil.integrations.azure_openai --dry-run
```

```text
mode: dry-run
deployment: not configured
sent-to-azure: Write a one-sentence support follow-up for [EMAIL:347ab11285a3] or [PHONE:548017338f6f].
findings: EMAIL=1, PHONE=1
azure-response: skipped (--dry-run)
```

For a live call, either export `AZURE_OPENAI_ENDPOINT`,
`AZURE_OPENAI_DEPLOYMENT`, `AZURE_OPENAI_API_KEY`, `PYVEIL_SECRET`, and
optionally `PYVEIL_SCOPE`, or use the checked-in
[`.env` template](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/azure_openai.env.example)
and [YAML template](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/azure_openai.example.yaml):

```bash
python -m pyveil.integrations.azure_openai --env-file .env
python -m pyveil.integrations.azure_openai \
  --config examples/azure_openai.example.yaml --env-file .env
```

Process environment variables override `.env`, which overrides non-secret YAML
settings. API keys and the pyveil HMAC secret are rejected if placed directly
in YAML; YAML names the environment variables that contain them.

<p align="center">
  <img src="https://raw.githubusercontent.com/hyeonsangjeon/pyveil/main/docs/media/pyveil-awesome-demo.gif"
       alt="pyveil redacts synthetic PII and secrets before an AI agent boundary"
       width="820">
</p>

## Why pyveil

| Need | What pyveil provides |
| --- | --- |
| Keep data local | Standard-library core, zero required dependencies, zero network calls |
| Preserve references | Stable `[TYPE:12hex]` placeholders from HMAC-SHA256 |
| Isolate tenants and runs | Caller-defined `scope` changes placeholders across boundaries |
| Redact real agent payloads | Recursive dictionaries, lists, tuples, and JSON strings |
| Cover more than prompts | Policy channels for tools, MCP, memory, logs, traces, input, and output |
| Stop credentials in tool calls | Auth headers, private keys, API keys, JWTs, and tokens block by default |
| Cover app-specific data | Exact known-value and trusted custom-regex rules |
| Audit without leaking | Findings contain type, rule, path, placeholder, and fingerprint, not raw values |
| Verify supported behavior | Public 39-case synthetic regression corpus, evaluator, and CI gate |

## Reproducible Evidence

The repository ships a public synthetic detector corpus and a standard-library
evaluator:

```bash
python evaluation/evaluate.py --check
```

For corpus v1, pyveil 0.2.5 matches all 36 expected findings across 39 cases
(33 positive, 6 negative), with no corpus false positives, false negatives,
labeled-value leaks, or non-empty `Finding.raw` values.

The repository also ships a machine-readable
[protection-surface contract](compatibility/README.md) with synthetic fixtures
for every channel, a validator that fails on manifest or documentation drift,
and a privacy-safe evidence receipt:

```bash
python scripts/validate_compatibility.py --check   # manifest, fixtures, and docs agree
python -m pytest tests/test_compatibility_surfaces.py
python scripts/verify_zero_dependencies.py         # 0 runtime dependencies
```

These numbers describe documented supported shapes only. They are **not** a
real-world PII recall benchmark and do not cover unknown names, addresses,
languages, documents, or images. Read the
[methodology and limits](https://hyeonsangjeon.github.io/pyveil/evaluation.html).

## Known Names And Domain IDs

Regex cannot discover arbitrary names or addresses. When your application
already knows a value is sensitive, teach that value to `pyveil` without adding
an NER model:

```python
from pyveil import CustomRule, Veil

rules = [
    CustomRule.exact("PERSON", ["Alice Kim", "Hong Gildong"]),
    CustomRule("CUSTOMER_ID", r"\bCUS-[A-Z0-9]{8}\b", rule_id="customer_id"),
]

veil = Veil.high(
    secret=b"tenant-secret",
    scope="tenant/session",
    rules=rules,
)

result = veil.redact_text("Alice Kim owns CUS-A1B2C3D4.")
print(result.text)
# [PERSON:...] owns [CUSTOMER_ID:...].
```

Custom patterns are trusted application code. Keep them narrow and test them
against realistic positive and negative samples.

## Agent Boundaries

Classic masking often stops at text input. Agents move data across several
surfaces, so channels are first-class policy inputs:

| Channel | Redact before |
| --- | --- |
| `prompt.input` | User, RAG, or application context reaches a model |
| `prompt.output` | Model output is displayed or chained |
| `tool.call.arguments` | A model-controlled tool executes |
| `tool.call.result` | Tool output returns to a model |
| `mcp.resource.content` | MCP resource content enters context |
| `memory.write` | Text is embedded or persisted |
| `trace.span.attributes` | Attributes leave through telemetry |
| `log.record` | Records reach handlers or external sinks |

```text
user / retrieval / tool / resource data
                    |
                  pyveil
                    |
model / tool / MCP / memory / trace / log boundary
```

Every channel above is backed by a synthetic fixture and a reproducible command
in the [compatibility contract](compatibility/README.md), and summarized in a
privacy-safe [Proof-of-Compatibility Receipt](compatibility/receipt.md) that
records redaction counts and output hashes, never raw values.

## Detection Board

Core detection is intentionally conservative and high precision:

| Type | Examples | HIGH output |
| --- | --- | --- |
| `EMAIL` | Email addresses | `[EMAIL:12hex]` |
| `PHONE` | Korean, separated international, and compact E.164 phone shapes | `[PHONE:12hex]` |
| `CREDIT_CARD` | Card numbers that pass Luhn validation | `[CREDIT_CARD:12hex]` |
| `JWT` | Compact JSON Web Tokens | `[JWT:12hex]` |
| `AUTH_HEADER` | Bearer and Basic authorization headers | `[AUTH_HEADER:12hex]` |
| `PRIVATE_KEY` | PEM private-key blocks | `[PRIVATE_KEY:12hex]` |
| `API_KEY` | OpenAI, GitHub, Slack, Google, and AWS-style keys | `[API_KEY:12hex]` |
| `URL_QUERY_SECRET` | Token, key, secret, code, and auth query values | `[URL_QUERY_SECRET:12hex]` |
| `KV_SECRET` | Password, cookie, secret, and token key-value pairs | `[KV_SECRET:12hex]` |
| Custom | Known values and application regex rules | `[YOUR_TYPE:12hex]` |

See the [full redaction reference](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/redaction-reference.md)
for examples, validation rules, LOW masking output, and limitations.

## HIGH And LOW

Use `HIGH` at model, agent, tool, MCP, memory, trace, and external log
boundaries. It produces stable HMAC placeholders such as
`[EMAIL:a13f7c91b0d2]`.

Use `LOW` only for human-facing previews where preserving shape is useful:

```text
alice@example.com  -> al***@e******.com
010-1234-5678      -> 010-****-5678
4242 4242 4242 4242 -> **** **** **** 4242
```

Credential-like values remain aggressively hidden in both levels.

## Policy

The default high policy redacts supported findings and blocks credentials in
`tool.call.arguments`:

```python
from pyveil import Action, Channel, Entity, Policy, Veil

policy = Policy.default_high().override(
    Channel.PROMPT_INPUT,
    Entity.EMAIL,
    Action.PASS,
)

veil = Veil.high(secret=b"tenant-secret", policy=policy)
```

When both `policy` and `level` are supplied, the explicit policy decides channel
levels and actions. Build one `Veil` per tenant, session, or run and reuse it.

## CLI

Use stdin for shell pipelines, files for preflight checks, and JSON output for
structured automation:

```bash
export PYVEIL_SECRET="tenant-or-run-secret"
export PYVEIL_SCOPE="tenant/session"

printf 'Email alice@example.com' | pyveil redact -
pyveil redact request.json --channel tool.call.result --format json
pyveil scan prompt.txt --format json
pyveil init
pyveil test-config pyveil.yaml
```

`scan` emits finding metadata without raw sensitive values. JSON-shaped input
is parsed and traversed structurally.

## Integration Recipes

| Stack or boundary | Copy-paste example |
| --- | --- |
| Any LLM provider | [Provider-neutral client wrapper](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/llm_client_boundary.py) |
| OpenAI Agents SDK | [Pre-dispatch `Runner.run` wrapper](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/openai_agents_guardrail.py) |
| OpenAI Responses API | [Installable integration](https://github.com/hyeonsangjeon/pyveil/blob/main/pyveil/integrations/openai.py) and [keyless contract guide](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/openai.md) |
| Anthropic / Claude | [Installable integration](https://github.com/hyeonsangjeon/pyveil/blob/main/pyveil/integrations/anthropic.py) and [keyless contract guide](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/integrations/anthropic.md) |
| Azure OpenAI | [Runnable env/YAML integration](https://github.com/hyeonsangjeon/pyveil/blob/main/pyveil/integrations/azure_openai.py) and [short example](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/azure_openai.py) |
| LiteLLM | [SDK wrapper and Proxy pre-call hook](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/litellm_proxy_filter.py) |
| FastAPI | [Request middleware example](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/fastapi_middleware.py) |
| MCP | [Server wrapper](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/mcp_server_wrapper.py) and [integration guide](https://hyeonsangjeon.github.io/pyveil/manual.html#integrations) |
| Python logging | [Logging filter example](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/log_filter.py) |
| Agent memory | [Before-embedding example](https://github.com/hyeonsangjeon/pyveil/blob/main/examples/memory_write.py) |

The [cookbook](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/cookbook.md)
covers prompts, tools, MCP, memory, logging, tracing, JSON, and CLI workflows.

## Pick The Right Tool

Choose `pyveil` when you want a small local boundary filter for structured PII,
credentials, known values, and domain identifiers across agent context flows.

Choose Presidio, GLiNER, or another NER-backed system when you need broad
semantic discovery of unknown people, organizations, locations, or addresses.
Choose an enterprise DLP product when you need managed policy, document/image
coverage, incident workflows, or compliance reporting.

These tools can be layered. `pyveil` does not claim perfect recall.
See the full [pyveil vs Presidio, NER, guardrails, and DLP decision guide](https://hyeonsangjeon.github.io/pyveil/guides/pyveil-vs-presidio.html).

## Safety Contract

- Raw sensitive values are not stored in `Finding` objects by default.
- Placeholders use HMAC-SHA256 with a caller-provided secret and scope.
- Credential-like values can be blocked before model-controlled tools execute.
- `max_input_chars` bounds work performed on text and structured payloads.
- The core makes no network calls and has no required third-party dependency.
- pyveil has no reversible vault or unmasking API.

`pyveil` is not a compliance guarantee, enterprise DLP system, secret-scanning
replacement, or prompt-injection firewall. Read the
[threat model](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/threat-model.md),
[known limitations](https://github.com/hyeonsangjeon/pyveil/blob/main/docs/known-limitations.md),
and [security policy](https://github.com/hyeonsangjeon/pyveil/blob/main/SECURITY.md)
before production use.

## Guides

- [Complete manual](https://hyeonsangjeon.github.io/pyveil/manual.html)
- [Python LLM PII redaction guide](https://hyeonsangjeon.github.io/pyveil/guides/python-llm-pii-redaction.html)
- [MCP PII redaction guide](https://hyeonsangjeon.github.io/pyveil/guides/mcp-pii-redaction.html)
- [pyveil vs Presidio / NER / DLP](https://hyeonsangjeon.github.io/pyveil/guides/pyveil-vs-presidio.html)
- [Reproducible detector evaluation](https://hyeonsangjeon.github.io/pyveil/evaluation.html)
- [English video guide](https://github.com/hyeonsangjeon/pyveil/releases/download/v0.1.2/pyveil-usage-guide-en.mp4)
- [Korean video guide](https://github.com/hyeonsangjeon/pyveil/releases/download/v0.1.2/pyveil-usage-guide-ko.mp4)
- [AGENTS.md](https://github.com/hyeonsangjeon/pyveil/blob/main/AGENTS.md) for coding agents
- [llms.txt](https://hyeonsangjeon.github.io/pyveil/llms.txt) for LLM-readable navigation
- [Contributing](https://github.com/hyeonsangjeon/pyveil/blob/main/CONTRIBUTING.md)

## Development

```bash
uv run --extra dev ruff check .
uv run --extra dev mypy pyveil tests
uv run --extra test pytest
uv run --extra test python evaluation/evaluate.py --check
```

CI runs the test suite on Python `3.8` through `3.14`. The core remains typed,
dependency-free, and MIT licensed.
