sieve-layer 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 (47) hide show
  1. sieve_layer-0.1.0/.gitignore +49 -0
  2. sieve_layer-0.1.0/CHANGELOG.md +10 -0
  3. sieve_layer-0.1.0/CONTRIBUTING.md +27 -0
  4. sieve_layer-0.1.0/LICENSE +21 -0
  5. sieve_layer-0.1.0/Makefile +12 -0
  6. sieve_layer-0.1.0/PKG-INFO +387 -0
  7. sieve_layer-0.1.0/README.md +355 -0
  8. sieve_layer-0.1.0/docs/ARCHITECTURE.md +72 -0
  9. sieve_layer-0.1.0/docs/POLICY_SCHEMA.md +80 -0
  10. sieve_layer-0.1.0/examples/policy.yaml +41 -0
  11. sieve_layer-0.1.0/examples/tamper_demo.py +51 -0
  12. sieve_layer-0.1.0/examples/toy_agent.py +269 -0
  13. sieve_layer-0.1.0/pyproject.toml +78 -0
  14. sieve_layer-0.1.0/src/sieve/__init__.py +35 -0
  15. sieve_layer-0.1.0/src/sieve/approval/__init__.py +4 -0
  16. sieve_layer-0.1.0/src/sieve/approval/base.py +32 -0
  17. sieve_layer-0.1.0/src/sieve/approval/cli.py +64 -0
  18. sieve_layer-0.1.0/src/sieve/audit/__init__.py +0 -0
  19. sieve_layer-0.1.0/src/sieve/audit/log.py +274 -0
  20. sieve_layer-0.1.0/src/sieve/audit/models.py +29 -0
  21. sieve_layer-0.1.0/src/sieve/cli.py +129 -0
  22. sieve_layer-0.1.0/src/sieve/config.py +55 -0
  23. sieve_layer-0.1.0/src/sieve/core/__init__.py +0 -0
  24. sieve_layer-0.1.0/src/sieve/core/cost.py +108 -0
  25. sieve_layer-0.1.0/src/sieve/core/decision.py +50 -0
  26. sieve_layer-0.1.0/src/sieve/core/errors.py +46 -0
  27. sieve_layer-0.1.0/src/sieve/core/interceptor.py +271 -0
  28. sieve_layer-0.1.0/src/sieve/core/similarity.py +161 -0
  29. sieve_layer-0.1.0/src/sieve/decorator.py +71 -0
  30. sieve_layer-0.1.0/src/sieve/integrations/__init__.py +0 -0
  31. sieve_layer-0.1.0/src/sieve/integrations/langchain.py +191 -0
  32. sieve_layer-0.1.0/src/sieve/integrations/mcp.py +61 -0
  33. sieve_layer-0.1.0/src/sieve/policy/__init__.py +0 -0
  34. sieve_layer-0.1.0/src/sieve/policy/engine.py +59 -0
  35. sieve_layer-0.1.0/src/sieve/policy/loader.py +227 -0
  36. sieve_layer-0.1.0/src/sieve/policy/models.py +55 -0
  37. sieve_layer-0.1.0/tests/__init__.py +0 -0
  38. sieve_layer-0.1.0/tests/test_approval.py +38 -0
  39. sieve_layer-0.1.0/tests/test_audit_chain.py +243 -0
  40. sieve_layer-0.1.0/tests/test_cli.py +98 -0
  41. sieve_layer-0.1.0/tests/test_cost_tracking.py +118 -0
  42. sieve_layer-0.1.0/tests/test_decorator.py +78 -0
  43. sieve_layer-0.1.0/tests/test_interceptor.py +268 -0
  44. sieve_layer-0.1.0/tests/test_langchain_integration.py +117 -0
  45. sieve_layer-0.1.0/tests/test_mcp_integration.py +79 -0
  46. sieve_layer-0.1.0/tests/test_policy_engine.py +273 -0
  47. sieve_layer-0.1.0/tests/test_similarity_circuit_breaker.py +118 -0
@@ -0,0 +1,49 @@
1
+ # Python bytecode and caches
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ .pytest_cache/
6
+ .mypy_cache/
7
+ .ruff_cache/
8
+ .tox/
9
+ .nox/
10
+
11
+ # Coverage and test output
12
+ .coverage
13
+ .coverage.*
14
+ htmlcov/
15
+ coverage.xml
16
+
17
+ # Build and packaging artifacts
18
+ build/
19
+ dist/
20
+ *.egg-info/
21
+ *.egg
22
+
23
+ # Virtual environments
24
+ .venv/
25
+ venv/
26
+ env/
27
+
28
+ # Local configuration and secrets
29
+ .env
30
+ .env.*
31
+ !.env.example
32
+
33
+ # Local databases and generated demo data
34
+ *.db
35
+ *.sqlite
36
+ *.sqlite3
37
+
38
+ # Installer and tool caches
39
+ .pip-cache/
40
+ .uv-cache/
41
+
42
+ # OS and editor files
43
+ .DS_Store
44
+ Thumbs.db
45
+ .idea/
46
+ .vscode/
47
+ *.swp
48
+ *.swo
49
+
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - Initial Release
4
+
5
+ - Added policy-based interception for guarded functions, LangChain tools, and FastMCP middleware.
6
+ - Added SQLite-backed tamper-evident audit logging with SHA-256 hash chaining.
7
+ - Added CLI audit inspection via `sieve tail` and `sieve verify`.
8
+ - Added similarity circuit breaker support for repeated/jittered tool-call loops using FastEmbed/ONNX Runtime.
9
+ - Replaced the torch-backed sentence-transformers similarity dependency with FastEmbed/ONNX Runtime to avoid CVE-2025-3000 in optional similarity installs.
10
+ - Added LangChain token cost tracking callback with task-level cost limits.
@@ -0,0 +1,27 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve Sieve.
4
+
5
+ ## Local setup
6
+
7
+ ```bash
8
+ python -m pip install -e ".[langchain,similarity,dev]"
9
+ python -m pytest -q
10
+ ```
11
+
12
+ If you are not using the repo's virtual environment, make sure the interpreter
13
+ you run has the editable install and extras above available.
14
+
15
+ ## What to send
16
+
17
+ - Keep pull requests focused and easy to review.
18
+ - Add or update tests when behavior changes.
19
+ - Docs, demo polish, and developer experience improvements are welcome.
20
+
21
+ ## Before opening a PR
22
+
23
+ ```bash
24
+ python -m pytest -q
25
+ python examples/toy_agent.py --loop-demo
26
+ make tamper-demo
27
+ ```
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
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,12 @@
1
+ PYTHON ?= $(if $(wildcard venv/bin/python),venv/bin/python,python)
2
+
3
+ .PHONY: test audit-deps tamper-demo
4
+
5
+ test:
6
+ $(PYTHON) -m pytest --cov=sieve --cov-report=term-missing
7
+
8
+ audit-deps:
9
+ $(PYTHON) -m pip_audit
10
+
11
+ tamper-demo:
12
+ $(PYTHON) examples/tamper_demo.py
@@ -0,0 +1,387 @@
1
+ Metadata-Version: 2.4
2
+ Name: sieve-layer
3
+ Version: 0.1.0
4
+ Summary: Drop-in middleware that intercepts every tool call your AI agents make, blocks unsafe actions, and logs a tamper-proof audit trail.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Keywords: ai-safety,audit,langchain,mcp,middleware,policy
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Requires-Python: >=3.10
16
+ Requires-Dist: pyyaml>=6.0
17
+ Provides-Extra: dev
18
+ Requires-Dist: fastmcp>=2.0; extra == 'dev'
19
+ Requires-Dist: langchain-community>=0.3; extra == 'dev'
20
+ Requires-Dist: langchain-core>=0.3; extra == 'dev'
21
+ Requires-Dist: pip-audit>=2.7; extra == 'dev'
22
+ Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
23
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
24
+ Requires-Dist: pytest>=8.0; extra == 'dev'
25
+ Provides-Extra: langchain
26
+ Requires-Dist: langchain-core>=0.3; extra == 'langchain'
27
+ Provides-Extra: mcp
28
+ Requires-Dist: fastmcp>=2.0; extra == 'mcp'
29
+ Provides-Extra: similarity
30
+ Requires-Dist: fastembed<1,>=0.8; extra == 'similarity'
31
+ Description-Content-Type: text/markdown
32
+
33
+ # Sieve
34
+
35
+ **Middleware for AI agent tool calls with policy checks, approval prompts, loop detection, and tamper-evident audit logs.**
36
+
37
+ ---
38
+
39
+ ## Quickstart
40
+
41
+ ```bash
42
+ python -m pip install sieve-layer[langchain,similarity]
43
+ cp examples/policy.yaml policy.yaml
44
+ python examples/toy_agent.py
45
+ sieve verify examples/demo_audit.db
46
+ ```
47
+
48
+ For contributors working from this repository, use the editable install:
49
+
50
+ ```bash
51
+ python -m pip install -e ".[langchain,similarity]"
52
+ cp examples/policy.yaml policy.yaml
53
+ python examples/toy_agent.py
54
+ sieve verify examples/demo_audit.db
55
+ ```
56
+
57
+ Use the same Python interpreter for installation and demo commands. If you use
58
+ a different interpreter, install the package and extras there first.
59
+
60
+ When the demo prompts for the DELETE query, type `n`. You should see the call
61
+ blocked, then a clean audit verification at the end.
62
+
63
+ ### 1. Install the repo
64
+
65
+ ```bash
66
+ python -m pip install -e ".[langchain,similarity]"
67
+ ```
68
+
69
+ ### 2. Copy the example policy
70
+
71
+ ```bash
72
+ cp examples/policy.yaml policy.yaml
73
+ ```
74
+
75
+ If you want to inspect or customize it first, this is the shipped example:
76
+
77
+ ```yaml
78
+ # policy.yaml
79
+ version: 1
80
+ default: deny # fail-closed: anything not explicitly allowed is blocked
81
+
82
+ rules:
83
+ - name: allow-selects
84
+ match:
85
+ tool: "postgres_query" # fnmatch glob on the tool name
86
+ args:
87
+ query: "(?i)^\\s*select" # regex matched against the argument value
88
+ action: allow
89
+
90
+ - name: guard-mutations
91
+ match:
92
+ tool: "postgres_query"
93
+ args:
94
+ query: "(?i)\\b(delete|drop|update|truncate|insert)\\b"
95
+ action: require_approval # blocks and prompts operator at the terminal
96
+
97
+ circuit_breaker:
98
+ - tool: "*"
99
+ max_similar_calls: 3
100
+ window_seconds: 60
101
+ similarity_threshold: 0.92
102
+ action: deny
103
+ ```
104
+
105
+ ### 3. Configure Sieve at startup
106
+
107
+ ```python
108
+ import sieve
109
+
110
+ sieve.configure(
111
+ policy_path="policy.yaml",
112
+ db_path="audit.db", # SQLite file for the tamper-evident audit log
113
+ )
114
+ ```
115
+
116
+ ### 4. Guard plain functions or tools
117
+
118
+ #### Plain functions
119
+
120
+ ```python
121
+ @sieve.guard()
122
+ def postgres_query(query: str) -> str:
123
+ # only runs if policy allows or operator approves
124
+ return run_sql(query)
125
+
126
+ result = postgres_query("SELECT * FROM users") # allowed
127
+ postgres_query("DELETE FROM users") # blocked or prompts for approval
128
+ ```
129
+
130
+ #### LangChain
131
+
132
+ ```python
133
+ from langchain_core.tools import BaseTool
134
+ from sieve.integrations.langchain import wrap_tool
135
+
136
+ class MyPostgresTool(BaseTool):
137
+ name = "postgres_query"
138
+ description = "Execute SQL"
139
+
140
+ def _run(self, query: str) -> str:
141
+ return run_sql(query)
142
+
143
+ safe_tool = wrap_tool(MyPostgresTool())
144
+ # safe_tool.invoke({"query": "DELETE ..."}) will trigger Sieve before execution
145
+ ```
146
+
147
+ #### FastMCP
148
+
149
+ ```python
150
+ from fastmcp import FastMCP
151
+ from sieve.integrations.mcp import SieveMiddleware
152
+
153
+ mcp = FastMCP("my-server")
154
+ mcp.add_middleware(SieveMiddleware())
155
+
156
+ @mcp.tool()
157
+ def postgres_query(sql: str) -> str:
158
+ return run_sql(sql)
159
+ ```
160
+
161
+ ### 5. Run the demos
162
+
163
+ ```bash
164
+ python examples/toy_agent.py
165
+ python examples/toy_agent.py --loop-demo
166
+ ```
167
+
168
+ This runs a toy LangChain agent against a local SQLite database. A `SELECT` passes through immediately; a `DELETE` triggers the approval prompt:
169
+
170
+ ```
171
+ ============================================================
172
+ [SIEVE] Approval required for tool call:
173
+ Tool : postgres_query
174
+ Args : {
175
+ "query": "DELETE FROM users WHERE id = 1"
176
+ }
177
+ ============================================================
178
+ Approve this call? [y/N]:
179
+ ```
180
+
181
+ Type `n` to block it. The `PolicyViolation` is raised and the DELETE never executes.
182
+
183
+ The loop demo runs a deterministic toy search agent that retries the same query
184
+ with tiny whitespace changes. Sieve allows the first two calls, then emits a
185
+ `CIRCUIT_BREAK` audit entry and halts the 3rd jittered repeat.
186
+
187
+ ### 6. Inspect the audit log
188
+
189
+ ```bash
190
+ sieve tail examples/demo_audit.db # print recent entries
191
+ sieve verify examples/demo_audit.db # verify the hash chain is intact
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Policy schema
197
+
198
+ See [docs/POLICY_SCHEMA.md](docs/POLICY_SCHEMA.md) for the full field-by-field
199
+ schema reference.
200
+
201
+ | Field | Type | Description |
202
+ |-------|------|-------------|
203
+ | `version` | int | Must be `1` |
204
+ | `default` | string | Action when no rule matches: `allow`, `deny`, or `require_approval` |
205
+ | `rules` | list | Ordered list of rules; first match wins |
206
+ | `rules[].name` | string | Human-readable identifier used in audit log |
207
+ | `rules[].match.tool` | string | fnmatch glob matched against the tool name |
208
+ | `rules[].match.args` | mapping | `arg_name: regex`; all patterns must match |
209
+ | `rules[].action` | string | `allow`, `deny`, or `require_approval` |
210
+ | `circuit_breaker[]` | list | Optional embedding-similarity repeat detectors |
211
+ | `circuit_breaker[].tool` | string | fnmatch glob for tools covered by this breaker |
212
+ | `circuit_breaker[].max_similar_calls` | int | Number of similar calls, including current call, before firing |
213
+ | `circuit_breaker[].window_seconds` | number | Recent-call window to inspect |
214
+ | `circuit_breaker[].similarity_threshold` | number | Cosine threshold for argument embeddings |
215
+ | `circuit_breaker[].action` | string | `deny` or `require_approval` |
216
+ | `max_cost_per_task` | number | Optional cumulative LLM cost limit for one configured runtime |
217
+
218
+ **Matching semantics:**
219
+ - Tool name uses `fnmatch` glob: `postgres*` matches `postgres_query`, `postgres_write`, etc.
220
+ - Argument patterns are full Python `re.search()` regexes applied to `str(value)`.
221
+ - All `args` patterns must match for a rule to fire (logical AND).
222
+ - Rules are evaluated top-to-bottom; the first match wins.
223
+ - Circuit breakers compare embedded, canonicalized args only within the same tool name.
224
+ - Cost tracking requires attaching a Sieve callback to the LLM/agent and providing model prices.
225
+
226
+ ---
227
+
228
+ ## Tamper-evident audit chain
229
+
230
+ Every tool call decision is written to a SQLite database as a hash-chained log:
231
+
232
+ ```
233
+ entry_hash = sha256(prev_hash + canonical_json(timestamp, tool_name, tool_args, outcome, ...))
234
+ ```
235
+
236
+ The first entry uses `prev_hash = "0" * 64` (genesis). Sieve stores full
237
+ 64-character SHA-256 hashes. Any retrospective modification, including editing
238
+ an outcome, deleting a row, or rewriting args, breaks the chain and is detected by
239
+ `sieve verify`.
240
+
241
+ New audit databases are created private to the current user, and Sieve removes
242
+ group/world permission bits from existing audit DB files when opening them.
243
+ Likely secrets in tool args and audit metadata are redacted before logging.
244
+
245
+ ```bash
246
+ $ sieve verify audit.db
247
+ OK: Chain intact - 42 entries verified.
248
+
249
+ # After tampering with a row:
250
+ $ sieve verify audit.db
251
+ TAMPER DETECTED: Hash mismatch at id=7: stored 'a1b2c3...' != computed 'deadbe...'
252
+ ```
253
+
254
+ Reproduce the tamper proof locally with one command:
255
+
256
+ ```bash
257
+ make tamper-demo
258
+ ```
259
+
260
+ If you installed Sieve into a non-default interpreter, run
261
+ `PYTHON=/path/to/python make tamper-demo` so the target uses the same environment.
262
+
263
+ Expected ending:
264
+
265
+ ```text
266
+ [3/3] Verifying the audit log...
267
+ TAMPER DETECTED: Hash mismatch at id=1: ...
268
+ ```
269
+
270
+ ## Error handling
271
+
272
+ | Exception | When raised |
273
+ |-----------|-------------|
274
+ | `sieve.PolicyViolation` | Tool call denied by policy or operator |
275
+ | `sieve.ApprovalDenied` | `require_approval` rule fired but operator said no |
276
+ | `sieve.core.errors.PolicyLoadError` | Policy YAML is missing, malformed, or fails validation |
277
+ | `RuntimeError` | `sieve.configure()` was not called before a guarded tool ran |
278
+
279
+ ---
280
+
281
+ ## Architecture and security notes
282
+
283
+ Read [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) for the interception flow,
284
+ what is logged, the hash-chain design, and the threat model.
285
+
286
+ Before publishing a release, run:
287
+
288
+ ```bash
289
+ python -m pytest --cov=sieve --cov-report=term-missing
290
+ python -m pip_audit
291
+ ```
292
+
293
+ ## Custom approval handler
294
+
295
+ The CLI prompt is the default. For headless agents, CI, or tests, use a
296
+ non-interactive handler:
297
+
298
+ ```python
299
+ import sieve
300
+ from sieve.approval import StaticApprovalHandler
301
+
302
+ sieve.configure(
303
+ policy_path="policy.yaml",
304
+ approval_handler=StaticApprovalHandler(approve=False), # auto-deny
305
+ )
306
+ ```
307
+
308
+ You can also make the terminal prompt auto-deny or auto-approve after a timeout:
309
+
310
+ ```python
311
+ from sieve.approval import CLIApprovalHandler
312
+
313
+ sieve.configure(
314
+ policy_path="policy.yaml",
315
+ approval_handler=CLIApprovalHandler(timeout_seconds=30, timeout_default=False),
316
+ )
317
+ ```
318
+
319
+ Implement `ApprovalHandler` to connect Sieve to another approval system:
320
+
321
+ ```python
322
+ from sieve.approval.base import ApprovalHandler
323
+ from sieve.core.decision import ToolCall
324
+
325
+ class QueueApprovalHandler:
326
+ def request(self, call: ToolCall) -> bool:
327
+ # Send the request to your approval system and return True or False.
328
+ ...
329
+
330
+ sieve.configure(policy_path="policy.yaml", approval_handler=QueueApprovalHandler())
331
+ ```
332
+
333
+ ## Cost tracking
334
+
335
+ Attach `SieveCostCallback` to LangChain LLMs or agents and provide prices for the
336
+ models you want enforced. Unknown model names are tracked as token usage but do
337
+ not count toward dollar limits.
338
+
339
+ ```python
340
+ from sieve.integrations.langchain import SieveCostCallback
341
+
342
+ cost_callback = SieveCostCallback(
343
+ {
344
+ "example-model": {
345
+ "input_per_1k": 0.01,
346
+ "output_per_1k": 0.03,
347
+ }
348
+ }
349
+ )
350
+ ```
351
+
352
+ ---
353
+
354
+ ## Project structure
355
+
356
+ ```
357
+ src/sieve/
358
+ ├── __init__.py # public API: configure, guard, PolicyViolation, ApprovalDenied
359
+ ├── config.py # SieveConfig, configure(), get_interceptor()
360
+ ├── decorator.py # @sieve.guard() for plain sync/async functions
361
+ ├── cli.py # sieve verify / sieve tail
362
+ ├── core/
363
+ │ ├── decision.py # ToolCall, Outcome, Decision dataclasses
364
+ │ ├── errors.py # PolicyViolation, ApprovalDenied, PolicyLoadError
365
+ │ └── interceptor.py # evaluate, approve, audit, execute pipeline
366
+ ├── policy/
367
+ │ ├── models.py # Rule, Match, ArgMatch, Policy
368
+ │ ├── loader.py # load_policy(path), YAML parse and validation
369
+ │ └── engine.py # PolicyEngine.evaluate(), fnmatch and regex matching
370
+ ├── audit/
371
+ │ ├── models.py # AuditEntry dataclass
372
+ │ └── log.py # SQLite hash-chained audit log
373
+ ├── approval/
374
+ │ ├── base.py # ApprovalHandler protocol
375
+ │ └── cli.py # interactive y/N approval handler, default deny
376
+ └── integrations/
377
+ ├── langchain.py # wrap_tool(BaseTool), lazy import
378
+ └── mcp.py # SieveMiddleware, lazy import fastmcp
379
+ ```
380
+
381
+ ---
382
+
383
+ ## License
384
+
385
+ MIT
386
+
387
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for local setup, tests, and PR expectations.