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.
- sentinel_prox-0.1.0/LICENSE +21 -0
- sentinel_prox-0.1.0/PKG-INFO +258 -0
- sentinel_prox-0.1.0/README.md +208 -0
- sentinel_prox-0.1.0/pyproject.toml +141 -0
- sentinel_prox-0.1.0/setup.cfg +4 -0
- sentinel_prox-0.1.0/src/sentinel/__init__.py +6 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/__init__.py +11 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/ast.py +189 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/engine.py +177 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/parser.py +262 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/policies/default.yaml +45 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/policy.py +156 -0
- sentinel_prox-0.1.0/src/sentinel/authorization/registry.py +89 -0
- sentinel_prox-0.1.0/src/sentinel/catalogue.py +279 -0
- sentinel_prox-0.1.0/src/sentinel/classifier/__init__.py +19 -0
- sentinel_prox-0.1.0/src/sentinel/classifier/attack_classifier.py +167 -0
- sentinel_prox-0.1.0/src/sentinel/cli.py +285 -0
- sentinel_prox-0.1.0/src/sentinel/config.py +205 -0
- sentinel_prox-0.1.0/src/sentinel/control/__init__.py +12 -0
- sentinel_prox-0.1.0/src/sentinel/control/app.py +485 -0
- sentinel_prox-0.1.0/src/sentinel/control/capabilities.py +97 -0
- sentinel_prox-0.1.0/src/sentinel/control/events.py +102 -0
- sentinel_prox-0.1.0/src/sentinel/control/manager.py +502 -0
- sentinel_prox-0.1.0/src/sentinel/control/mcp_gateway.py +424 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-400.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-500.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-600.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/inter-700.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/jetbrainsmono-400.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/fonts/jetbrainsmono-600.woff2 +0 -0
- sentinel_prox-0.1.0/src/sentinel/control/static/index.html +659 -0
- sentinel_prox-0.1.0/src/sentinel/demo/__init__.py +16 -0
- sentinel_prox-0.1.0/src/sentinel/demo/driver.py +107 -0
- sentinel_prox-0.1.0/src/sentinel/demo/foundry.py +94 -0
- sentinel_prox-0.1.0/src/sentinel/demo/llm_driver.py +450 -0
- sentinel_prox-0.1.0/src/sentinel/demo/preflight.py +99 -0
- sentinel_prox-0.1.0/src/sentinel/demo/scenario.py +392 -0
- sentinel_prox-0.1.0/src/sentinel/demo/tool_servers.py +150 -0
- sentinel_prox-0.1.0/src/sentinel/demo/toolserver_main.py +35 -0
- sentinel_prox-0.1.0/src/sentinel/downstream.py +217 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/__init__.py +17 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/emitter.py +88 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/events.py +215 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/replay.py +124 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/span.py +93 -0
- sentinel_prox-0.1.0/src/sentinel/forensics/store.py +336 -0
- sentinel_prox-0.1.0/src/sentinel/labels.py +52 -0
- sentinel_prox-0.1.0/src/sentinel/mcp_proxy/__init__.py +10 -0
- sentinel_prox-0.1.0/src/sentinel/mcp_proxy/content.py +50 -0
- sentinel_prox-0.1.0/src/sentinel/mcp_proxy/proxy.py +326 -0
- sentinel_prox-0.1.0/src/sentinel/mcp_proxy/router.py +83 -0
- sentinel_prox-0.1.0/src/sentinel/mcp_proxy/skeleton.py +135 -0
- sentinel_prox-0.1.0/src/sentinel/provenance/__init__.py +17 -0
- sentinel_prox-0.1.0/src/sentinel/provenance/graph.py +138 -0
- sentinel_prox-0.1.0/src/sentinel/provenance/model.py +47 -0
- sentinel_prox-0.1.0/src/sentinel/provenance/sanitizer.py +171 -0
- sentinel_prox-0.1.0/src/sentinel/py.typed +0 -0
- sentinel_prox-0.1.0/src/sentinel/scaffold.py +167 -0
- sentinel_prox-0.1.0/src/sentinel/shield/__init__.py +12 -0
- sentinel_prox-0.1.0/src/sentinel/shield/input_shield.py +131 -0
- sentinel_prox-0.1.0/src/sentinel/toolservers/__init__.py +15 -0
- sentinel_prox-0.1.0/src/sentinel/toolservers/web_fetch.py +202 -0
- sentinel_prox-0.1.0/src/sentinel/tracing.py +117 -0
- sentinel_prox-0.1.0/src/sentinel/trust/__init__.py +15 -0
- sentinel_prox-0.1.0/src/sentinel/trust/config.py +59 -0
- sentinel_prox-0.1.0/src/sentinel/trust/config.yaml +19 -0
- sentinel_prox-0.1.0/src/sentinel/trust/scorer.py +304 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/PKG-INFO +258 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/SOURCES.txt +106 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/dependency_links.txt +1 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/entry_points.txt +2 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/requires.txt +23 -0
- sentinel_prox-0.1.0/src/sentinel_prox.egg-info/top_level.txt +1 -0
- sentinel_prox-0.1.0/tests/test_agent_default_driver.py +66 -0
- sentinel_prox-0.1.0/tests/test_authorization_ast.py +75 -0
- sentinel_prox-0.1.0/tests/test_authorization_engine.py +169 -0
- sentinel_prox-0.1.0/tests/test_authorization_parser.py +158 -0
- sentinel_prox-0.1.0/tests/test_authorization_policy.py +101 -0
- sentinel_prox-0.1.0/tests/test_azure_wiring.py +52 -0
- sentinel_prox-0.1.0/tests/test_catalogue_enforcement.py +103 -0
- sentinel_prox-0.1.0/tests/test_catalogue_integrity.py +153 -0
- sentinel_prox-0.1.0/tests/test_catalogue_monitor.py +93 -0
- sentinel_prox-0.1.0/tests/test_cli.py +145 -0
- sentinel_prox-0.1.0/tests/test_downstream_discovery.py +148 -0
- sentinel_prox-0.1.0/tests/test_downstream_endpoint.py +80 -0
- sentinel_prox-0.1.0/tests/test_forensics_emitter.py +97 -0
- sentinel_prox-0.1.0/tests/test_forensics_events.py +124 -0
- sentinel_prox-0.1.0/tests/test_forensics_replay.py +123 -0
- sentinel_prox-0.1.0/tests/test_forensics_span.py +97 -0
- sentinel_prox-0.1.0/tests/test_forensics_store.py +195 -0
- sentinel_prox-0.1.0/tests/test_mcp_skeleton.py +87 -0
- sentinel_prox-0.1.0/tests/test_phase0_gitleaks.py +127 -0
- sentinel_prox-0.1.0/tests/test_phase0_smoke.py +194 -0
- sentinel_prox-0.1.0/tests/test_phase10_llm_agent.py +196 -0
- sentinel_prox-0.1.0/tests/test_phase2_dod.py +346 -0
- sentinel_prox-0.1.0/tests/test_phase3_dod.py +187 -0
- sentinel_prox-0.1.0/tests/test_phase4_dod.py +278 -0
- sentinel_prox-0.1.0/tests/test_phase5_hero.py +190 -0
- sentinel_prox-0.1.0/tests/test_phase6_control_plane.py +458 -0
- sentinel_prox-0.1.0/tests/test_phase7_dashboard.py +59 -0
- sentinel_prox-0.1.0/tests/test_phase8_enterprise.py +179 -0
- sentinel_prox-0.1.0/tests/test_phase9_mcp_gateway.py +340 -0
- sentinel_prox-0.1.0/tests/test_policy_scaffold.py +123 -0
- sentinel_prox-0.1.0/tests/test_provenance_graph.py +93 -0
- sentinel_prox-0.1.0/tests/test_real_web_pipeline.py +56 -0
- sentinel_prox-0.1.0/tests/test_sanitizer.py +88 -0
- sentinel_prox-0.1.0/tests/test_toolservers_web_fetch.py +128 -0
- 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,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
|