sentinel-prox 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. sentinel_prox-0.1.0/LICENSE +21 -0
  2. sentinel_prox-0.1.0/PKG-INFO +258 -0
  3. sentinel_prox-0.1.0/README.md +208 -0
  4. sentinel_prox-0.1.0/pyproject.toml +141 -0
  5. sentinel_prox-0.1.0/setup.cfg +4 -0
  6. sentinel_prox-0.1.0/src/sentinel/__init__.py +6 -0
  7. sentinel_prox-0.1.0/src/sentinel/authorization/__init__.py +11 -0
  8. sentinel_prox-0.1.0/src/sentinel/authorization/ast.py +189 -0
  9. sentinel_prox-0.1.0/src/sentinel/authorization/engine.py +177 -0
  10. sentinel_prox-0.1.0/src/sentinel/authorization/parser.py +262 -0
  11. sentinel_prox-0.1.0/src/sentinel/authorization/policies/default.yaml +45 -0
  12. sentinel_prox-0.1.0/src/sentinel/authorization/policy.py +156 -0
  13. sentinel_prox-0.1.0/src/sentinel/authorization/registry.py +89 -0
  14. sentinel_prox-0.1.0/src/sentinel/catalogue.py +279 -0
  15. sentinel_prox-0.1.0/src/sentinel/classifier/__init__.py +19 -0
  16. sentinel_prox-0.1.0/src/sentinel/classifier/attack_classifier.py +167 -0
  17. sentinel_prox-0.1.0/src/sentinel/cli.py +285 -0
  18. sentinel_prox-0.1.0/src/sentinel/config.py +205 -0
  19. sentinel_prox-0.1.0/src/sentinel/control/__init__.py +12 -0
  20. sentinel_prox-0.1.0/src/sentinel/control/app.py +485 -0
  21. sentinel_prox-0.1.0/src/sentinel/control/capabilities.py +97 -0
  22. sentinel_prox-0.1.0/src/sentinel/control/events.py +102 -0
  23. sentinel_prox-0.1.0/src/sentinel/control/manager.py +502 -0
  24. sentinel_prox-0.1.0/src/sentinel/control/mcp_gateway.py +424 -0
  25. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-400.woff2 +0 -0
  26. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-500.woff2 +0 -0
  27. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-600.woff2 +0 -0
  28. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-700.woff2 +0 -0
  29. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/jetbrainsmono-400.woff2 +0 -0
  30. sentinel_prox-0.1.0/src/sentinel/control/static/fonts/jetbrainsmono-600.woff2 +0 -0
  31. sentinel_prox-0.1.0/src/sentinel/control/static/index.html +659 -0
  32. sentinel_prox-0.1.0/src/sentinel/demo/__init__.py +16 -0
  33. sentinel_prox-0.1.0/src/sentinel/demo/driver.py +107 -0
  34. sentinel_prox-0.1.0/src/sentinel/demo/foundry.py +94 -0
  35. sentinel_prox-0.1.0/src/sentinel/demo/llm_driver.py +450 -0
  36. sentinel_prox-0.1.0/src/sentinel/demo/preflight.py +99 -0
  37. sentinel_prox-0.1.0/src/sentinel/demo/scenario.py +392 -0
  38. sentinel_prox-0.1.0/src/sentinel/demo/tool_servers.py +150 -0
  39. sentinel_prox-0.1.0/src/sentinel/demo/toolserver_main.py +35 -0
  40. sentinel_prox-0.1.0/src/sentinel/downstream.py +217 -0
  41. sentinel_prox-0.1.0/src/sentinel/forensics/__init__.py +17 -0
  42. sentinel_prox-0.1.0/src/sentinel/forensics/emitter.py +88 -0
  43. sentinel_prox-0.1.0/src/sentinel/forensics/events.py +215 -0
  44. sentinel_prox-0.1.0/src/sentinel/forensics/replay.py +124 -0
  45. sentinel_prox-0.1.0/src/sentinel/forensics/span.py +93 -0
  46. sentinel_prox-0.1.0/src/sentinel/forensics/store.py +336 -0
  47. sentinel_prox-0.1.0/src/sentinel/labels.py +52 -0
  48. sentinel_prox-0.1.0/src/sentinel/mcp_proxy/__init__.py +10 -0
  49. sentinel_prox-0.1.0/src/sentinel/mcp_proxy/content.py +50 -0
  50. sentinel_prox-0.1.0/src/sentinel/mcp_proxy/proxy.py +326 -0
  51. sentinel_prox-0.1.0/src/sentinel/mcp_proxy/router.py +83 -0
  52. sentinel_prox-0.1.0/src/sentinel/mcp_proxy/skeleton.py +135 -0
  53. sentinel_prox-0.1.0/src/sentinel/provenance/__init__.py +17 -0
  54. sentinel_prox-0.1.0/src/sentinel/provenance/graph.py +138 -0
  55. sentinel_prox-0.1.0/src/sentinel/provenance/model.py +47 -0
  56. sentinel_prox-0.1.0/src/sentinel/provenance/sanitizer.py +171 -0
  57. sentinel_prox-0.1.0/src/sentinel/py.typed +0 -0
  58. sentinel_prox-0.1.0/src/sentinel/scaffold.py +167 -0
  59. sentinel_prox-0.1.0/src/sentinel/shield/__init__.py +12 -0
  60. sentinel_prox-0.1.0/src/sentinel/shield/input_shield.py +131 -0
  61. sentinel_prox-0.1.0/src/sentinel/toolservers/__init__.py +15 -0
  62. sentinel_prox-0.1.0/src/sentinel/toolservers/web_fetch.py +202 -0
  63. sentinel_prox-0.1.0/src/sentinel/tracing.py +117 -0
  64. sentinel_prox-0.1.0/src/sentinel/trust/__init__.py +15 -0
  65. sentinel_prox-0.1.0/src/sentinel/trust/config.py +59 -0
  66. sentinel_prox-0.1.0/src/sentinel/trust/config.yaml +19 -0
  67. sentinel_prox-0.1.0/src/sentinel/trust/scorer.py +304 -0
  68. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/PKG-INFO +258 -0
  69. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/SOURCES.txt +106 -0
  70. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/dependency_links.txt +1 -0
  71. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/entry_points.txt +2 -0
  72. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/requires.txt +23 -0
  73. sentinel_prox-0.1.0/src/sentinel_prox.egg-info/top_level.txt +1 -0
  74. sentinel_prox-0.1.0/tests/test_agent_default_driver.py +66 -0
  75. sentinel_prox-0.1.0/tests/test_authorization_ast.py +75 -0
  76. sentinel_prox-0.1.0/tests/test_authorization_engine.py +169 -0
  77. sentinel_prox-0.1.0/tests/test_authorization_parser.py +158 -0
  78. sentinel_prox-0.1.0/tests/test_authorization_policy.py +101 -0
  79. sentinel_prox-0.1.0/tests/test_azure_wiring.py +52 -0
  80. sentinel_prox-0.1.0/tests/test_catalogue_enforcement.py +103 -0
  81. sentinel_prox-0.1.0/tests/test_catalogue_integrity.py +153 -0
  82. sentinel_prox-0.1.0/tests/test_catalogue_monitor.py +93 -0
  83. sentinel_prox-0.1.0/tests/test_cli.py +145 -0
  84. sentinel_prox-0.1.0/tests/test_downstream_discovery.py +148 -0
  85. sentinel_prox-0.1.0/tests/test_downstream_endpoint.py +80 -0
  86. sentinel_prox-0.1.0/tests/test_forensics_emitter.py +97 -0
  87. sentinel_prox-0.1.0/tests/test_forensics_events.py +124 -0
  88. sentinel_prox-0.1.0/tests/test_forensics_replay.py +123 -0
  89. sentinel_prox-0.1.0/tests/test_forensics_span.py +97 -0
  90. sentinel_prox-0.1.0/tests/test_forensics_store.py +195 -0
  91. sentinel_prox-0.1.0/tests/test_mcp_skeleton.py +87 -0
  92. sentinel_prox-0.1.0/tests/test_phase0_gitleaks.py +127 -0
  93. sentinel_prox-0.1.0/tests/test_phase0_smoke.py +194 -0
  94. sentinel_prox-0.1.0/tests/test_phase10_llm_agent.py +196 -0
  95. sentinel_prox-0.1.0/tests/test_phase2_dod.py +346 -0
  96. sentinel_prox-0.1.0/tests/test_phase3_dod.py +187 -0
  97. sentinel_prox-0.1.0/tests/test_phase4_dod.py +278 -0
  98. sentinel_prox-0.1.0/tests/test_phase5_hero.py +190 -0
  99. sentinel_prox-0.1.0/tests/test_phase6_control_plane.py +458 -0
  100. sentinel_prox-0.1.0/tests/test_phase7_dashboard.py +59 -0
  101. sentinel_prox-0.1.0/tests/test_phase8_enterprise.py +179 -0
  102. sentinel_prox-0.1.0/tests/test_phase9_mcp_gateway.py +340 -0
  103. sentinel_prox-0.1.0/tests/test_policy_scaffold.py +123 -0
  104. sentinel_prox-0.1.0/tests/test_provenance_graph.py +93 -0
  105. sentinel_prox-0.1.0/tests/test_real_web_pipeline.py +56 -0
  106. sentinel_prox-0.1.0/tests/test_sanitizer.py +88 -0
  107. sentinel_prox-0.1.0/tests/test_toolservers_web_fetch.py +128 -0
  108. sentinel_prox-0.1.0/tests/test_trust_scorer.py +132 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Pali Krishna Harshith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,258 @@
1
+ Metadata-Version: 2.4
2
+ Name: sentinel-prox
3
+ Version: 0.1.0
4
+ Summary: Provenance-aware security proxy for AI agents: block prompt-injection actions at the MCP tool boundary.
5
+ Author: Pali Krishna Harshith
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Harshith029/Sentinel
8
+ Project-URL: Repository, https://github.com/Harshith029/Sentinel
9
+ Project-URL: Issues, https://github.com/Harshith029/Sentinel/issues
10
+ Project-URL: Demo, https://sentinel-i63x.onrender.com
11
+ Keywords: mcp,model-context-protocol,ai-security,prompt-injection,ai-agents,llm-security,agentic-ai,security-proxy,information-flow-control,provenance
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Security
21
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
22
+ Classifier: Topic :: System :: Networking :: Monitoring
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: azure-ai-projects==2.0.0
28
+ Requires-Dist: azure-identity>=1.17
29
+ Requires-Dist: azure-cosmos>=4.7
30
+ Requires-Dist: azure-monitor-opentelemetry>=1.6
31
+ Requires-Dist: mcp>=1.2
32
+ Requires-Dist: openai>=1.50
33
+ Requires-Dist: opentelemetry-api>=1.27
34
+ Requires-Dist: opentelemetry-sdk>=1.27
35
+ Requires-Dist: fastapi>=0.115
36
+ Requires-Dist: uvicorn[standard]>=0.32
37
+ Requires-Dist: sse-starlette>=2.1
38
+ Requires-Dist: httpx>=0.27
39
+ Requires-Dist: pydantic<3,>=2.9
40
+ Requires-Dist: pyyaml>=6.0
41
+ Provides-Extra: dev
42
+ Requires-Dist: pytest>=8.3; extra == "dev"
43
+ Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
44
+ Requires-Dist: httpx>=0.27; extra == "dev"
45
+ Requires-Dist: pre-commit>=3.8; extra == "dev"
46
+ Requires-Dist: ruff>=0.6; extra == "dev"
47
+ Requires-Dist: mypy>=1.11; extra == "dev"
48
+ Requires-Dist: types-PyYAML>=6.0; extra == "dev"
49
+ Dynamic: license-file
50
+
51
+ # SENTINEL
52
+
53
+ **A provenance-aware security proxy for AI agents.** It sits between your agent and
54
+ your MCP tool servers, tracks where every byte of context came from, and refuses
55
+ actions whose data originated in untrusted content — even when the attack slipped
56
+ past your content filter.
57
+
58
+ ```bash
59
+ pip install sentinel-prox # imports and CLI are both `sentinel`
60
+ sentinel init # write sentinel.yaml
61
+ sentinel check # validate config, connect to your servers, vet their tools
62
+ sentinel scaffold > policy.yaml
63
+ sentinel serve # your agent points at http://127.0.0.1:8765/mcp
64
+ ```
65
+
66
+ Your agent needs **no code changes**: point its MCP endpoint at SENTINEL instead of
67
+ directly at your tool servers. Interception is guaranteed by topology, not by asking
68
+ the agent to cooperate.
69
+
70
+ ---
71
+
72
+ ## The problem
73
+
74
+ AI agents don't just answer questions any more — they send email, query business
75
+ systems, and read the open web. That makes **indirect prompt injection** an action
76
+ problem, not a text problem: an attacker hides an instruction inside content the
77
+ agent will read, and it executes with the agent's full privileges.
78
+ *"Summarize this pricing page"* quietly becomes *"email this customer's SSN to the
79
+ attacker."*
80
+
81
+ Content filters scan the words. Microsoft's own Prompt Shields documentation says it
82
+ "may not catch all attack vectors" and recommends additional validation layers.
83
+ **The gap: security is applied to the words, while the damage is done by the actions.**
84
+
85
+ SENTINEL closes it by judging an action on **where its data came from**, not on how
86
+ the request was phrased.
87
+
88
+ ## What it protects against
89
+
90
+ | Attack | How SENTINEL stops it |
91
+ |---|---|
92
+ | **Indirect prompt injection** | Actions whose lineage includes untrusted content are denied — regardless of phrasing, so obfuscation doesn't help |
93
+ | **Data exfiltration** | A `send_email` built from a retrieved page is refused before it executes |
94
+ | **Tool poisoning** | Tool descriptions *and* input schemas are scanned at connect; a poisoned catalogue is refused |
95
+ | **Cross-server shadowing** | Two servers claiming one tool name fails closed — SENTINEL won't guess which is authoritative |
96
+ | **Rug pulls** | The catalogue is fingerprinted at approval and re-checked; post-approval mutation is detected |
97
+ | **Privilege escalation** | Unknown tools are default-denied until you write a rule |
98
+ | **Repeated abuse** | A trust score degrades on blocked calls and quarantines the agent |
99
+
100
+ Every decision becomes an immutable, replayable forensic record, exportable as
101
+ SIEM-ready JSONL.
102
+
103
+ ## How it works
104
+
105
+ ```
106
+ your agent ──MCP──▶ SENTINEL ──MCP──▶ your MCP servers
107
+ │
108
+ 1. trace label the origin of everything the agent has seen
109
+ 2. authorize policy decides each call using that lineage (deny-overrides)
110
+ 3. contain trust score + automatic quarantine
111
+ 4. record immutable spans → replay + SOC export
112
+ ```
113
+
114
+ Provenance is a **set of trust labels** (`SYSTEM > USER > AGENT > RETRIEVED_CONTENT`)
115
+ unioned over an action's transitive `derived_from` ancestry — computed by a real
116
+ cycle-safe graph walk, not a mutable flag. An action is tainted iff
117
+ `RETRIEVED_CONTENT` is in that set. Taint clears only through an explicit, auditable
118
+ `StructuredExtractor` (strict schema validation produces a fresh SYSTEM-trust value
119
+ with no inherited ancestry), and a sanitized value **cannot launder a tainted
120
+ sibling** — recombination re-taints.
121
+
122
+ Policy compiles to a **typed condition AST** and is evaluated by tree-walk;
123
+ there is no `eval` anywhere in the codebase. Rules are deny-only with
124
+ deny-overrides, and **unknown tools are default-denied**.
125
+
126
+ ## Configuration
127
+
128
+ `sentinel.yaml` (created by `sentinel init`):
129
+
130
+ ```yaml
131
+ servers: # YOUR MCP servers — SENTINEL ships no tools
132
+ - name: github
133
+ url: https://mcp.example/gh
134
+ policy: ./policy.yaml # your rules; generate with `sentinel scaffold`
135
+ host: 127.0.0.1
136
+ port: 8765
137
+ dashboard: false # the bundled UI is a DEMO, opt-in only
138
+ catalogue_strict: true # refuse catalogues containing injection markers
139
+ ```
140
+
141
+ Precedence is **CLI flag > environment variable > config file > default**, so a
142
+ container can override a checked-in file. Every key has an env equivalent
143
+ (`SENTINEL_MCP_SERVERS`, `SENTINEL_POLICY_FILE`, …) — see [`.env.example`](./.env.example).
144
+
145
+ ### Writing policy
146
+
147
+ Rules are **deny-only**: a call is allowed when no deny rule matches. `sentinel
148
+ scaffold` emits every discovered tool explicitly denied, with its description and a
149
+ recommended starting rule, so you edit rather than invent.
150
+
151
+ ```yaml
152
+ policy_version: 1
153
+ tools:
154
+ send_email:
155
+ rules:
156
+ - id: block-untrusted-origin
157
+ deny_if: "RETRIEVED_CONTENT in effective_provenance"
158
+ - id: domain-allowlist
159
+ deny_if: "recipient_domain not in allowed_domains"
160
+ delete_record:
161
+ rules:
162
+ - id: never
163
+ deny_always: true
164
+ ```
165
+
166
+ Predicates support `== != < >= in "not in"`, set literals (`{USER}`), tool arguments,
167
+ and config values.
168
+
169
+ ## Deployment
170
+
171
+ ```bash
172
+ docker build -t sentinel -f deploy/Dockerfile .
173
+ docker run --rm -p 8765:8765 -v $(pwd)/sentinel.yaml:/app/sentinel.yaml sentinel
174
+ ```
175
+
176
+ Put your tool servers on an internal network reachable **only** by SENTINEL — that
177
+ topology is what makes interception unbypassable. An Azure Container Apps blueprint
178
+ (internal-ingress tool servers, KEDA scaling, managed identity, Cosmos persistence)
179
+ is in [`deploy/`](./deploy).
180
+
181
+ Gate the endpoint on any public deploy with `SENTINEL_API_TOKEN`.
182
+
183
+ ## Try the demo
184
+
185
+ A bundled demo shows the whole pipeline on a scripted attack — useful for seeing what
186
+ a block looks like, but **not** the product surface:
187
+
188
+ ```bash
189
+ sentinel serve --dashboard # → http://localhost:8765
190
+ ```
191
+
192
+ Hosted: **https://sentinel-i63x.onrender.com** (free tier — first load may take ~50 s
193
+ to wake). A poisoned page induces the agent to email a synthetic customer record to an
194
+ attacker; the Layer-1 filter misses the obfuscated variant and authorization blocks it
195
+ anyway. All demo data is synthetic — the record is a labelled fake
196
+ (SSN `000-00-0000`, a non-functional `sk-synthetic-DO-NOT-USE` key) and `send_email`
197
+ writes to an in-memory sink. Nothing is ever sent.
198
+
199
+ ### With a real model
200
+
201
+ The default agent is a real LLM whenever a credential is present — set
202
+ `OPENAI_API_KEY`, or `AZURE_OPENAI_ENDPOINT` + `AZURE_OPENAI_DEPLOYMENT`. With no
203
+ credential it falls back to a deterministic scripted transcript so CI stays key-free.
204
+ For a free local model, point `OPENAI_BASE_URL` at any OpenAI-compatible endpoint:
205
+
206
+ ```bash
207
+ ollama serve && ollama pull llama3.2
208
+ export OPENAI_BASE_URL=http://localhost:11434/v1 OPENAI_MODEL=llama3.2
209
+ ```
210
+
211
+ ## What SENTINEL does *not* protect against
212
+
213
+ Stating the boundary precisely is what separates a security product from a demo.
214
+
215
+ - Provenance is tracked at **message / tool-result granularity**, not token-level
216
+ inside model reasoning. Taint spreads conservatively unless a sanitizer clears it.
217
+ - It secures the **action layer**, not the model's cognition. It does not stop a model
218
+ being *persuaded* — it stops the resulting unauthorized **action**.
219
+ - The proxy and the policy store are **trusted** components.
220
+ - One trace is handled by one proxy instance; horizontal scaling is *across* traces.
221
+ - **Conservative tainting is intentional.** Some benign workflows will need explicit
222
+ sanitization. Taint saturation is the correct bias for action-layer security.
223
+ - **Sanitization is syntactic, not semantic.** A schema-valid `{"price": 999999}` is
224
+ well-formed but still subject to argument-level rules such as an amount cap.
225
+
226
+ ## Development
227
+
228
+ ```bash
229
+ python -m venv .venv
230
+ .venv/bin/python -m pip install -e ".[dev]" -c versions.lock # Windows: .venv\Scripts\python.exe
231
+ .venv/bin/python -m pytest # 370 tests
232
+ .venv/bin/python -m ruff check src tests
233
+ .venv/bin/python -m mypy src # strict
234
+ ```
235
+
236
+ Install `-c versions.lock` so local matches CI and the container — a floating
237
+ dependency is how a production deploy once broke. Requires Python 3.11+.
238
+
239
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the security invariants a change must
240
+ preserve, and [CHANGELOG.md](./CHANGELOG.md) for release notes. Design notes live in
241
+ [`docs/`](./docs): the scoping analysis for proxying arbitrary MCP servers, and the
242
+ competitive/threat-landscape research behind the roadmap.
243
+
244
+ ## License & credits
245
+
246
+ MIT — see [LICENSE](./LICENSE).
247
+
248
+ Built on the [Model Context Protocol](https://modelcontextprotocol.io) Python SDK,
249
+ FastAPI, Starlette, `sse-starlette`, Uvicorn, Pydantic, OpenTelemetry, PyYAML, httpx,
250
+ pytest, ruff, mypy, gitleaks, and the Azure SDKs for Python. Thank you to their
251
+ maintainers.
252
+
253
+ **AI tools used in development:** Claude Code (Anthropic) and GitHub Copilot.
254
+ SENTINEL also *integrates* Azure OpenAI (attack classification) and Azure AI Content
255
+ Safety / Prompt Shields (Layer-1 screening) as optional components.
256
+
257
+ Originally built for the Microsoft Build AI Hackathon 2026 — *Security in the Agentic
258
+ Future* — by Pali Krishna Harshith.
@@ -0,0 +1,208 @@
1
+ # SENTINEL
2
+
3
+ **A provenance-aware security proxy for AI agents.** It sits between your agent and
4
+ your MCP tool servers, tracks where every byte of context came from, and refuses
5
+ actions whose data originated in untrusted content — even when the attack slipped
6
+ past your content filter.
7
+
8
+ ```bash
9
+ pip install sentinel-prox # imports and CLI are both `sentinel`
10
+ sentinel init # write sentinel.yaml
11
+ sentinel check # validate config, connect to your servers, vet their tools
12
+ sentinel scaffold > policy.yaml
13
+ sentinel serve # your agent points at http://127.0.0.1:8765/mcp
14
+ ```
15
+
16
+ Your agent needs **no code changes**: point its MCP endpoint at SENTINEL instead of
17
+ directly at your tool servers. Interception is guaranteed by topology, not by asking
18
+ the agent to cooperate.
19
+
20
+ ---
21
+
22
+ ## The problem
23
+
24
+ AI agents don't just answer questions any more — they send email, query business
25
+ systems, and read the open web. That makes **indirect prompt injection** an action
26
+ problem, not a text problem: an attacker hides an instruction inside content the
27
+ agent will read, and it executes with the agent's full privileges.
28
+ *"Summarize this pricing page"* quietly becomes *"email this customer's SSN to the
29
+ attacker."*
30
+
31
+ Content filters scan the words. Microsoft's own Prompt Shields documentation says it
32
+ "may not catch all attack vectors" and recommends additional validation layers.
33
+ **The gap: security is applied to the words, while the damage is done by the actions.**
34
+
35
+ SENTINEL closes it by judging an action on **where its data came from**, not on how
36
+ the request was phrased.
37
+
38
+ ## What it protects against
39
+
40
+ | Attack | How SENTINEL stops it |
41
+ |---|---|
42
+ | **Indirect prompt injection** | Actions whose lineage includes untrusted content are denied — regardless of phrasing, so obfuscation doesn't help |
43
+ | **Data exfiltration** | A `send_email` built from a retrieved page is refused before it executes |
44
+ | **Tool poisoning** | Tool descriptions *and* input schemas are scanned at connect; a poisoned catalogue is refused |
45
+ | **Cross-server shadowing** | Two servers claiming one tool name fails closed — SENTINEL won't guess which is authoritative |
46
+ | **Rug pulls** | The catalogue is fingerprinted at approval and re-checked; post-approval mutation is detected |
47
+ | **Privilege escalation** | Unknown tools are default-denied until you write a rule |
48
+ | **Repeated abuse** | A trust score degrades on blocked calls and quarantines the agent |
49
+
50
+ Every decision becomes an immutable, replayable forensic record, exportable as
51
+ SIEM-ready JSONL.
52
+
53
+ ## How it works
54
+
55
+ ```
56
+ your agent ──MCP──▶ SENTINEL ──MCP──▶ your MCP servers
57
+ │
58
+ 1. trace label the origin of everything the agent has seen
59
+ 2. authorize policy decides each call using that lineage (deny-overrides)
60
+ 3. contain trust score + automatic quarantine
61
+ 4. record immutable spans → replay + SOC export
62
+ ```
63
+
64
+ Provenance is a **set of trust labels** (`SYSTEM > USER > AGENT > RETRIEVED_CONTENT`)
65
+ unioned over an action's transitive `derived_from` ancestry — computed by a real
66
+ cycle-safe graph walk, not a mutable flag. An action is tainted iff
67
+ `RETRIEVED_CONTENT` is in that set. Taint clears only through an explicit, auditable
68
+ `StructuredExtractor` (strict schema validation produces a fresh SYSTEM-trust value
69
+ with no inherited ancestry), and a sanitized value **cannot launder a tainted
70
+ sibling** — recombination re-taints.
71
+
72
+ Policy compiles to a **typed condition AST** and is evaluated by tree-walk;
73
+ there is no `eval` anywhere in the codebase. Rules are deny-only with
74
+ deny-overrides, and **unknown tools are default-denied**.
75
+
76
+ ## Configuration
77
+
78
+ `sentinel.yaml` (created by `sentinel init`):
79
+
80
+ ```yaml
81
+ servers: # YOUR MCP servers — SENTINEL ships no tools
82
+ - name: github
83
+ url: https://mcp.example/gh
84
+ policy: ./policy.yaml # your rules; generate with `sentinel scaffold`
85
+ host: 127.0.0.1
86
+ port: 8765
87
+ dashboard: false # the bundled UI is a DEMO, opt-in only
88
+ catalogue_strict: true # refuse catalogues containing injection markers
89
+ ```
90
+
91
+ Precedence is **CLI flag > environment variable > config file > default**, so a
92
+ container can override a checked-in file. Every key has an env equivalent
93
+ (`SENTINEL_MCP_SERVERS`, `SENTINEL_POLICY_FILE`, …) — see [`.env.example`](./.env.example).
94
+
95
+ ### Writing policy
96
+
97
+ Rules are **deny-only**: a call is allowed when no deny rule matches. `sentinel
98
+ scaffold` emits every discovered tool explicitly denied, with its description and a
99
+ recommended starting rule, so you edit rather than invent.
100
+
101
+ ```yaml
102
+ policy_version: 1
103
+ tools:
104
+ send_email:
105
+ rules:
106
+ - id: block-untrusted-origin
107
+ deny_if: "RETRIEVED_CONTENT in effective_provenance"
108
+ - id: domain-allowlist
109
+ deny_if: "recipient_domain not in allowed_domains"
110
+ delete_record:
111
+ rules:
112
+ - id: never
113
+ deny_always: true
114
+ ```
115
+
116
+ Predicates support `== != < >= in "not in"`, set literals (`{USER}`), tool arguments,
117
+ and config values.
118
+
119
+ ## Deployment
120
+
121
+ ```bash
122
+ docker build -t sentinel -f deploy/Dockerfile .
123
+ docker run --rm -p 8765:8765 -v $(pwd)/sentinel.yaml:/app/sentinel.yaml sentinel
124
+ ```
125
+
126
+ Put your tool servers on an internal network reachable **only** by SENTINEL — that
127
+ topology is what makes interception unbypassable. An Azure Container Apps blueprint
128
+ (internal-ingress tool servers, KEDA scaling, managed identity, Cosmos persistence)
129
+ is in [`deploy/`](./deploy).
130
+
131
+ Gate the endpoint on any public deploy with `SENTINEL_API_TOKEN`.
132
+
133
+ ## Try the demo
134
+
135
+ A bundled demo shows the whole pipeline on a scripted attack — useful for seeing what
136
+ a block looks like, but **not** the product surface:
137
+
138
+ ```bash
139
+ sentinel serve --dashboard # → http://localhost:8765
140
+ ```
141
+
142
+ Hosted: **https://sentinel-i63x.onrender.com** (free tier — first load may take ~50 s
143
+ to wake). A poisoned page induces the agent to email a synthetic customer record to an
144
+ attacker; the Layer-1 filter misses the obfuscated variant and authorization blocks it
145
+ anyway. All demo data is synthetic — the record is a labelled fake
146
+ (SSN `000-00-0000`, a non-functional `sk-synthetic-DO-NOT-USE` key) and `send_email`
147
+ writes to an in-memory sink. Nothing is ever sent.
148
+
149
+ ### With a real model
150
+
151
+ The default agent is a real LLM whenever a credential is present — set
152
+ `OPENAI_API_KEY`, or `AZURE_OPENAI_ENDPOINT` + `AZURE_OPENAI_DEPLOYMENT`. With no
153
+ credential it falls back to a deterministic scripted transcript so CI stays key-free.
154
+ For a free local model, point `OPENAI_BASE_URL` at any OpenAI-compatible endpoint:
155
+
156
+ ```bash
157
+ ollama serve && ollama pull llama3.2
158
+ export OPENAI_BASE_URL=http://localhost:11434/v1 OPENAI_MODEL=llama3.2
159
+ ```
160
+
161
+ ## What SENTINEL does *not* protect against
162
+
163
+ Stating the boundary precisely is what separates a security product from a demo.
164
+
165
+ - Provenance is tracked at **message / tool-result granularity**, not token-level
166
+ inside model reasoning. Taint spreads conservatively unless a sanitizer clears it.
167
+ - It secures the **action layer**, not the model's cognition. It does not stop a model
168
+ being *persuaded* — it stops the resulting unauthorized **action**.
169
+ - The proxy and the policy store are **trusted** components.
170
+ - One trace is handled by one proxy instance; horizontal scaling is *across* traces.
171
+ - **Conservative tainting is intentional.** Some benign workflows will need explicit
172
+ sanitization. Taint saturation is the correct bias for action-layer security.
173
+ - **Sanitization is syntactic, not semantic.** A schema-valid `{"price": 999999}` is
174
+ well-formed but still subject to argument-level rules such as an amount cap.
175
+
176
+ ## Development
177
+
178
+ ```bash
179
+ python -m venv .venv
180
+ .venv/bin/python -m pip install -e ".[dev]" -c versions.lock # Windows: .venv\Scripts\python.exe
181
+ .venv/bin/python -m pytest # 370 tests
182
+ .venv/bin/python -m ruff check src tests
183
+ .venv/bin/python -m mypy src # strict
184
+ ```
185
+
186
+ Install `-c versions.lock` so local matches CI and the container — a floating
187
+ dependency is how a production deploy once broke. Requires Python 3.11+.
188
+
189
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the security invariants a change must
190
+ preserve, and [CHANGELOG.md](./CHANGELOG.md) for release notes. Design notes live in
191
+ [`docs/`](./docs): the scoping analysis for proxying arbitrary MCP servers, and the
192
+ competitive/threat-landscape research behind the roadmap.
193
+
194
+ ## License & credits
195
+
196
+ MIT — see [LICENSE](./LICENSE).
197
+
198
+ Built on the [Model Context Protocol](https://modelcontextprotocol.io) Python SDK,
199
+ FastAPI, Starlette, `sse-starlette`, Uvicorn, Pydantic, OpenTelemetry, PyYAML, httpx,
200
+ pytest, ruff, mypy, gitleaks, and the Azure SDKs for Python. Thank you to their
201
+ maintainers.
202
+
203
+ **AI tools used in development:** Claude Code (Anthropic) and GitHub Copilot.
204
+ SENTINEL also *integrates* Azure OpenAI (attack classification) and Azure AI Content
205
+ Safety / Prompt Shields (Layer-1 screening) as optional components.
206
+
207
+ Originally built for the Microsoft Build AI Hackathon 2026 — *Security in the Agentic
208
+ Future* — by Pali Krishna Harshith.
@@ -0,0 +1,141 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ # Distribution name only — the import package and CLI command are both `sentinel`.
7
+ # (`sentinel`, `sentinel-mcp`, `mcp-sentinel` and `sentinel-proxy` were all
8
+ # unavailable on PyPI, so the published distribution is `sentinel-prox`.)
9
+ name = "sentinel-prox"
10
+ version = "0.1.0"
11
+ description = "Provenance-aware security proxy for AI agents: block prompt-injection actions at the MCP tool boundary."
12
+ requires-python = ">=3.11"
13
+ authors = [{ name = "Pali Krishna Harshith" }]
14
+ readme = "README.md"
15
+ license = { text = "MIT" }
16
+ keywords = [
17
+ "mcp", "model-context-protocol", "ai-security", "prompt-injection",
18
+ "ai-agents", "llm-security", "agentic-ai", "security-proxy",
19
+ "information-flow-control", "provenance",
20
+ ]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Intended Audience :: Developers",
24
+ "Intended Audience :: System Administrators",
25
+ "License :: OSI Approved :: MIT License",
26
+ "Operating System :: OS Independent",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.11",
29
+ "Programming Language :: Python :: 3.12",
30
+ "Topic :: Security",
31
+ "Topic :: Software Development :: Libraries :: Application Frameworks",
32
+ "Topic :: System :: Networking :: Monitoring",
33
+ "Typing :: Typed",
34
+ ]
35
+
36
+ # NOTE: keep `[project.urls]` (and every other sub-table) BELOW the plain keys of
37
+ # `[project]` — in TOML, a table header ends the parent table, so anything after it
38
+ # would be absorbed into the sub-table.
39
+
40
+ # Phase 0 §3: dependencies are runtime-resolved. pyproject.toml carries
41
+ # minimum-floor constraints; the exact installed versions are written to
42
+ # versions.lock and SDK facts to SDK_VERIFICATION.md. BUILD_SPEC.md is NOT
43
+ # mutated.
44
+ dependencies = [
45
+ # Azure / Foundry
46
+ "azure-ai-projects==2.0.0", # Foundry; AIProjectClient is THE entry point
47
+ "azure-identity>=1.17",
48
+ "azure-cosmos>=4.7",
49
+ "azure-monitor-opentelemetry>=1.6",
50
+
51
+ # MCP — official Python SDK (server + client)
52
+ "mcp>=1.2",
53
+
54
+ # OpenAI — exact pin written to versions.lock after resolve
55
+ "openai>=1.50",
56
+
57
+ # OpenTelemetry
58
+ "opentelemetry-api>=1.27",
59
+ "opentelemetry-sdk>=1.27",
60
+
61
+ # HTTP / SSE
62
+ "fastapi>=0.115",
63
+ "uvicorn[standard]>=0.32",
64
+ "sse-starlette>=2.1",
65
+ "httpx>=0.27", # real web_fetch tool + Content Safety REST client
66
+
67
+ # Models / config
68
+ "pydantic>=2.9,<3",
69
+ "pyyaml>=6.0",
70
+ ]
71
+
72
+ [project.urls]
73
+ Homepage = "https://github.com/Harshith029/Sentinel"
74
+ Repository = "https://github.com/Harshith029/Sentinel"
75
+ Issues = "https://github.com/Harshith029/Sentinel/issues"
76
+ Demo = "https://sentinel-i63x.onrender.com"
77
+
78
+ [project.scripts]
79
+ # The product surface: `sentinel init|check|scaffold|serve`.
80
+ sentinel = "sentinel.cli:main"
81
+
82
+ [project.optional-dependencies]
83
+ dev = [
84
+ "pytest>=8.3",
85
+ "pytest-asyncio>=0.24",
86
+ "httpx>=0.27",
87
+ "pre-commit>=3.8",
88
+ "ruff>=0.6",
89
+ "mypy>=1.11",
90
+ "types-PyYAML>=6.0", # stubs so `import yaml` type-checks under mypy --strict
91
+ ]
92
+
93
+ [tool.setuptools.packages.find]
94
+ where = ["src"]
95
+
96
+ [tool.setuptools.package-data]
97
+ # Ship the canonical authorization policy and trust config with the package so
98
+ # they are loadable via importlib.resources in both editable and wheel installs.
99
+ "sentinel" = ["py.typed"] # PEP 561: ship inline types to consumers
100
+ "sentinel.authorization" = ["policies/*.yaml"]
101
+ "sentinel.trust" = ["config.yaml"]
102
+ "sentinel.control" = ["static/*.html", "static/fonts/*.woff2"]
103
+
104
+ [tool.pytest.ini_options]
105
+ testpaths = ["tests"]
106
+ addopts = "-ra --strict-markers --strict-config"
107
+ asyncio_mode = "auto"
108
+ filterwarnings = [
109
+ "error",
110
+ # azure-* and opentelemetry occasionally emit deprecation noise we do not own
111
+ "ignore::DeprecationWarning:azure.*",
112
+ "ignore::DeprecationWarning:opentelemetry.*",
113
+ "ignore::PendingDeprecationWarning:azure.*",
114
+ # anyio/mcp in-memory streams are closed at GC time, which pytest surfaces as
115
+ # an *unraisable* ResourceWarning. Under "error" this intermittently fails the
116
+ # suite depending on GC timing (benign on Windows, fails on the Linux runner).
117
+ # It is a third-party cleanup artifact, not a defect in our code — ignore it
118
+ # like the azure/opentelemetry noise above.
119
+ "ignore::ResourceWarning",
120
+ "ignore::pytest.PytestUnraisableExceptionWarning",
121
+ ]
122
+
123
+ [tool.ruff]
124
+ line-length = 100
125
+ target-version = "py311"
126
+
127
+ [tool.ruff.lint]
128
+ select = ["E", "F", "I", "B", "UP", "ANN", "S"]
129
+ ignore = [
130
+ "ANN101", # missing self type
131
+ "ANN102", # missing cls type
132
+ "S101", # assert in tests is fine
133
+ ]
134
+
135
+ [tool.ruff.lint.per-file-ignores]
136
+ "tests/*" = ["S101", "ANN"]
137
+
138
+ [tool.mypy]
139
+ python_version = "3.11"
140
+ strict = true
141
+ plugins = ["pydantic.mypy"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ """SENTINEL — provenance-aware MCP security proxy.
2
+
3
+ See README.md for the architecture, threat model, and quick start.
4
+ """
5
+
6
+ __version__ = "0.1.0"
@@ -0,0 +1,11 @@
1
+ """Authorization engine (BUILD_SPEC §2B) — the policy backstop behind provenance.
2
+
3
+ Predicates are parsed into a TYPED condition AST (:mod:`ast`) by a hand-written
4
+ tokenizer/parser (:mod:`parser`) — never ``eval``. Policy is declarative,
5
+ versioned YAML validated at LOAD time (:mod:`policy`). The engine
6
+ (:mod:`engine`) resolves a proposed call against the policy with DENY-OVERRIDES
7
+ precedence (any matching deny short-circuits; unknown tools default-deny) and
8
+ emits a complete :class:`~sentinel.forensics.events.AuthorizationDecided`
9
+ decision trace.
10
+ """
11
+ from __future__ import annotations