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.
- sieve_layer-0.1.0/.gitignore +49 -0
- sieve_layer-0.1.0/CHANGELOG.md +10 -0
- sieve_layer-0.1.0/CONTRIBUTING.md +27 -0
- sieve_layer-0.1.0/LICENSE +21 -0
- sieve_layer-0.1.0/Makefile +12 -0
- sieve_layer-0.1.0/PKG-INFO +387 -0
- sieve_layer-0.1.0/README.md +355 -0
- sieve_layer-0.1.0/docs/ARCHITECTURE.md +72 -0
- sieve_layer-0.1.0/docs/POLICY_SCHEMA.md +80 -0
- sieve_layer-0.1.0/examples/policy.yaml +41 -0
- sieve_layer-0.1.0/examples/tamper_demo.py +51 -0
- sieve_layer-0.1.0/examples/toy_agent.py +269 -0
- sieve_layer-0.1.0/pyproject.toml +78 -0
- sieve_layer-0.1.0/src/sieve/__init__.py +35 -0
- sieve_layer-0.1.0/src/sieve/approval/__init__.py +4 -0
- sieve_layer-0.1.0/src/sieve/approval/base.py +32 -0
- sieve_layer-0.1.0/src/sieve/approval/cli.py +64 -0
- sieve_layer-0.1.0/src/sieve/audit/__init__.py +0 -0
- sieve_layer-0.1.0/src/sieve/audit/log.py +274 -0
- sieve_layer-0.1.0/src/sieve/audit/models.py +29 -0
- sieve_layer-0.1.0/src/sieve/cli.py +129 -0
- sieve_layer-0.1.0/src/sieve/config.py +55 -0
- sieve_layer-0.1.0/src/sieve/core/__init__.py +0 -0
- sieve_layer-0.1.0/src/sieve/core/cost.py +108 -0
- sieve_layer-0.1.0/src/sieve/core/decision.py +50 -0
- sieve_layer-0.1.0/src/sieve/core/errors.py +46 -0
- sieve_layer-0.1.0/src/sieve/core/interceptor.py +271 -0
- sieve_layer-0.1.0/src/sieve/core/similarity.py +161 -0
- sieve_layer-0.1.0/src/sieve/decorator.py +71 -0
- sieve_layer-0.1.0/src/sieve/integrations/__init__.py +0 -0
- sieve_layer-0.1.0/src/sieve/integrations/langchain.py +191 -0
- sieve_layer-0.1.0/src/sieve/integrations/mcp.py +61 -0
- sieve_layer-0.1.0/src/sieve/policy/__init__.py +0 -0
- sieve_layer-0.1.0/src/sieve/policy/engine.py +59 -0
- sieve_layer-0.1.0/src/sieve/policy/loader.py +227 -0
- sieve_layer-0.1.0/src/sieve/policy/models.py +55 -0
- sieve_layer-0.1.0/tests/__init__.py +0 -0
- sieve_layer-0.1.0/tests/test_approval.py +38 -0
- sieve_layer-0.1.0/tests/test_audit_chain.py +243 -0
- sieve_layer-0.1.0/tests/test_cli.py +98 -0
- sieve_layer-0.1.0/tests/test_cost_tracking.py +118 -0
- sieve_layer-0.1.0/tests/test_decorator.py +78 -0
- sieve_layer-0.1.0/tests/test_interceptor.py +268 -0
- sieve_layer-0.1.0/tests/test_langchain_integration.py +117 -0
- sieve_layer-0.1.0/tests/test_mcp_integration.py +79 -0
- sieve_layer-0.1.0/tests/test_policy_engine.py +273 -0
- 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.
|