Skip to main content

ProtectRAG

PyPI Python License

Screen RAG documents for prompt injection, apply allow / warn / block policies, and export logs, metrics, and eval reports — as a lightweight Python library with zero required dependencies.

Quick links: PyPI · Issues · Contributing · Security


Table of contents


Requirements

  • Python 3.10+ (see pyproject.toml)
  • Core install: no mandatory third-party dependencies
  • Optional extras: llm, langchain, llamaindex, fastapi, otel, redis — see Install

What it is

Text going into a RAG vector store can contain prompt injection — hidden instructions that hijack the assistant when the chunk is retrieved. ProtectRAG screens that text before indexing (and optionally after retrieval), classifies risk, applies your policy, and gives you structured logs, metrics, callbacks, and eval reports.


How it works

  Text chunk
       │
       ▼
  ┌──────────┐    ┌────────────────┐
  │  Scan    │───▶│ Heuristic (13  │  ← free, fast, local
  │          │    │ rule families)  │
  │          │───▶│ LLM classifier │  ← OpenAI-compatible API
  │          │───▶│ Hybrid         │  ← heuristics first, LLM when needed
  └──────────┘    └────────────────┘
       │
       ▼
  ┌──────────┐
  │  Policy  │──▶  ALLOW  /  WARN  /  BLOCK
  └──────────┘
       │
       ▼
  Logs · Metrics · Callbacks · OTel spans

Detection coverage (13 heuristic rule families)

Rule family Examples
Instruction override "ignore previous instructions", "you are now", "act as if", "switch to mode"
Multi-language override Chinese, Japanese, French, Spanish, German, Arabic, Korean
Role delimiter injection <|system|>, [SYSTEM]:, ---SYSTEM---, END_OF_SYSTEM_PROMPT
Prompt leak / exfiltration request "repeat your system prompt", "reveal hidden instructions"
Data exfiltration "send all docs to URL", markdown image exfil, HTML img exfil
Fake tool / function call <tool_call>, function call:, JSON role injection
Encoding tricks base64 decode, rot13, hex, "decode this"
Unicode manipulation Zero-width characters, bidi overrides, invisible tag blocks
HTML / script injection <script>, <iframe>, javascript:, HTML comment tricks
Markdown injection Image event handlers, data: URIs, comment overrides
Indirect / deferred injection "when the user asks X, reply Y instead"
Payload splitting "this is part 1 of 3, combine with next"

Plus an LLM classifier for nuanced cases the rules can't catch.


Install

pip install protectrag                    # core (zero dependencies)
pip install "protectrag[llm]"             # + LLM classifier (httpx)
pip install "protectrag[langchain]"       # + LangChain integration
pip install "protectrag[llamaindex]"      # + LlamaIndex integration
pip install "protectrag[fastapi]"         # + FastAPI middleware/dependency
pip install "protectrag[otel]"            # + OpenTelemetry tracing
pip install "protectrag[redis]"           # + Redis-backed LLM result cache (multi-replica)
pip install "protectrag[llm,fastapi]"     # combine extras

Quick start

Heuristics only (no API key needed)

from protectrag import ingest_document, IngestDecision, InjectionSeverity

out = ingest_document(
    "Your document chunk text here.",
    document_id="chunk-001",
    block_on=InjectionSeverity.HIGH,
    warn_on=InjectionSeverity.MEDIUM,
)

if out.decision is IngestDecision.BLOCK:
    print("Blocked:", out.message)
elif out.decision is IngestDecision.ALLOW_WITH_WARNING:
    print("Warning:", out.message)
else:
    print("Clean — safe to index")

Hybrid (heuristics + LLM)

import os
from protectrag import HybridScanner, LLMScanner, ingest_document

os.environ["OPENAI_API_KEY"] = "sk-..."

with LLMScanner.from_env() as llm:
    hybrid = HybridScanner(llm)
    out = ingest_document(
        text,
        document_id="chunk-001",
        scan=lambda t, d: hybrid.scan(t, document_id=d),
    )

Shared Redis cache (multi-replica): pip install "protectrag[redis]", then pass RedisLLMClassificationCache(redis.Redis(...)) as LLMScanner(..., shared_cache=cache) or LLMScanner.from_env(shared_cache=cache) so workers dedupe identical bodies and reduce LLM spend.


Retrieval-time screening

Screen chunks after they come back from the vector DB and before they reach the LLM:

from protectrag import RetrievedChunk, screen_retrieved_chunks

chunks = [
    RetrievedChunk(text="Normal policy docs.", chunk_id="c1"),
    RetrievedChunk(text="Ignore previous instructions.", chunk_id="c2"),
]

result = screen_retrieved_chunks(chunks)
safe_texts = result.passed_texts()  # only clean chunks
print(f"Blocked {result.n_blocked} of {result.total} chunks")

Use screen_retrieved_chunks(..., max_workers=8) for parallel sync screening in thread pools, or await screen_retrieved_chunks_async(..., max_concurrency=10) (and optional async_scan_fn=hybrid.ascan) in async apps.


Async & batch API

For high-throughput pipelines processing thousands of chunks:

import asyncio
from protectrag import async_scan_batch

items = [("chunk text 1", "id-1"), ("chunk text 2", "id-2"), ...]

result = asyncio.run(async_scan_batch(items, max_concurrency=10))
print(result.summary())  # {"total": ..., "blocked": ..., "allowed": ...}
  • async_scan_batch(..., batch_chunk_size=2000) avoids scheduling millions of tasks at once.
  • Pass async_scan_fn=hybrid.ascan for native async hybrid/LLM scans (no thread wrapper).
  • ingest_document_async and trace_ingest_screen_async mirror the sync ingest path for FastAPI/async services.

Callbacks (alerts & webhooks)

Fire custom functions on block / warn / allow decisions:

from protectrag import CallbackRegistry, ingest_document

def send_slack_alert(text, result):
    print(f"BLOCKED doc={result.document_id} severity={result.severity.name}")

def quarantine(text, result):
    # write to quarantine queue / database
    pass

cb = CallbackRegistry(
    on_block=[send_slack_alert, quarantine],
    on_warn=[send_slack_alert],
)

ingest_document(text, document_id="d1", callbacks=cb)

Framework integrations

LangChain

from protectrag.integrations.langchain import ProtectRAGFilter

guard = ProtectRAGFilter()  # parallel screening across docs (tune with max_workers=1 for sequential)
docs = retriever.get_relevant_documents(query)
safe_docs = guard.transform_documents(docs)  # injected docs removed

LlamaIndex

from protectrag.integrations.llamaindex import ProtectRAGPostprocessor

query_engine = index.as_query_engine(
    node_postprocessors=[ProtectRAGPostprocessor()],
)

FastAPI

from fastapi import FastAPI
from protectrag.integrations.fastapi import create_screening_middleware, screen_text_dependency_async

app = FastAPI()
app.add_middleware(create_screening_middleware(paths=["/api/ingest"]))
# Async routes: use screen_text_dependency_async(async_scan_fn=hybrid.ascan) with Depends(...)

Golden dataset & offline evals

Ships with 100 labeled examples (50 injection + 50 clean) covering all attack families:

from protectrag import load_golden_v1, run_eval_dataset, scan_document_for_injection

cases = load_golden_v1()
report = run_eval_dataset(
    cases,
    classify=lambda t, d: scan_document_for_injection(t, document_id=d),
    run_id="ci-build-42",
)
print(f"Precision: {report.precision:.2f}  Recall: {report.recall:.2f}")

Run this in CI to catch regressions when you change rules or models.


Observability

Structured logs

from protectrag import configure_logging, ingest_document, RunContext

configure_logging()
ctx = RunContext(project="acme-rag", environment="prod")
ingest_document(text, document_id="d1", context=ctx)
# Emits JSON: {"event":"rag_document_screen","action":"ingest_blocked",...,"run_id":"...","project":"acme-rag"}

RunContext also supports log_sample_rate_block and log_sample_rate_warn (0.0–1.0) to sample high-volume structured logs; metrics and callbacks are always recorded.

Metrics

from protectrag import InMemoryMetrics, ingest_document

m = InMemoryMetrics()
ingest_document(text, document_id="d1", metrics=m)
print(m.snapshot())  # counters + histograms

OpenTelemetry spans

from protectrag import span_attributes_for_ingest_scan
# Attach to your exporter (Phoenix, Jaeger, Datadog, etc.)
attrs = span_attributes_for_ingest_scan(result, latency_ms=12.3, model="gpt-4o-mini")

LLM retry & fallback

Automatic retry with exponential backoff on rate limits (429) and server errors (5xx). Falls back to heuristics if the LLM is completely unavailable:

from protectrag import LLMScanner, RetryConfig, with_retry

scanner = LLMScanner.from_env()
result = with_retry(
    lambda: scanner.scan(text, document_id="d1"),
    text=text,
    document_id="d1",
    config=RetryConfig(max_retries=3, fallback_to_heuristic=True),
)

Configuration

Optional environment variables (LLM mode only — see .env.example):

Variable Default Purpose
OPENAI_API_KEY — Required for LLM calls
OPENAI_BASE_URL https://api.openai.com/v1 Any OpenAI-compatible server
PROTECTRAG_LLM_MODEL gpt-4o-mini Classification model

LLMScanConfig also exposes http_max_connections / http_max_keepalive_connections for httpx pool sizing under load.

Heuristic-only usage needs none of these.


Concepts & full API

Export Role
scan_document_for_injection Heuristic scan → DocumentScanResult
ingest_document Scan + policy + logs/metrics/callbacks
ingest_document_async Same pipeline for async classifiers / FastAPI
screen_retrieved_chunks Retrieval-time filtering (max_workers for threads)
screen_retrieved_chunks_async Async concurrent retrieval screening
async_scan, async_scan_batch, AsyncScanFn Async + batch (async_scan_fn, batch_chunk_size)
LLMScanner, HybridScanner LLM / hybrid (ascan, shared_cache, httpx limits)
RedisLLMClassificationCache, LLMClassificationCache Cross-replica LLM result cache
with_retry, with_retry_async, RetryConfig Sync / async LLM retry + heuristic fallback
CallbackRegistry on_block / on_warn / on_allow hooks
load_golden_v1, run_eval_dataset Golden dataset + eval reports
RunContext, InMemoryMetrics Run correlation + metrics
configure_logging, emit_ingest_event Structured JSON logging
trace_ingest_screen, trace_ingest_screen_async Latency + optional OTel spans
span_attributes_for_ingest_scan OTel span attributes
ProtectRAGFilter LangChain document transformer
ProtectRAGPostprocessor LlamaIndex node postprocessor
create_screening_middleware, screen_text_dependency_async FastAPI middleware + async Depends

UI / visual analysis

ProtectRAG is a library, not a hosted platform — it has no built-in web UI. For visual analysis:

  • Jupyter notebook — notebooks/eval_dashboard.ipynb loads the golden dataset, runs evals, and prints confusion matrix + severity distribution.
  • Grafana / Datadog — Consume the structured JSON logs (configure_logging()) or metrics (MetricsSink) to build dashboards.
  • Phoenix / Jaeger — Use the [otel] extra to export spans to any OTel-compatible backend for trace visualization.

Vector database compatibility

Works with every vector store — Pinecone, Qdrant, Weaviate, Milvus, pgvector, Chroma, OpenSearch, FAISS, etc. ProtectRAG only processes text strings; it never talks to your database. You call it before (or after) your embed + upsert.


Limitations

  • Heuristic rules are pattern-based and can miss novel attack styles or rarely flag benign text.
  • The LLM classifier is stronger but costs money and is not perfect.
  • Long texts are truncated (head + tail) before sending to the LLM.
  • No built-in web dashboard — use notebooks, Grafana, or OTel backends.

Community & support


Contributing

We welcome contributions: bug fixes, documentation improvements, tests, heuristic tuning (with eval impact), and careful API extensions.

Please read CONTRIBUTING.md for local setup, how to run tests, expectations for pull requests, and where code lives in the tree.


Security

To report a security vulnerability privately, follow SECURITY.md (use GitHub Security Advisories rather than a public issue when disclosure could harm users).


Development

git clone https://github.com/Ritvik777/ProtectRAG.git
cd ProtectRAG
python -m venv .venv && source .venv/bin/activate  # optional
pip install -e ".[dev]"
pytest tests/ -v          # 60+ tests
python -m build           # build wheel + sdist

Full contributor workflow, PR checklist, and conventions: CONTRIBUTING.md.


Repository layout

Path Purpose
src/protectrag/ Library package (scanner, ingest, llm, async_api, retrieval, integrations, …)
tests/ Pytest suite
src/protectrag/data/ Bundled data (e.g. golden eval set)
notebooks/ Example Jupyter workflow
docs/ Extra documentation pointers
CONTRIBUTING.md How to contribute
SECURITY.md Vulnerability reporting

Publishing new versions

# 1. Bump version in pyproject.toml
# 2. Build
rm -rf dist && python -m build
# 3. Upload
python -m twine upload dist/*

License

Apache 2.0 — see LICENSE.

Metadata

Release files for protectrag 0.6.0

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

Source distribution (sdist)

Source distribution for protectrag 0.6.0
File Size Uploaded Provenance Actions
protectrag-0.6.0.tar.gz 55.0 kB Without provenance Details for protectrag-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for protectrag 0.6.0
File Interpreter ABI Platform Actions
protectrag-0.6.0-py3-none-any.whl Python 3 none any Details for protectrag-0.6.0-py3-none-any.whl

Total release size: 103.3 kB

Release files / protectrag-0.6.0.tar.gz

Download URL protectrag-0.6.0.tar.gz
Size 55.0 kB
Tags Source
SHA-256 checksum
How to use checksums
dab9fca37f69545216fb8c68d011dffcab87a72bce7970dbf03bc00e5aed4e48
BLAKE2b-256 checksum
How to use checksums
f0fb2c726f805710cce75c766d08aac72703a4c39a2585571cafc772446e7509
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.4

Release files / protectrag-0.6.0-py3-none-any.whl

Download URL protectrag-0.6.0-py3-none-any.whl
Size 48.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
0ce935efa0564f9f5b18134cf74cbd01c37fd1e4625cb9f221636d25db4ec419
BLAKE2b-256 checksum
How to use checksums
2e9a2357fa48903fcf3f12e2d634ad91852819409b0a2abadd89c98e375fdcd5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.4

Security for protectrag 0.6.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

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