SENTINEL
A provenance-aware security proxy for AI agents. It sits between your agent and your MCP tool servers, tracks where every byte of context came from, and refuses actions whose data originated in untrusted content — even when the attack slipped past your content filter.
pip install sentinel-prox # imports and CLI are both `sentinel`
sentinel init # write sentinel.yaml
sentinel check # validate config, connect to your servers, vet their tools
sentinel scaffold > policy.yaml
sentinel serve # your agent points at http://127.0.0.1:8765/mcp
Your agent needs no code changes: point its MCP endpoint at SENTINEL instead of directly at your tool servers. Interception is guaranteed by topology, not by asking the agent to cooperate.
The problem
AI agents don't just answer questions any more — they send email, query business systems, and read the open web. That makes indirect prompt injection an action problem, not a text problem: an attacker hides an instruction inside content the agent will read, and it executes with the agent's full privileges. "Summarize this pricing page" quietly becomes "email this customer's SSN to the attacker."
Content filters scan the words. Microsoft's own Prompt Shields documentation says it "may not catch all attack vectors" and recommends additional validation layers. The gap: security is applied to the words, while the damage is done by the actions.
SENTINEL closes it by judging an action on where its data came from, not on how the request was phrased.
What it protects against
| Attack | How SENTINEL stops it |
|---|---|
| Indirect prompt injection | Actions whose lineage includes untrusted content are denied — regardless of phrasing, so obfuscation doesn't help |
| Data exfiltration | A send_email built from a retrieved page is refused before it executes |
| Tool poisoning | Tool descriptions and input schemas are scanned at connect; a poisoned catalogue is refused |
| Cross-server shadowing | Two servers claiming one tool name fails closed — SENTINEL won't guess which is authoritative |
| Rug pulls | The catalogue is fingerprinted at approval and re-checked; post-approval mutation is detected |
| Privilege escalation | Unknown tools are default-denied until you write a rule |
| Repeated abuse | A trust score degrades on blocked calls and quarantines the agent |
Every decision becomes an immutable, replayable forensic record, exportable as SIEM-ready JSONL.
How it works
your agent ──MCP──▶ SENTINEL ──MCP──▶ your MCP servers
│
1. trace label the origin of everything the agent has seen
2. authorize policy decides each call using that lineage (deny-overrides)
3. contain trust score + automatic quarantine
4. record immutable spans → replay + SOC export
Provenance is a set of trust labels (SYSTEM > USER > AGENT > RETRIEVED_CONTENT)
unioned over an action's transitive derived_from ancestry — computed by a real
cycle-safe graph walk, not a mutable flag. An action is tainted iff
RETRIEVED_CONTENT is in that set. Taint clears only through an explicit, auditable
StructuredExtractor (strict schema validation produces a fresh SYSTEM-trust value
with no inherited ancestry), and a sanitized value cannot launder a tainted
sibling — recombination re-taints.
Policy compiles to a typed condition AST and is evaluated by tree-walk;
there is no eval anywhere in the codebase. Rules are deny-only with
deny-overrides, and unknown tools are default-denied.
Configuration
sentinel.yaml (created by sentinel init):
servers: # YOUR MCP servers — SENTINEL ships no tools
- name: github
url: https://mcp.example/gh
policy: ./policy.yaml # your rules; generate with `sentinel scaffold`
host: 127.0.0.1
port: 8765
dashboard: false # the bundled UI is a DEMO, opt-in only
catalogue_strict: true # refuse catalogues containing injection markers
Precedence is CLI flag > environment variable > config file > default, so a
container can override a checked-in file. Every key has an env equivalent
(SENTINEL_MCP_SERVERS, SENTINEL_POLICY_FILE, …) — see .env.example.
Writing policy
Rules are deny-only: a call is allowed when no deny rule matches. sentinel scaffold emits every discovered tool explicitly denied, with its description and a
recommended starting rule, so you edit rather than invent.
policy_version: 1
tools:
send_email:
rules:
- id: block-untrusted-origin
deny_if: "RETRIEVED_CONTENT in effective_provenance"
- id: domain-allowlist
deny_if: "recipient_domain not in allowed_domains"
delete_record:
rules:
- id: never
deny_always: true
Predicates support == != < >= in "not in", set literals ({USER}), tool arguments,
and config values.
Deployment
docker build -t sentinel -f deploy/Dockerfile .
docker run --rm -p 8765:8765 -v $(pwd)/sentinel.yaml:/app/sentinel.yaml sentinel
Put your tool servers on an internal network reachable only by SENTINEL — that
topology is what makes interception unbypassable. An Azure Container Apps blueprint
(internal-ingress tool servers, KEDA scaling, managed identity, Cosmos persistence)
is in deploy/.
Gate the endpoint on any public deploy with SENTINEL_API_TOKEN.
Try the demo
A bundled demo shows the whole pipeline on a scripted attack — useful for seeing what a block looks like, but not the product surface:
sentinel serve --dashboard # → http://localhost:8765
Hosted: https://sentinel-i63x.onrender.com (free tier — first load may take ~50 s
to wake). A poisoned page induces the agent to email a synthetic customer record to an
attacker; the Layer-1 filter misses the obfuscated variant and authorization blocks it
anyway. All demo data is synthetic — the record is a labelled fake
(SSN 000-00-0000, a non-functional sk-synthetic-DO-NOT-USE key) and send_email
writes to an in-memory sink. Nothing is ever sent.
With a real model
The default agent is a real LLM whenever a credential is present — set
OPENAI_API_KEY, or AZURE_OPENAI_ENDPOINT + AZURE_OPENAI_DEPLOYMENT. With no
credential it falls back to a deterministic scripted transcript so CI stays key-free.
For a free local model, point OPENAI_BASE_URL at any OpenAI-compatible endpoint:
ollama serve && ollama pull llama3.2
export OPENAI_BASE_URL=http://localhost:11434/v1 OPENAI_MODEL=llama3.2
What SENTINEL does not protect against
Stating the boundary precisely is what separates a security product from a demo.
- Provenance is tracked at message / tool-result granularity, not token-level inside model reasoning. Taint spreads conservatively unless a sanitizer clears it.
- It secures the action layer, not the model's cognition. It does not stop a model being persuaded — it stops the resulting unauthorized action.
- The proxy and the policy store are trusted components.
- One trace is handled by one proxy instance; horizontal scaling is across traces.
- Conservative tainting is intentional. Some benign workflows will need explicit sanitization. Taint saturation is the correct bias for action-layer security.
- Sanitization is syntactic, not semantic. A schema-valid
{"price": 999999}is well-formed but still subject to argument-level rules such as an amount cap.
Development
python -m venv .venv
.venv/bin/python -m pip install -e ".[dev]" -c versions.lock # Windows: .venv\Scripts\python.exe
.venv/bin/python -m pytest # 370 tests
.venv/bin/python -m ruff check src tests
.venv/bin/python -m mypy src # strict
Install -c versions.lock so local matches CI and the container — a floating
dependency is how a production deploy once broke. Requires Python 3.11+.
See CONTRIBUTING.md for the security invariants a change must
preserve, and CHANGELOG.md for release notes. Design notes live in
docs/: the scoping analysis for proxying arbitrary MCP servers, and the
competitive/threat-landscape research behind the roadmap.
License & credits
MIT — see LICENSE.
Built on the Model Context Protocol Python SDK,
FastAPI, Starlette, sse-starlette, Uvicorn, Pydantic, OpenTelemetry, PyYAML, httpx,
pytest, ruff, mypy, gitleaks, and the Azure SDKs for Python. Thank you to their
maintainers.
AI tools used in development: Claude Code (Anthropic) and GitHub Copilot. SENTINEL also integrates Azure OpenAI (attack classification) and Azure AI Content Safety / Prompt Shields (Layer-1 screening) as optional components.
Originally built for the Microsoft Build AI Hackathon 2026 — Security in the Agentic Future — by Pali Krishna Harshith.
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 sentinel_prox-0.1.1.tar.gz.
File metadata
- Download URL: sentinel_prox-0.1.1.tar.gz
- Upload date:
- Size: 326.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b6609ab2631fc0936c4e357c80556e1784b86505da1a7206fe37642e42028b62
|
|
| MD5 |
727776009ea189b695883f6d07f71c25
|
|
| BLAKE2b-256 |
b179019b02fd812c27304cc8fe6134381642d84ba0e1c80c8addea7e04cc2f13
|
Provenance
The following attestation bundles were made for sentinel_prox-0.1.1.tar.gz:
Publisher:
release.yml on Harshith029/Sentinel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sentinel_prox-0.1.1.tar.gz -
Subject digest:
b6609ab2631fc0936c4e357c80556e1784b86505da1a7206fe37642e42028b62 - Sigstore transparency entry: 2381848730
- Sigstore integration time:
-
Permalink:
Harshith029/Sentinel@a94434d6c1e965228bef90fd39add43cc96bf7a0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Harshith029
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a94434d6c1e965228bef90fd39add43cc96bf7a0 -
Trigger Event:
push
-
Statement type:
File details
Details for the file sentinel_prox-0.1.1-py3-none-any.whl.
File metadata
- Download URL: sentinel_prox-0.1.1-py3-none-any.whl
- Upload date:
- Size: 291.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fc87aa1da46ca004e73b2a6b971d53a93e6530a3cd86ce816d9e3902617f5b55
|
|
| MD5 |
d3536e113fa65508691536e64118aa35
|
|
| BLAKE2b-256 |
1d4d04f51260e849e27a4c5cf4b769ed5a3d291d09eed87bf93fa2dcbc38db13
|
Provenance
The following attestation bundles were made for sentinel_prox-0.1.1-py3-none-any.whl:
Publisher:
release.yml on Harshith029/Sentinel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
sentinel_prox-0.1.1-py3-none-any.whl -
Subject digest:
fc87aa1da46ca004e73b2a6b971d53a93e6530a3cd86ce816d9e3902617f5b55 - Sigstore transparency entry: 2381848789
- Sigstore integration time:
-
Permalink:
Harshith029/Sentinel@a94434d6c1e965228bef90fd39add43cc96bf7a0 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/Harshith029
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@a94434d6c1e965228bef90fd39add43cc96bf7a0 -
Trigger Event:
push
-
Statement type: