Policy-as-code guardrail enforcement for enterprise LLM applications
Project description
Rampart
Open Policy Agent for LLM Applications.
Rampart sits between your application and any LLM provider — Bedrock, Snowflake Cortex, Azure OpenAI — and enforces configurable safety policies on every request and every response. One pip install. One YAML file. Policy owned by your governance team, not your developers.
Your App → [Rampart: Input Guards] → LLM Provider → [Rampart: Output Guards] → Your App
Why Rampart?
After dozens of enterprise GenAI engagements, the same problem appears every time: every team implements guardrails differently. One team uses LangChain callbacks. Another writes custom middleware. A third skips it entirely. No two projects handle PII the same way. No audit trail. No central policy.
Rampart solves this the same way Open Policy Agent solved it for infrastructure: separate the policy from the code that enforces it.
Governance teams write policy. Developers call one method. Rampart handles everything in between.
from rampart import Rampart
client = Rampart(
policy_registry="file://./policies/banking.yaml",
provider="bedrock",
app_id="customer-chatbot"
)
response = client.invoke(
model_id="anthropic.claude-sonnet-4-6",
messages=[{"role": "user", "content": user_input}],
profile="customer_support"
)
That is the entire integration. The policy file does the rest.
What Rampart Does
Input guards — before the LLM sees the message
- PII detection — detect and block or redact credit cards, Aadhaar, PAN, phone numbers, emails, and more. Powered by Microsoft Presidio. No LLM required.
- Prompt injection detection — catch attempts to override system prompts or hijack model behaviour. Powered by LLM Guard classifiers. No LLM required.
- (More built-in guards coming in v0.2)
Output guards — before your application sees the response
- PII leakage detection — catch PII the LLM may have surfaced in its response
- (More built-in guards coming in v0.2)
Custom guards — bring your own
Any guard your use case needs that is not built in. Implement one interface. Reference it in your policy YAML. Done.
from rampart import BaseGuard, GuardResult, Action
class CompetitorMentionGuard(BaseGuard):
def scan(self, text: str, context: dict) -> GuardResult:
competitors = self.config.get("competitors", [])
found = [c for c in competitors if c.lower() in text.lower()]
if found:
return GuardResult(
passed=False,
action=Action.WARN,
detail=f"Competitor names detected: {found}"
)
return GuardResult(passed=True, action=Action.ALLOW, detail="Clean")
# Reference it in your policy — no code changes to Rampart required
- guard: CompetitorMentionGuard
module: myapp.guards.competitor
action: warn
config:
competitors: [RivalBank, CompetitorApp]
Policy as Code
Every guard behaviour is declared in a versioned YAML policy file. No guard logic lives in application code.
# policies/banking.yaml
version: "1.0.0"
description: "Banking application guardrail policy"
profiles:
customer_support:
input:
- guard: PiiGuard
module: rampart.guards.pii
engine: classifier
action: block
config:
entities: [CREDIT_CARD, AADHAAR, PAN, PHONE_NUMBER, EMAIL_ADDRESS]
- guard: PromptInjectionGuard
module: rampart.guards.prompt_injection
engine: classifier
action: block
config:
threshold: 0.8
output:
- guard: PiiGuard
module: rampart.guards.pii
engine: classifier
action: redact
config:
entities: [CREDIT_CARD, AADHAAR, PAN]
internal_analyst:
input:
- guard: PromptInjectionGuard
module: rampart.guards.prompt_injection
engine: classifier
action: block
config:
threshold: 0.9
# No PiiGuard — analysts work with real customer data
kyc_onboarding:
input:
- guard: PiiGuard
module: rampart.guards.pii
engine: classifier
action: block
config:
entities: [CREDIT_CARD]
# PAN and AADHAAR not listed — required for KYC, permitted
The developer selects a profile per call:
# Same client. Same policy file. Three completely different behaviours.
client.invoke(..., profile="customer_support")
client.invoke(..., profile="internal_analyst")
client.invoke(..., profile="kyc_onboarding")
When your governance team updates the policy YAML, all running applications pick up the change automatically — no application deployments needed.
Guard Actions
Every guard declares one of four actions. The action is in the policy YAML, not the application code.
| Action | What happens |
|---|---|
block |
Request rejected. PolicyViolationError raised. Caller receives an error. |
redact |
Offending content masked. Request continues with cleaned text. |
warn |
Issue logged in audit trail. Request continues unchanged. |
allow |
Guard runs, findings recorded, no action taken. Use when you want visibility without enforcement. |
Hybrid Engine Mode
Every guard supports three execution modes. Set it per guard in the policy YAML.
- guard: PromptInjectionGuard
engine: classifier # local ML model only — fast, free, no external calls
- guard: PromptInjectionGuard
engine: llm # LLM judge only — higher accuracy, adds latency
- guard: PromptInjectionGuard
engine: hybrid # classifier first, LLM only for uncertain cases
config:
threshold: 0.8
uncertainty_band: [0.4, 0.8]
llm:
provider: bedrock
model_id: anthropic.claude-haiku-4-5-20251001
Hybrid mode runs the local classifier first. If the confidence score is clearly high or clearly low, it decides immediately. Only ambiguous cases go to the LLM judge. This keeps average latency low while maintaining accuracy on edge cases.
Policy Registry
Rampart loads policy from wherever you store it. The URI scheme selects the registry automatically.
# Local file — for development and single-server deployments
Rampart(policy_registry="file://./policies/banking.yaml")
# HTTP/S URL — for multi-server deployments
# All servers share one policy. Updates propagate automatically via polling.
Rampart(policy_registry="https://policies.internal.yourbank.com/banking.yaml")
# Git registry — coming in v0.2
# Full audit trail of who changed what and when, via git history
Rampart(policy_registry="git+https://github.com/yourorg/rampart-policies.git")
For HTTP registries, Rampart polls the URL every 5 minutes by default and reloads the policy if it has changed — without restarting the application.
Rampart(
policy_registry="https://policies.internal.yourbank.com/banking.yaml",
reload_interval=300 # seconds, set to 0 to disable polling
)
Audit Log
Every request and response is logged with full policy evaluation detail. This is not optional — it is core.
{
"request_id": "3f7a2b1c-...",
"timestamp": "2026-06-03T10:24:51Z",
"app_id": "customer-chatbot",
"policy_version": "1.0.0",
"profile": "customer_support",
"provider": "bedrock",
"model_id": "anthropic.claude-sonnet-4-6",
"direction": "input",
"guard_results": [
{
"guard": "PiiGuard",
"engine": "classifier",
"passed": false,
"action": "block",
"confidence": 0.97,
"detail": "PII detected: [CREDIT_CARD, AADHAAR]",
"latency_ms": 14
}
],
"final_decision": "blocked",
"total_latency_ms": 14
}
Audit logs go to stdout by default, ready to pipe into CloudWatch, Splunk, or any SIEM.
Installation
# Core package
pip install rampart-llm
# With AWS Bedrock support
pip install rampart-llm[bedrock]
# With Snowflake Cortex support
pip install rampart-llm[cortex]
# With both
pip install rampart-llm[bedrock,cortex]
Note on first run: Rampart's built-in guards use local ML models (Microsoft Presidio and LLM Guard). These models — approximately 300MB total — are downloaded automatically on first use and cached locally. No data is sent to any external service during this download or during scanning.
Requires Python 3.10+
Error Handling
from rampart import Rampart
from rampart.exceptions import PolicyViolationError, RampartError
client = Rampart(
policy_registry="file://./policies/banking.yaml",
provider="bedrock",
app_id="my-app"
)
try:
response = client.invoke(
model_id="anthropic.claude-sonnet-4-6",
messages=[{"role": "user", "content": user_input}],
profile="customer_support"
)
print(response.text) # the LLM response
print(response.request_id) # UUID — correlate with audit log
print(response.warnings) # list of WARN-level findings
except PolicyViolationError as e:
# A guard with action: block fired
print(e.request_id) # for audit log correlation
print(e.direction) # "input" or "output"
print(e.violations) # list of GuardResult objects
except RampartError as e:
# Policy load failure, provider error, registry unreachable
print(e)
Architecture
Your Application
│
│ client.invoke(messages, profile="customer_support")
▼
┌───────────────────────────────────────┐
│ Rampart │
│ │
│ Registry ──▶ PolicyLoader │
│ │ │
│ ┌─────▼──────┐ │
│ │ Profile │ │
│ └─────┬──────┘ │
│ │ │
│ ┌──────────▼──────────┐ │
│ │ Input Pipeline │ │
│ │ PiiGuard │ │
│ │ PromptInjection │ │
│ │ [custom guards] │ │
│ └──────────┬──────────┘ │
│ │ clean text │
└────────────────────┼──────────────────┘
│
┌────────────▼────────────┐
│ LLM Provider │
│ Bedrock / Cortex / │
│ Azure OpenAI │
└────────────┬────────────┘
│
┌────────────────────┼──────────────────┐
│ Rampart │
│ │ LLM response │
│ ┌──────────▼──────────┐ │
│ │ Output Pipeline │ │
│ │ PiiGuard │ │
│ │ [custom guards] │ │
│ └──────────┬──────────┘ │
│ │ │
│ Audit Log (structured JSON) │
└────────────────────┼──────────────────┘
│
RampartResponse to your application
Writing a Custom Guard
- Subclass
BaseGuard - Implement
scan(text, context) -> GuardResult - Reference it in your policy YAML by module path
# myapp/guards/internal_topics.py
from rampart import BaseGuard, GuardResult, Action
class InternalTopicGuard(BaseGuard):
"""
Blocks questions about internal systems, unreleased products,
or confidential projects by keyword matching.
"""
def scan(self, text: str, context: dict) -> GuardResult:
blocked_topics = self.config.get("topics", [])
text_lower = text.lower()
found = [t for t in blocked_topics if t.lower() in text_lower]
if found:
return GuardResult(
passed=False,
action=Action(self.config.get("action", "block")),
detail=f"Internal topic detected: {found}"
)
return GuardResult(
passed=True,
action=Action.ALLOW,
detail="No restricted topics detected"
)
# In your policy YAML
- guard: InternalTopicGuard
module: myapp.guards.internal_topics
engine: classifier
action: block
config:
topics:
- Project Phoenix
- Operation Delta
- Q4 restructure
The context dict available inside scan() contains:
{
"user_id": "hashed-user-id",
"session_id": "uuid",
"app_id": "customer-chatbot",
"policy_version": "1.0.0",
"profile": "customer_support",
"provider": "bedrock",
"model_id": "anthropic.claude-sonnet-4-6",
"direction": "input", # or "output"
"timestamp": "2026-06-03T10:24:51Z"
}
Use context to write guards that behave differently based on who is asking, which application is calling, or whether you are on the input or output side.
Supported Providers
| Provider | Install extra | Notes |
|---|---|---|
| AWS Bedrock | pip install rampart[bedrock] |
Uses standard AWS credential chain |
| Snowflake Cortex | pip install rampart[cortex] |
Requires Snowflake account config |
| Azure OpenAI | pip install rampart[azure] |
Coming in v0.2 |
| OpenAI | pip install rampart[openai] |
Coming in v0.2 |
Roadmap
v0.1 (current)
PiiGuardandPromptInjectionGuardbuilt-in- File and HTTP policy registries
- AWS Bedrock and Snowflake Cortex providers
- Classifier and hybrid engine modes
- Structured audit log
v0.2
- Git policy registry with change detection
ToxicityGuard,BadWordsGuard,SensitiveDataGuard- Azure OpenAI and OpenAI providers
- Async client (
AsyncRampart) - Policy version pinning and compatibility checks
v0.3
- FastAPI gateway mode (deploy Rampart as a shared service)
- Pip-installable thin client for the gateway mode
- Webhook-triggered policy reloads
Contributing
Rampart is at its best when the guard library grows with the community. The easiest contribution is a new guard.
- Fork the repo
- Create
rampart/guards/your_guard.py— subclassBaseGuard - Add a test in
tests/guards/test_your_guard.py - Add an example policy snippet to
docs/guards/your_guard.md - Open a pull request
See CONTRIBUTING.md for the full guide.
License
MIT — see LICENSE.
Acknowledgements
Rampart stands on the shoulders of excellent open source work:
- Microsoft Presidio — PII detection and anonymisation
- LLM Guard — input and output scanners
- Open Policy Agent — the architectural inspiration
Built for enterprise GenAI teams who need policy enforcement without policy complexity.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rampart_llm-0.1.1.tar.gz.
File metadata
- Download URL: rampart_llm-0.1.1.tar.gz
- Upload date:
- Size: 32.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ae361e518864e9f36c5c51f78115425775d1e51b5431e8b75fe47dc9db3c6fea
|
|
| MD5 |
830e03ad273dd6aae5422ea388cd02df
|
|
| BLAKE2b-256 |
232a294a04202b473464ccee27861b82847a14cd6087712ed8a6102923d674bc
|
File details
Details for the file rampart_llm-0.1.1-py3-none-any.whl.
File metadata
- Download URL: rampart_llm-0.1.1-py3-none-any.whl
- Upload date:
- Size: 36.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.11
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7919ad0e29af7f73ace35b8fe5d937b0a42c74b1b2d862e69307b437ad9a307c
|
|
| MD5 |
1f391d0a6968b6700463e98c2f383820
|
|
| BLAKE2b-256 |
f7a589f7541bd772509af92bc92725806d4c370ca049eff431b9b8a2c0f65349
|