trustwarden 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.
- trustwarden-0.1.0/.cursor/mcp.json +19 -0
- trustwarden-0.1.0/.cursor/rules/efficiency.mdc +20 -0
- trustwarden-0.1.0/.cursor/rules/engineering.mdc +30 -0
- trustwarden-0.1.0/.env.example +2 -0
- trustwarden-0.1.0/.gitignore +38 -0
- trustwarden-0.1.0/.python-version +1 -0
- trustwarden-0.1.0/AGENTS.md +50 -0
- trustwarden-0.1.0/PKG-INFO +256 -0
- trustwarden-0.1.0/README.md +238 -0
- trustwarden-0.1.0/examples/basic_usage.py +37 -0
- trustwarden-0.1.0/examples/custom_policy.py +39 -0
- trustwarden-0.1.0/examples/logging_hooks.py +52 -0
- trustwarden-0.1.0/examples/wrap_registry.py +46 -0
- trustwarden-0.1.0/frontend/public/favicon.svg +6 -0
- trustwarden-0.1.0/frontend/public/logo.svg +6 -0
- trustwarden-0.1.0/kaisen-manifest.json +106 -0
- trustwarden-0.1.0/policies/default.json +68 -0
- trustwarden-0.1.0/pyproject.toml +64 -0
- trustwarden-0.1.0/src/trustwarden/__init__.py +138 -0
- trustwarden-0.1.0/src/trustwarden/adapters.py +186 -0
- trustwarden-0.1.0/src/trustwarden/config.py +32 -0
- trustwarden-0.1.0/src/trustwarden/decisions.py +164 -0
- trustwarden-0.1.0/src/trustwarden/events.py +283 -0
- trustwarden-0.1.0/src/trustwarden/exceptions.py +83 -0
- trustwarden-0.1.0/src/trustwarden/labels.py +275 -0
- trustwarden-0.1.0/src/trustwarden/llm_review.py +187 -0
- trustwarden-0.1.0/src/trustwarden/marking.py +159 -0
- trustwarden-0.1.0/src/trustwarden/policy.py +513 -0
- trustwarden-0.1.0/src/trustwarden/py.typed +0 -0
- trustwarden-0.1.0/src/trustwarden/redaction.py +96 -0
- trustwarden-0.1.0/src/trustwarden/safety.py +189 -0
- trustwarden-0.1.0/src/trustwarden/simulator.py +346 -0
- trustwarden-0.1.0/src/trustwarden/wrap.py +337 -0
- trustwarden-0.1.0/tests/test_adapters_and_policy_format.py +95 -0
- trustwarden-0.1.0/tests/test_config_and_redaction.py +40 -0
- trustwarden-0.1.0/tests/test_doctests.py +39 -0
- trustwarden-0.1.0/tests/test_events.py +121 -0
- trustwarden-0.1.0/tests/test_labels.py +82 -0
- trustwarden-0.1.0/tests/test_llm_review.py +163 -0
- trustwarden-0.1.0/tests/test_policy.py +216 -0
- trustwarden-0.1.0/tests/test_safety.py +213 -0
- trustwarden-0.1.0/tests/test_simulator.py +86 -0
- trustwarden-0.1.0/tests/test_wrap.py +144 -0
- trustwarden-0.1.0/uv.lock +381 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Work efficiently with few tokens
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Efficiency
|
|
7
|
+
|
|
8
|
+
- Read only the files you need; search before opening; never print whole large files.
|
|
9
|
+
- Make focused edits; do not rewrite files that only need a small change.
|
|
10
|
+
- Prefer established libraries and generators over writing boilerplate by hand.
|
|
11
|
+
- Look up current library APIs with the Context7 MCP server instead of guessing.
|
|
12
|
+
- Verify by running the commands, not by reasoning about them.
|
|
13
|
+
- Never run anything that does not exit on its own in the foreground (`next start`,
|
|
14
|
+
`npm run dev`, `uvicorn`, `vite`, watch modes): it blocks you until your time runs out.
|
|
15
|
+
To try a server, start it, check it and stop it in ONE command, with a time limit:
|
|
16
|
+
`timeout 120 npx next start -p 3999 > /tmp/server.log 2>&1 & pid=$!; for i in $(seq 40);
|
|
17
|
+
do curl -sf -m 3 http://127.0.0.1:3999/api/health && break; sleep 1; done; <your curl
|
|
18
|
+
checks>; kill $pid`. Use port 3999, never 8765 (the Factory's own check uses it). Run
|
|
19
|
+
tests once, never in watch mode (`vitest run`, `CI=1`).
|
|
20
|
+
- Keep your final summary short: what changed and what was verified.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Kaisen Factory engineering standards
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Engineering standards
|
|
7
|
+
|
|
8
|
+
- Production quality, not a prototype: every feature works end to end (UI, API, database),
|
|
9
|
+
with validation, error handling, loading and empty states.
|
|
10
|
+
- Layout: `frontend/` (the web app) and the backend API served under `/api` (see AGENTS.md).
|
|
11
|
+
Keep modules small and named by what they do. Shared types between layers where the stack
|
|
12
|
+
allows it.
|
|
13
|
+
- Data: model the domain properly (relations, indexes, timestamps, ownership per user/team).
|
|
14
|
+
Seed realistic demo data so the product is meaningful on first login.
|
|
15
|
+
- Serverless startup must be fast (the backend runs as a function that cold-starts): never
|
|
16
|
+
run a chain of database calls before serving. Create indexes concurrently (e.g.
|
|
17
|
+
`asyncio.gather` / `Promise.all`) and at most once per process, and keep `/api/health`
|
|
18
|
+
independent of index creation. No work at import time beyond building the app.
|
|
19
|
+
- Authentication is mandatory: every application has accounts with JWT authentication
|
|
20
|
+
(sign-up, sign-in, sign-out, the current user; tokens signed with `JWT_SECRET`, with an
|
|
21
|
+
expiry), and every non-public endpoint and page requires a valid token.
|
|
22
|
+
- Security: hash passwords (bcrypt/argon2), sign tokens with `JWT_SECRET`, check authorisation
|
|
23
|
+
on every endpoint (users only see their own or their team's data), validate all input,
|
|
24
|
+
never log or return secrets, never commit `.env` (only `.env.example` with names).
|
|
25
|
+
- Configuration comes only from environment variables (names in AGENTS.md); never hardcode
|
|
26
|
+
their values.
|
|
27
|
+
- Tests: unit tests for logic and API tests for every endpoint, including auth failures.
|
|
28
|
+
Never delete or weaken tests to make them pass.
|
|
29
|
+
- Before finishing a task, run the install, lint, typecheck, test and build commands from
|
|
30
|
+
AGENTS.md and fix what fails.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Environment and secrets
|
|
2
|
+
.env
|
|
3
|
+
.env.*
|
|
4
|
+
!.env.example
|
|
5
|
+
|
|
6
|
+
# Python
|
|
7
|
+
__pycache__/
|
|
8
|
+
*.py[cod]
|
|
9
|
+
*$py.class
|
|
10
|
+
*.so
|
|
11
|
+
.Python
|
|
12
|
+
build/
|
|
13
|
+
dist/
|
|
14
|
+
wheels/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
.eggs/
|
|
17
|
+
*.egg
|
|
18
|
+
|
|
19
|
+
# Virtual environments
|
|
20
|
+
.venv/
|
|
21
|
+
venv/
|
|
22
|
+
ENV/
|
|
23
|
+
|
|
24
|
+
# Tooling
|
|
25
|
+
.mypy_cache/
|
|
26
|
+
.ruff_cache/
|
|
27
|
+
.pytest_cache/
|
|
28
|
+
.coverage
|
|
29
|
+
htmlcov/
|
|
30
|
+
.tox/
|
|
31
|
+
.nox/
|
|
32
|
+
.cache/
|
|
33
|
+
|
|
34
|
+
# Editors / OS
|
|
35
|
+
.idea/
|
|
36
|
+
.vscode/
|
|
37
|
+
*.swp
|
|
38
|
+
.DS_Store
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.11
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# TrustWarden — Guarding your tools with certainty.
|
|
2
|
+
|
|
3
|
+
Built autonomously by Kaisen Factory. This file and `.cursor/rules` are generated by the Factory; keep them. `README.md` is the application's documentation, written in the plan's documentation task and kept current by every change; the Factory maintains the part between `<!-- kaisen-factory:live -->` markers (live URLs, owner account).
|
|
4
|
+
|
|
5
|
+
## Product
|
|
6
|
+
A production-ready Python security library that propagates trust labels from tool outputs into subsequent calls, evaluates declared policies before sensitive tools run, and provides a runnable attack simulator to test enforcement.
|
|
7
|
+
|
|
8
|
+
- Domain: AI agent security and developer tools
|
|
9
|
+
- Core: Track trust and sensitivity labels across wrapped Python tool calls, then evaluate policy before sensitive actions execute.
|
|
10
|
+
- Features (all of them must be fully implemented):
|
|
11
|
+
- Typed decorator and adapter API for wrapping Python tools without requiring a specific agent framework.
|
|
12
|
+
- Trust-label propagation for tool outputs, with explicit APIs for marking user input, trusted instructions, and sensitive values.
|
|
13
|
+
- Pre-execution policies for data exposure, destinations, and state-changing actions, with configurable allow, block, or escalate decisions.
|
|
14
|
+
- Optional LLM-assisted review for policy-defined ambiguous cases; deterministic rules remain available without an LLM.
|
|
15
|
+
- Safe-by-default handling of unknown labels and policy errors, with explicit configuration for fail-closed behavior.
|
|
16
|
+
- Local attack simulator with realistic malicious tool outputs, expected outcomes, and policy regression tests.
|
|
17
|
+
- Structured, secret-redacted decision records and hooks for applications to export events to their own logging systems.
|
|
18
|
+
- Framework-neutral core, compatibility adapters, typed interfaces, versioned policy formats, and comprehensive documentation and examples.
|
|
19
|
+
|
|
20
|
+
## Brand
|
|
21
|
+
- Name: TrustWarden
|
|
22
|
+
- Tagline: Guarding your tools with certainty.
|
|
23
|
+
- Colours: primary `#003366`, accent `#FFCC00`
|
|
24
|
+
- Logo: `frontend/public/logo.svg` (also `frontend/public/favicon.svg`)
|
|
25
|
+
|
|
26
|
+
## Stack
|
|
27
|
+
- Frontend: -; backend: -; database: -; vector database: -
|
|
28
|
+
- Install: `uv sync`
|
|
29
|
+
- Lint: `uv run ruff check .`
|
|
30
|
+
- Typecheck: `uv run mypy src`
|
|
31
|
+
- Test: `uv run pytest`
|
|
32
|
+
- Build: `uv build`
|
|
33
|
+
|
|
34
|
+
## Third-party services: each user brings their own keys
|
|
35
|
+
Add an external service only when a feature truly needs it. The application is multi-tenant: every user (or workspace) connects **their own** account or API key for it (Slack, Stripe, Notion, an email provider, …), never a shared key of the owner:
|
|
36
|
+
- A Connections/Integrations page in the user's settings: paste a key, token or webhook URL; test it with one harmless call before saving; show it masked (last 4 characters); allow replace and disconnect.
|
|
37
|
+
- Store each credential encrypted at rest (AES-GCM, e.g. Fernet) in the user's or workspace's own record, with a key derived from `JWT_SECRET` via HKDF (info `tenant-credentials`); never return it to the browser, log it or send it to another tenant; every call uses the current tenant's credential.
|
|
38
|
+
- A feature whose service the user has not connected shows a clear "connect <service> in Settings" state; nothing crashes.
|
|
39
|
+
- Prefer services that work with a pasted key or webhook URL. Only when a service requires an OAuth app (a client ID and secret registered once for the whole application), treat that app credential as an owner key (below); each user's OAuth tokens are still stored per tenant as above.
|
|
40
|
+
- A Python package takes credentials from its caller (arguments or the caller's own environment variables), never bundles one.
|
|
41
|
+
|
|
42
|
+
## Keys the owner provides (rare: application-wide credentials only)
|
|
43
|
+
For an application-wide credential that cannot be per user (an OAuth app's client ID and secret):
|
|
44
|
+
- Read its key from an environment variable and declare it in `.env.example` of the part that uses it (a key the browser needs: the frontend's, prefixed `VITE_`), with a comment line right above: `# <Feature>: <what the key is and where to get it>`.
|
|
45
|
+
- The application must work, deploy and pass every check **without** the key: the feature shows a clear "not configured yet" state and nothing crashes.
|
|
46
|
+
- The Factory asks the owner for declared keys and redeploys when one is provided; never hardcode, commit or log a key.
|
|
47
|
+
|
|
48
|
+
## Environment (already set; read at runtime, never hardcode)
|
|
49
|
+
`PYPI_PACKAGE_NAME`
|
|
50
|
+
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: trustwarden
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Guarding your tools with certainty. Trust labels and policy enforcement for AI agent tools.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Keywords: ai-agents,llm,policy,security,tool-calling,trust
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Classifier: Topic :: Security
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# TrustWarden
|
|
20
|
+
|
|
21
|
+
TrustWarden is a Python security library for AI agents and tool-calling applications. It tracks trust and sensitivity labels across wrapped Python tool calls, then evaluates policy before sensitive actions execute—so prompt injection, secret exfiltration, and untrusted mutations can be denied fail-closed before they run.
|
|
22
|
+
|
|
23
|
+
## Features
|
|
24
|
+
|
|
25
|
+
### Typed decorator and adapter API
|
|
26
|
+
|
|
27
|
+
Wrap any sync Python callable with pre-execution policy checks. No LangChain, OpenAI, or other agent framework is required.
|
|
28
|
+
|
|
29
|
+
**Main actions:** decorate with `@warden.tool(...)`, adapt an existing function with `warden.adapt(...)`, or wrap registries via `wrap_callable`, `wrap_tool_map`, and `wrap_tool_specs` / `ToolSpec`.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from trustwarden import Warden, mark_user_input, unwrap_result
|
|
33
|
+
|
|
34
|
+
warden = Warden()
|
|
35
|
+
|
|
36
|
+
@warden.tool(destination="local", state_changing=False)
|
|
37
|
+
def search(query: object) -> str:
|
|
38
|
+
return str(unwrap_result(query))
|
|
39
|
+
|
|
40
|
+
search(mark_user_input("roadmap"))
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Trust-label propagation and marking
|
|
44
|
+
|
|
45
|
+
Values carry `TrustLevel` (`unknown` → `system`) and `Sensitivity` (`unknown` → `secret`) via `Labelled`. Mark data at boundaries with `mark_user_input`, `mark_trusted`, `mark_system`, `mark_untrusted`, `mark_sensitive`, and `mark_secret`. Tool outputs inherit the weakest trust and strongest sensitivity from their inputs through `propagate_labels` / the decorator (or use `collect_labels` / `as_labelled` / `unwrap_result` directly).
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
from trustwarden import mark_user_input, mark_secret, collect_labels, propagate_labels
|
|
49
|
+
|
|
50
|
+
trust, sensitivity = collect_labels(mark_user_input("q"), mark_secret("sk"))
|
|
51
|
+
out = propagate_labels({"hits": 1}, mark_user_input("q"))
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Pre-execution policies (allow / block / escalate)
|
|
55
|
+
|
|
56
|
+
Versioned policy documents (`POLICY_FORMAT_VERSION` = `1.0`) declare rules for data exposure, destinations, and state-changing actions. `PolicyEngine` evaluates a `ToolContext` and returns a `DecisionAction`. Load JSON with `load_policy` / `load_policy_file`, or use `default_safe_policy()`. Author rules with `Policy`, `PolicyRule`, and `RuleKind`.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from pathlib import Path
|
|
60
|
+
from trustwarden import Warden, load_policy_file, mark_secret
|
|
61
|
+
|
|
62
|
+
policy = load_policy_file(Path("policies/default.json"))
|
|
63
|
+
warden = Warden(policy)
|
|
64
|
+
warden.decide(
|
|
65
|
+
"http_post",
|
|
66
|
+
{"body": mark_secret("sk-demo")},
|
|
67
|
+
destination="external_http",
|
|
68
|
+
state_changing=True,
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Optional LLM-assisted review
|
|
73
|
+
|
|
74
|
+
When a rule returns `escalate`, `Warden` can call a caller-supplied `LLMReviewer` (`ReviewRequest` → `ReviewResult`). TrustWarden never bundles an LLM client or API key. Without one, `DeterministicReviewer` resolves escalations offline (default: block). Use `apply_review` for manual review of a decision.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from trustwarden import Warden, DeterministicReviewer, DecisionAction, mark_user_input
|
|
78
|
+
|
|
79
|
+
warden = Warden(reviewer=DeterministicReviewer(default_action=DecisionAction.BLOCK))
|
|
80
|
+
warden.decide(
|
|
81
|
+
"send_email",
|
|
82
|
+
{"body": mark_user_input("hello")},
|
|
83
|
+
destination="email",
|
|
84
|
+
state_changing=True,
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Safe-by-default / fail-closed behavior
|
|
89
|
+
|
|
90
|
+
Unknown labels and policy evaluation errors deny by default. Configure posture with `FailClosedConfig` (safe defaults or permissive) on `Warden` / `PolicyEngine`. Detect bare values with `has_unknown_labels`. Wrapped tools raise `PolicyBlockedError` when blocked; invalid policy documents raise `PolicyError` (both under `TrustWardenError`).
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from trustwarden import Warden, FailClosedConfig, has_unknown_labels
|
|
94
|
+
|
|
95
|
+
assert has_unknown_labels("bare") is True
|
|
96
|
+
Warden(fail_closed=FailClosedConfig.safe_defaults())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Local attack simulator
|
|
100
|
+
|
|
101
|
+
`AttackSimulator` runs built-in malicious scenarios (`builtin_scenarios`: prompt-injection exfil, secret/webhook exfil, untrusted writes, unknown labels, and more) against a policy and reports pass/fail (`AttackScenario`, `ScenarioResult`). Use `assert_all_passed()` / `summary()` in CI, or the CLI entrypoint `trustwarden-simulate`.
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
from trustwarden import AttackSimulator
|
|
105
|
+
|
|
106
|
+
AttackSimulator().assert_all_passed()
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uv run trustwarden-simulate
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Structured, secret-redacted decision records and event hooks
|
|
114
|
+
|
|
115
|
+
Every evaluation produces a `DecisionRecord` (via `build_decision`) with redacted arguments (`redact_text` / `redact_value` / `REDACTED`). Subscribe to `EventBus` with callables or a `DecisionSink`: built-ins are `CollectingSink`, `LoggingSink`, and `JsonLinesSink`. Export flat dicts with `decision_to_log_record`.
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
from io import StringIO
|
|
119
|
+
from trustwarden import EventBus, JsonLinesSink, Warden, mark_user_input
|
|
120
|
+
|
|
121
|
+
bus = EventBus()
|
|
122
|
+
buf = StringIO()
|
|
123
|
+
bus.subscribe_sink(JsonLinesSink(buf))
|
|
124
|
+
warden = Warden(events=bus)
|
|
125
|
+
warden.decide("read", {"q": mark_user_input("x")}, destination="local")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Framework-neutral core, typed interfaces, versioned policies, examples
|
|
129
|
+
|
|
130
|
+
The core lives under `src/trustwarden/` with `py.typed`. Compatibility adapters and typed interfaces (`ToolSpec`, `ToolCallable`, `LLMReviewer`, `DecisionSink`) sit beside the policy format (`policies/default.json`). Runnable examples: `examples/basic_usage.py`, `examples/logging_hooks.py`, `examples/wrap_registry.py`, `examples/custom_policy.py`. Brand assets (logo/favicon) are in `frontend/public/` for packaging and docs; there is no web UI.
|
|
131
|
+
|
|
132
|
+
## Pages
|
|
133
|
+
|
|
134
|
+
TrustWarden is a **Python package**, not a web application. There are **no frontend routes** and no interactive screens.
|
|
135
|
+
|
|
136
|
+
| Path | What it is |
|
|
137
|
+
| --- | --- |
|
|
138
|
+
| _(none)_ | No app pages or SPA routes are served |
|
|
139
|
+
| `frontend/public/logo.svg` | Brand logo asset (primary `#003366`, accent `#FFCC00`) |
|
|
140
|
+
| `frontend/public/favicon.svg` | Brand favicon asset |
|
|
141
|
+
|
|
142
|
+
Use the library from your own agent or service process; there is nothing to open in a browser for TrustWarden itself.
|
|
143
|
+
|
|
144
|
+
## API
|
|
145
|
+
|
|
146
|
+
There is **no HTTP backend**. The product surface is the **importable Python API** (package version `0.1.0`) plus one console script. Grouped by feature:
|
|
147
|
+
|
|
148
|
+
### Labels and marking
|
|
149
|
+
|
|
150
|
+
| Symbol | Purpose |
|
|
151
|
+
| --- | --- |
|
|
152
|
+
| `TrustLevel` / `Sensitivity` | Enumerations for provenance and sensitivity |
|
|
153
|
+
| `Labelled` | Value + label metadata (`unwrap`, `with_labels`) |
|
|
154
|
+
| `as_labelled` / `collect_labels` / `propagate_labels` | Coerce, aggregate, and attach labels |
|
|
155
|
+
| `mark_user_input` / `mark_trusted` / `mark_system` / `mark_untrusted` | Mark provenance |
|
|
156
|
+
| `mark_sensitive` / `mark_secret` | Mark sensitivity |
|
|
157
|
+
| `unwrap_result` | Extract raw value from a labelled result |
|
|
158
|
+
|
|
159
|
+
### Guarding tools
|
|
160
|
+
|
|
161
|
+
| Symbol | Purpose |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `Warden` | Policy guard: `tool`, `adapt`, `decide`, `check` |
|
|
164
|
+
| `ToolSpec` | Declarative tool description for registries |
|
|
165
|
+
| `wrap_callable` / `wrap_tool_map` / `wrap_tool_specs` | Framework-neutral adapters |
|
|
166
|
+
|
|
167
|
+
### Policy
|
|
168
|
+
|
|
169
|
+
| Symbol | Purpose |
|
|
170
|
+
| --- | --- |
|
|
171
|
+
| `POLICY_FORMAT_VERSION` | Schema version (`1.0`) |
|
|
172
|
+
| `Policy` / `PolicyRule` / `RuleKind` / `ToolContext` | Versioned policy model |
|
|
173
|
+
| `PolicyEngine` | Deterministic evaluation |
|
|
174
|
+
| `default_safe_policy` | Built-in fail-closed policy |
|
|
175
|
+
| `load_policy` / `load_policy_file` | Parse JSON policies |
|
|
176
|
+
|
|
177
|
+
### Decisions, redaction, events
|
|
178
|
+
|
|
179
|
+
| Symbol | Purpose |
|
|
180
|
+
| --- | --- |
|
|
181
|
+
| `DecisionAction` / `DecisionRecord` / `build_decision` | Structured decisions |
|
|
182
|
+
| `REDACTED` / `redact_text` / `redact_value` | Secret scrubbing |
|
|
183
|
+
| `EventBus` / `DecisionSink` | Export hooks |
|
|
184
|
+
| `CollectingSink` / `LoggingSink` / `JsonLinesSink` | Built-in sinks |
|
|
185
|
+
| `decision_to_log_record` | Flat mapping for loggers |
|
|
186
|
+
|
|
187
|
+
### Review, safety, simulation, errors
|
|
188
|
+
|
|
189
|
+
| Symbol | Purpose |
|
|
190
|
+
| --- | --- |
|
|
191
|
+
| `LLMReviewer` / `ReviewRequest` / `ReviewResult` / `DeterministicReviewer` / `apply_review` | Escalation review |
|
|
192
|
+
| `FailClosedConfig` / `has_unknown_labels` | Fail-closed configuration |
|
|
193
|
+
| `AttackScenario` / `ScenarioResult` / `builtin_scenarios` / `AttackSimulator` | Attack simulation |
|
|
194
|
+
| `TrustWardenError` / `PolicyError` / `PolicyBlockedError` | Exception hierarchy |
|
|
195
|
+
|
|
196
|
+
### CLI
|
|
197
|
+
|
|
198
|
+
| Entry | Purpose |
|
|
199
|
+
| --- | --- |
|
|
200
|
+
| `trustwarden-simulate` | Run built-in attack scenarios and print a JSON summary (`trustwarden.simulator:main`) |
|
|
201
|
+
|
|
202
|
+
Import everything from the package root, for example `from trustwarden import Warden, mark_user_input`.
|
|
203
|
+
|
|
204
|
+
## Configuration
|
|
205
|
+
|
|
206
|
+
| Variable | Required | Purpose |
|
|
207
|
+
| --- | --- | --- |
|
|
208
|
+
| `PYPI_PACKAGE_NAME` | Yes (for publish / simulator package field) | Distribution name of this package on PyPI; read at runtime via `trustwarden.config` (never hardcode). Listed in `.env.example`. |
|
|
209
|
+
|
|
210
|
+
**Third-party API keys:** none are owned or required by TrustWarden. Optional LLM review uses a caller-supplied `LLMReviewer`; pass any model credentials only inside that implementation (arguments or the caller’s own environment). The library works fully without an LLM via `DeterministicReviewer`.
|
|
211
|
+
|
|
212
|
+
Do not commit a `.env` file or put secret values in documentation.
|
|
213
|
+
|
|
214
|
+
## Run it locally
|
|
215
|
+
|
|
216
|
+
Install dependencies and the editable package:
|
|
217
|
+
|
|
218
|
+
```bash
|
|
219
|
+
uv sync
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Lint, typecheck, test, and build:
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
uv run ruff check .
|
|
226
|
+
uv run mypy src
|
|
227
|
+
uv run pytest
|
|
228
|
+
uv build
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Try the library:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
uv run python examples/basic_usage.py
|
|
235
|
+
uv run trustwarden-simulate
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
`uv build` writes a wheel and sdist under `dist/` (for example `trustwarden-0.1.0-py3-none-any.whl` and `trustwarden-0.1.0.tar.gz`).
|
|
239
|
+
|
|
240
|
+
## Architecture
|
|
241
|
+
|
|
242
|
+
**Stack:** frontend none · backend none · database none · vector database none. Language: Python 3.11+ · package manager: uv · build backend: hatchling · typed package with `py.typed`.
|
|
243
|
+
|
|
244
|
+
**Repository layout:**
|
|
245
|
+
|
|
246
|
+
| Path | Role |
|
|
247
|
+
| --- | --- |
|
|
248
|
+
| `src/trustwarden/` | Library implementation (labels, wrap, policy, events, simulator, …) |
|
|
249
|
+
| `tests/` | Unit tests and doctest runner |
|
|
250
|
+
| `examples/` | Runnable usage samples |
|
|
251
|
+
| `policies/default.json` | Sample versioned policy document |
|
|
252
|
+
| `frontend/public/` | Brand SVG assets only (no app) |
|
|
253
|
+
| `pyproject.toml` | Package metadata, ruff/mypy/pytest, `trustwarden-simulate` script |
|
|
254
|
+
| `.env.example` | Declares `PYPI_PACKAGE_NAME` (names only) |
|
|
255
|
+
|
|
256
|
+
**How “frontend” and “backend” talk:** they do not—there is no browser client and no HTTP API. Consumers import `trustwarden` in their own process (agent runtime, worker, or CLI). Policy evaluation happens in-process before the wrapped tool body runs; decisions can be exported to the host app’s logging via `EventBus` sinks.
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
# TrustWarden
|
|
2
|
+
|
|
3
|
+
TrustWarden is a Python security library for AI agents and tool-calling applications. It tracks trust and sensitivity labels across wrapped Python tool calls, then evaluates policy before sensitive actions execute—so prompt injection, secret exfiltration, and untrusted mutations can be denied fail-closed before they run.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Typed decorator and adapter API
|
|
8
|
+
|
|
9
|
+
Wrap any sync Python callable with pre-execution policy checks. No LangChain, OpenAI, or other agent framework is required.
|
|
10
|
+
|
|
11
|
+
**Main actions:** decorate with `@warden.tool(...)`, adapt an existing function with `warden.adapt(...)`, or wrap registries via `wrap_callable`, `wrap_tool_map`, and `wrap_tool_specs` / `ToolSpec`.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from trustwarden import Warden, mark_user_input, unwrap_result
|
|
15
|
+
|
|
16
|
+
warden = Warden()
|
|
17
|
+
|
|
18
|
+
@warden.tool(destination="local", state_changing=False)
|
|
19
|
+
def search(query: object) -> str:
|
|
20
|
+
return str(unwrap_result(query))
|
|
21
|
+
|
|
22
|
+
search(mark_user_input("roadmap"))
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Trust-label propagation and marking
|
|
26
|
+
|
|
27
|
+
Values carry `TrustLevel` (`unknown` → `system`) and `Sensitivity` (`unknown` → `secret`) via `Labelled`. Mark data at boundaries with `mark_user_input`, `mark_trusted`, `mark_system`, `mark_untrusted`, `mark_sensitive`, and `mark_secret`. Tool outputs inherit the weakest trust and strongest sensitivity from their inputs through `propagate_labels` / the decorator (or use `collect_labels` / `as_labelled` / `unwrap_result` directly).
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
from trustwarden import mark_user_input, mark_secret, collect_labels, propagate_labels
|
|
31
|
+
|
|
32
|
+
trust, sensitivity = collect_labels(mark_user_input("q"), mark_secret("sk"))
|
|
33
|
+
out = propagate_labels({"hits": 1}, mark_user_input("q"))
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Pre-execution policies (allow / block / escalate)
|
|
37
|
+
|
|
38
|
+
Versioned policy documents (`POLICY_FORMAT_VERSION` = `1.0`) declare rules for data exposure, destinations, and state-changing actions. `PolicyEngine` evaluates a `ToolContext` and returns a `DecisionAction`. Load JSON with `load_policy` / `load_policy_file`, or use `default_safe_policy()`. Author rules with `Policy`, `PolicyRule`, and `RuleKind`.
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from pathlib import Path
|
|
42
|
+
from trustwarden import Warden, load_policy_file, mark_secret
|
|
43
|
+
|
|
44
|
+
policy = load_policy_file(Path("policies/default.json"))
|
|
45
|
+
warden = Warden(policy)
|
|
46
|
+
warden.decide(
|
|
47
|
+
"http_post",
|
|
48
|
+
{"body": mark_secret("sk-demo")},
|
|
49
|
+
destination="external_http",
|
|
50
|
+
state_changing=True,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Optional LLM-assisted review
|
|
55
|
+
|
|
56
|
+
When a rule returns `escalate`, `Warden` can call a caller-supplied `LLMReviewer` (`ReviewRequest` → `ReviewResult`). TrustWarden never bundles an LLM client or API key. Without one, `DeterministicReviewer` resolves escalations offline (default: block). Use `apply_review` for manual review of a decision.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from trustwarden import Warden, DeterministicReviewer, DecisionAction, mark_user_input
|
|
60
|
+
|
|
61
|
+
warden = Warden(reviewer=DeterministicReviewer(default_action=DecisionAction.BLOCK))
|
|
62
|
+
warden.decide(
|
|
63
|
+
"send_email",
|
|
64
|
+
{"body": mark_user_input("hello")},
|
|
65
|
+
destination="email",
|
|
66
|
+
state_changing=True,
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Safe-by-default / fail-closed behavior
|
|
71
|
+
|
|
72
|
+
Unknown labels and policy evaluation errors deny by default. Configure posture with `FailClosedConfig` (safe defaults or permissive) on `Warden` / `PolicyEngine`. Detect bare values with `has_unknown_labels`. Wrapped tools raise `PolicyBlockedError` when blocked; invalid policy documents raise `PolicyError` (both under `TrustWardenError`).
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from trustwarden import Warden, FailClosedConfig, has_unknown_labels
|
|
76
|
+
|
|
77
|
+
assert has_unknown_labels("bare") is True
|
|
78
|
+
Warden(fail_closed=FailClosedConfig.safe_defaults())
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Local attack simulator
|
|
82
|
+
|
|
83
|
+
`AttackSimulator` runs built-in malicious scenarios (`builtin_scenarios`: prompt-injection exfil, secret/webhook exfil, untrusted writes, unknown labels, and more) against a policy and reports pass/fail (`AttackScenario`, `ScenarioResult`). Use `assert_all_passed()` / `summary()` in CI, or the CLI entrypoint `trustwarden-simulate`.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from trustwarden import AttackSimulator
|
|
87
|
+
|
|
88
|
+
AttackSimulator().assert_all_passed()
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uv run trustwarden-simulate
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### Structured, secret-redacted decision records and event hooks
|
|
96
|
+
|
|
97
|
+
Every evaluation produces a `DecisionRecord` (via `build_decision`) with redacted arguments (`redact_text` / `redact_value` / `REDACTED`). Subscribe to `EventBus` with callables or a `DecisionSink`: built-ins are `CollectingSink`, `LoggingSink`, and `JsonLinesSink`. Export flat dicts with `decision_to_log_record`.
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from io import StringIO
|
|
101
|
+
from trustwarden import EventBus, JsonLinesSink, Warden, mark_user_input
|
|
102
|
+
|
|
103
|
+
bus = EventBus()
|
|
104
|
+
buf = StringIO()
|
|
105
|
+
bus.subscribe_sink(JsonLinesSink(buf))
|
|
106
|
+
warden = Warden(events=bus)
|
|
107
|
+
warden.decide("read", {"q": mark_user_input("x")}, destination="local")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Framework-neutral core, typed interfaces, versioned policies, examples
|
|
111
|
+
|
|
112
|
+
The core lives under `src/trustwarden/` with `py.typed`. Compatibility adapters and typed interfaces (`ToolSpec`, `ToolCallable`, `LLMReviewer`, `DecisionSink`) sit beside the policy format (`policies/default.json`). Runnable examples: `examples/basic_usage.py`, `examples/logging_hooks.py`, `examples/wrap_registry.py`, `examples/custom_policy.py`. Brand assets (logo/favicon) are in `frontend/public/` for packaging and docs; there is no web UI.
|
|
113
|
+
|
|
114
|
+
## Pages
|
|
115
|
+
|
|
116
|
+
TrustWarden is a **Python package**, not a web application. There are **no frontend routes** and no interactive screens.
|
|
117
|
+
|
|
118
|
+
| Path | What it is |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| _(none)_ | No app pages or SPA routes are served |
|
|
121
|
+
| `frontend/public/logo.svg` | Brand logo asset (primary `#003366`, accent `#FFCC00`) |
|
|
122
|
+
| `frontend/public/favicon.svg` | Brand favicon asset |
|
|
123
|
+
|
|
124
|
+
Use the library from your own agent or service process; there is nothing to open in a browser for TrustWarden itself.
|
|
125
|
+
|
|
126
|
+
## API
|
|
127
|
+
|
|
128
|
+
There is **no HTTP backend**. The product surface is the **importable Python API** (package version `0.1.0`) plus one console script. Grouped by feature:
|
|
129
|
+
|
|
130
|
+
### Labels and marking
|
|
131
|
+
|
|
132
|
+
| Symbol | Purpose |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `TrustLevel` / `Sensitivity` | Enumerations for provenance and sensitivity |
|
|
135
|
+
| `Labelled` | Value + label metadata (`unwrap`, `with_labels`) |
|
|
136
|
+
| `as_labelled` / `collect_labels` / `propagate_labels` | Coerce, aggregate, and attach labels |
|
|
137
|
+
| `mark_user_input` / `mark_trusted` / `mark_system` / `mark_untrusted` | Mark provenance |
|
|
138
|
+
| `mark_sensitive` / `mark_secret` | Mark sensitivity |
|
|
139
|
+
| `unwrap_result` | Extract raw value from a labelled result |
|
|
140
|
+
|
|
141
|
+
### Guarding tools
|
|
142
|
+
|
|
143
|
+
| Symbol | Purpose |
|
|
144
|
+
| --- | --- |
|
|
145
|
+
| `Warden` | Policy guard: `tool`, `adapt`, `decide`, `check` |
|
|
146
|
+
| `ToolSpec` | Declarative tool description for registries |
|
|
147
|
+
| `wrap_callable` / `wrap_tool_map` / `wrap_tool_specs` | Framework-neutral adapters |
|
|
148
|
+
|
|
149
|
+
### Policy
|
|
150
|
+
|
|
151
|
+
| Symbol | Purpose |
|
|
152
|
+
| --- | --- |
|
|
153
|
+
| `POLICY_FORMAT_VERSION` | Schema version (`1.0`) |
|
|
154
|
+
| `Policy` / `PolicyRule` / `RuleKind` / `ToolContext` | Versioned policy model |
|
|
155
|
+
| `PolicyEngine` | Deterministic evaluation |
|
|
156
|
+
| `default_safe_policy` | Built-in fail-closed policy |
|
|
157
|
+
| `load_policy` / `load_policy_file` | Parse JSON policies |
|
|
158
|
+
|
|
159
|
+
### Decisions, redaction, events
|
|
160
|
+
|
|
161
|
+
| Symbol | Purpose |
|
|
162
|
+
| --- | --- |
|
|
163
|
+
| `DecisionAction` / `DecisionRecord` / `build_decision` | Structured decisions |
|
|
164
|
+
| `REDACTED` / `redact_text` / `redact_value` | Secret scrubbing |
|
|
165
|
+
| `EventBus` / `DecisionSink` | Export hooks |
|
|
166
|
+
| `CollectingSink` / `LoggingSink` / `JsonLinesSink` | Built-in sinks |
|
|
167
|
+
| `decision_to_log_record` | Flat mapping for loggers |
|
|
168
|
+
|
|
169
|
+
### Review, safety, simulation, errors
|
|
170
|
+
|
|
171
|
+
| Symbol | Purpose |
|
|
172
|
+
| --- | --- |
|
|
173
|
+
| `LLMReviewer` / `ReviewRequest` / `ReviewResult` / `DeterministicReviewer` / `apply_review` | Escalation review |
|
|
174
|
+
| `FailClosedConfig` / `has_unknown_labels` | Fail-closed configuration |
|
|
175
|
+
| `AttackScenario` / `ScenarioResult` / `builtin_scenarios` / `AttackSimulator` | Attack simulation |
|
|
176
|
+
| `TrustWardenError` / `PolicyError` / `PolicyBlockedError` | Exception hierarchy |
|
|
177
|
+
|
|
178
|
+
### CLI
|
|
179
|
+
|
|
180
|
+
| Entry | Purpose |
|
|
181
|
+
| --- | --- |
|
|
182
|
+
| `trustwarden-simulate` | Run built-in attack scenarios and print a JSON summary (`trustwarden.simulator:main`) |
|
|
183
|
+
|
|
184
|
+
Import everything from the package root, for example `from trustwarden import Warden, mark_user_input`.
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
| Variable | Required | Purpose |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| `PYPI_PACKAGE_NAME` | Yes (for publish / simulator package field) | Distribution name of this package on PyPI; read at runtime via `trustwarden.config` (never hardcode). Listed in `.env.example`. |
|
|
191
|
+
|
|
192
|
+
**Third-party API keys:** none are owned or required by TrustWarden. Optional LLM review uses a caller-supplied `LLMReviewer`; pass any model credentials only inside that implementation (arguments or the caller’s own environment). The library works fully without an LLM via `DeterministicReviewer`.
|
|
193
|
+
|
|
194
|
+
Do not commit a `.env` file or put secret values in documentation.
|
|
195
|
+
|
|
196
|
+
## Run it locally
|
|
197
|
+
|
|
198
|
+
Install dependencies and the editable package:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
uv sync
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Lint, typecheck, test, and build:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
uv run ruff check .
|
|
208
|
+
uv run mypy src
|
|
209
|
+
uv run pytest
|
|
210
|
+
uv build
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Try the library:
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
uv run python examples/basic_usage.py
|
|
217
|
+
uv run trustwarden-simulate
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
`uv build` writes a wheel and sdist under `dist/` (for example `trustwarden-0.1.0-py3-none-any.whl` and `trustwarden-0.1.0.tar.gz`).
|
|
221
|
+
|
|
222
|
+
## Architecture
|
|
223
|
+
|
|
224
|
+
**Stack:** frontend none · backend none · database none · vector database none. Language: Python 3.11+ · package manager: uv · build backend: hatchling · typed package with `py.typed`.
|
|
225
|
+
|
|
226
|
+
**Repository layout:**
|
|
227
|
+
|
|
228
|
+
| Path | Role |
|
|
229
|
+
| --- | --- |
|
|
230
|
+
| `src/trustwarden/` | Library implementation (labels, wrap, policy, events, simulator, …) |
|
|
231
|
+
| `tests/` | Unit tests and doctest runner |
|
|
232
|
+
| `examples/` | Runnable usage samples |
|
|
233
|
+
| `policies/default.json` | Sample versioned policy document |
|
|
234
|
+
| `frontend/public/` | Brand SVG assets only (no app) |
|
|
235
|
+
| `pyproject.toml` | Package metadata, ruff/mypy/pytest, `trustwarden-simulate` script |
|
|
236
|
+
| `.env.example` | Declares `PYPI_PACKAGE_NAME` (names only) |
|
|
237
|
+
|
|
238
|
+
**How “frontend” and “backend” talk:** they do not—there is no browser client and no HTTP API. Consumers import `trustwarden` in their own process (agent runtime, worker, or CLI). Policy evaluation happens in-process before the wrapped tool body runs; decisions can be exported to the host app’s logging via `EventBus` sinks.
|