Metadata-Version: 2.4
Name: ciphyrs
Version: 2.5.0
Summary: Python SDK for the Ciphyrs PII Shield API
Project-URL: Homepage, https://www.ciphyrs.com
Project-URL: Documentation, https://www.ciphyrs.com/docs
Project-URL: Repository, https://github.com/praveen190/Ciphyrs
Project-URL: Changelog, https://github.com/praveen190/Ciphyrs/releases
Author-email: Ciphyrs <support@ciphyrs.com>
License-Expression: MIT
Keywords: ciphyrs,masking,pii,privacy
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
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: Topic :: Security
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Provides-Extra: all
Requires-Dist: anthropic>=0.25.0; extra == 'all'
Requires-Dist: crewai>=0.41.0; extra == 'all'
Requires-Dist: haystack-ai>=2.0.0; extra == 'all'
Requires-Dist: langchain-core>=0.2.0; extra == 'all'
Requires-Dist: langgraph>=0.2.0; extra == 'all'
Requires-Dist: litellm>=1.30.0; extra == 'all'
Requires-Dist: llama-index-core>=0.10.0; extra == 'all'
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'all'
Provides-Extra: anthropic
Requires-Dist: anthropic>=0.25.0; extra == 'anthropic'
Provides-Extra: crewai
Requires-Dist: crewai>=0.41.0; extra == 'crewai'
Provides-Extra: haystack
Requires-Dist: haystack-ai>=2.0.0; extra == 'haystack'
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.2.0; extra == 'langchain'
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2.0; extra == 'langgraph'
Provides-Extra: litellm
Requires-Dist: litellm>=1.30.0; extra == 'litellm'
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.10.0; extra == 'llamaindex'
Provides-Extra: opentelemetry
Requires-Dist: opentelemetry-sdk>=1.20.0; extra == 'opentelemetry'
Description-Content-Type: text/markdown

# Ciphyrs — PII Shield for AI Workflows

[![PyPI](https://img.shields.io/pypi/v/ciphyrs)](https://pypi.org/project/ciphyrs/)
[![Python](https://img.shields.io/pypi/pyversions/ciphyrs)](https://pypi.org/project/ciphyrs/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

Intercept, mask, and restore PII before it reaches any LLM. Works with LangChain, CrewAI, or any Python application.

## Install

```bash
pip install ciphyrs                  # core SDK
pip install 'ciphyrs[langchain]'     # + LangChain integration
pip install 'ciphyrs[crewai]'        # + CrewAI integration
pip install 'ciphyrs[all]'           # everything
```

## Quick Start

```python
from ciphyrs import CiphyrsClient

client = CiphyrsClient(
    api_key="cyp_live_...",
    base_url="https://www.ciphyrs.com"
)

# Mask PII
result = client.mask("Contact Praveen at 9876125640 and praveen@acme.com")
print(result.masked_text)
# "Contact XXXX_a1b2c3 at XXXX_d4e5f6 and XXXX_g7h8i9"

# Restore PII
restored = client.restore(result.masked_text, result.session_id)
print(restored.restored_text)
# "Contact Praveen at 9876125640 and praveen@acme.com"
```

## One-Shot Protect (recommended)

`protect()` wraps mask → LLM call → restore so you can't accidentally
ship `[PERSON_1]` to end users:

```python
from openai import OpenAI
oai = OpenAI()

result = client.protect(
    user_message,
    lambda masked, ctx: oai.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": masked}],
    ).choices[0].message.content,
)
return result["output"]    # "Hello John, ..." — never "[PERSON_1]"
```

## Active Blocking — Guard (V58)

Inline `<50ms` allow/block decision, 5 detection layers:

```python
# Option 1 — manual check
guard = client.guard_check(input=user_msg, agent_name="support-bot")
if guard["decision"] == "block":
    return {"error": guard["reason"]}, 400

# Option 2 — full wrap
result = client.guard_wrap(user_msg, lambda inp:
    oai.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": inp}],
    ).choices[0].message.content
)
if result["blocked"]:
    return {"error": result["reason"]}, 400
return {"reply": result["output"]}
```

## Security Operations (V55-V59)

```python
# List recent attacks
detections = client.list_detections(days=7, severity="high")

# Plant a honeypot canary
created = client.create_canary(name="fake-admin-secret", scope="output")
print(created["token"])   # save this; plant it where it shouldn't appear

# Generate compliance evidence PDF
report = client.generate_prod_report(
    title="Q4 SOC 2 Evidence",
    range_start="2026-10-01",
    range_end="2026-12-31",
    sections=["security_incidents", "pii_detections", "performance", "cost"],
)
pdf_bytes = client.download_prod_report_pdf(report["report"]["id"])
open("soc2-evidence.pdf", "wb").write(pdf_bytes)

# Share with auditors (no login required)
share = client.share_prod_report(report["report"]["id"], ttl_days=30)
print(share["share_url_path"])
```

## Authentication

All API calls require an `x-api-key` header. Get your API key from the [Ciphyrs Dashboard](https://www.ciphyrs.com/dashboard).

1. Register at [ciphyrs.com/register](https://www.ciphyrs.com/register)
2. Go to **Settings > API Keys**
3. Click **Generate API Key**
4. Copy the key (starts with `cyp_live_`)

## LangChain Integration

### Option 1: Callback Handler (auto-masks all LLM calls)

```python
from langchain_openai import ChatOpenAI
from ciphyrs.integrations.langchain import CiphyrsPIICallback

callback = CiphyrsPIICallback(api_key="cyp_live_...")
llm = ChatOpenAI(model="gpt-4o", callbacks=[callback])

# PII is automatically masked before reaching OpenAI
# and restored in the response
result = llm.invoke("Hi, I'm Praveen Kumar, my phone is 9876125640")
```

### Option 2: LCEL Runnables (pipe operator)

```python
from ciphyrs.integrations.langchain import CiphyrsMaskRunnable, CiphyrsRestoreRunnable

session = {}
mask = CiphyrsMaskRunnable(api_key="cyp_live_...", session_store=session)
restore = CiphyrsRestoreRunnable(api_key="cyp_live_...", session_store=session)

# Build pipeline: mask -> prompt -> llm -> restore
chain = mask | prompt_template | llm | restore
result = chain.invoke({"input": "Call Praveen at 9876125640"})
```

### Option 3: Shield Wrapper (wrap any chain)

```python
from ciphyrs.integrations.langchain import CiphyrsShield

shield = CiphyrsShield(api_key="cyp_live_...")
safe_chain = shield.wrap(my_existing_chain)
result = safe_chain.invoke({"input": "Praveen's email is praveen@acme.com"})
```

## Async Support

```python
from ciphyrs import AsyncCiphyrsClient

async with AsyncCiphyrsClient(api_key="cyp_live_...") as client:
    result = await client.mask("Contact Praveen at praveen@acme.com")
    restored = await client.restore(result.masked_text, result.session_id)
```

## Detected Entity Types

| Entity | Example |
|--------|---------|
| PERSON | Praveen Kumar |
| EMAIL | praveen@acme.com |
| PHONE | 9876125640 |
| IN_AADHAAR | 2345 6789 0123 |
| IN_PAN | ABCDE1234F |
| CREDIT_CARD | 4111-1111-1111-1111 |
| IP_ADDRESS | 192.168.1.1 |
| DATE_OF_BIRTH | 15/03/1990 |
| LOCATION | Mumbai |
| ORGANIZATION | Acme Corp |
| API_KEY | sk-abc123... |

## Links

- [Website](https://www.ciphyrs.com)
- [Documentation](https://www.ciphyrs.com/docs)
- [Dashboard](https://www.ciphyrs.com/dashboard)
