Skip to main content
Yanked

This release has been yanked by its maintainers, and will be ignored by installers, except when explicitly specified.
Consider using release 0.1.1 instead.
Reason given by maintainers: broken on fresh install: unbounded mcp dependency resolved to 2.0.0

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

sentinel_prox-0.1.0.tar.gz (326.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

sentinel_prox-0.1.0-py3-none-any.whl (291.7 kB view details)

Uploaded Python 3

File details

Details for the file sentinel_prox-0.1.0.tar.gz.

File metadata

  • Download URL: sentinel_prox-0.1.0.tar.gz
  • Upload date:
  • Size: 326.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for sentinel_prox-0.1.0.tar.gz
Algorithm Hash digest
SHA256 39af9dfcb27f9461298730d527ca50b2c21d26a18be4462e0263041958f9b841
MD5 3908f5cc241742f992004f78f335e2ec
BLAKE2b-256 ceeee873c2077426d4127f6600185cb8a59923119215d29f804b1a685b9b76d4

See more details on using hashes here.

Provenance

The following attestation bundles were made for sentinel_prox-0.1.0.tar.gz:

Publisher: release.yml on Harshith029/Sentinel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file sentinel_prox-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: sentinel_prox-0.1.0-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

Hashes for sentinel_prox-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 818324f0d99a18c84aa6ddd4523f999f360cf62efc963fcf5d5a6d414091cc7d
MD5 cb4da73b0a9d858485826b1e78dc2763
BLAKE2b-256 c0bc88248da0a581ef21e6a82bc35fd341567b293f49ecb9f6f17a52267bcca2

See more details on using hashes here.

Provenance

The following attestation bundles were made for sentinel_prox-0.1.0-py3-none-any.whl:

Publisher: release.yml on Harshith029/Sentinel

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page