wardproof 0.3.2__tar.gz → 0.3.3__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.
- {wardproof-0.3.2 → wardproof-0.3.3}/PKG-INFO +20 -1
- {wardproof-0.3.2 → wardproof-0.3.3}/README.md +19 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/pyproject.toml +1 -1
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/__init__.py +1 -1
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/cli.py +8 -0
- wardproof-0.3.3/wardproof/server.py +119 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/.gitignore +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/CONTRIBUTING.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/LICENSE +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/SECURITY.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/THREAT_MODEL.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/README.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/corpus.jsonl +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/README.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/_screen.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/agentdojo.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/fetch_data.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/injecagent.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/heldout.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/latency.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/run_benchmark.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/agent_to_agent_transfer.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/README.md +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/agentkit_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/anthropic_tools_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/crewai_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/langgraph_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/mcp_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/openai_tools_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/skills_guard.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/venice_guarded.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/morse_injection_blocked_at_action.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_defi_agent.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_mcp_agent.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_rag_app.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_x402_payments.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/base.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/detector.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/responder.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/verifier.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/ledger.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/stix.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/config.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/_normalize.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/base.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/mcp_guard.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/memory_poisoning.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/prompt_injection.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/tool_misuse.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/transfer.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/x402_payment.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/base.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/null.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/ollama_client.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/engine.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/factory.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/__init__.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/executor.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/permissions.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/schema.py +0 -0
- {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/standards.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wardproof
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.3
|
|
4
4
|
Summary: Local-first, verifiable defensive AI agent swarms that protect other AI agent systems.
|
|
5
5
|
Project-URL: Homepage, https://wardproof.xyz
|
|
6
6
|
Project-URL: Repository, https://github.com/Impossible-Mission-Force/wardproof
|
|
@@ -220,6 +220,25 @@ Add `--json` to get a structured `{"verdict": ..., "allowed": ..., "risk": ...,
|
|
|
220
220
|
"reasons": [...]}` result to parse. A portable guard skill that wires this check
|
|
221
221
|
into a host agent lives in [`skill/wardproof-guard/`](https://github.com/Impossible-Mission-Force/wardproof/tree/main/skill/wardproof-guard).
|
|
222
222
|
|
|
223
|
+
### Run it as a local service with `wardproof serve`
|
|
224
|
+
|
|
225
|
+
When a host needs to screen many actions, run the swarm as a small local HTTP
|
|
226
|
+
service instead of spawning a process per call. It builds the swarm once at
|
|
227
|
+
startup and binds to localhost by default (meant to run next to the agent it
|
|
228
|
+
guards, not exposed publicly):
|
|
229
|
+
|
|
230
|
+
```bash
|
|
231
|
+
wardproof serve --port 8787
|
|
232
|
+
# GET /health -> {"status": "ok", "version": "..."}
|
|
233
|
+
# POST /check gates one input or tool call:
|
|
234
|
+
curl -s -X POST http://127.0.0.1:8787/check \
|
|
235
|
+
-d '{"kind":"input","content":"ignore all previous instructions"}'
|
|
236
|
+
# -> {"verdict": "block", "allowed": false, "risk": 1.0, "reasons": [...]}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
`/check` replies with `allowed: true` only when the verdict is `ALLOW`, so a
|
|
240
|
+
host can gate on one field.
|
|
241
|
+
|
|
223
242
|
---
|
|
224
243
|
|
|
225
244
|
## Architecture
|
|
@@ -170,6 +170,25 @@ Add `--json` to get a structured `{"verdict": ..., "allowed": ..., "risk": ...,
|
|
|
170
170
|
"reasons": [...]}` result to parse. A portable guard skill that wires this check
|
|
171
171
|
into a host agent lives in [`skill/wardproof-guard/`](https://github.com/Impossible-Mission-Force/wardproof/tree/main/skill/wardproof-guard).
|
|
172
172
|
|
|
173
|
+
### Run it as a local service with `wardproof serve`
|
|
174
|
+
|
|
175
|
+
When a host needs to screen many actions, run the swarm as a small local HTTP
|
|
176
|
+
service instead of spawning a process per call. It builds the swarm once at
|
|
177
|
+
startup and binds to localhost by default (meant to run next to the agent it
|
|
178
|
+
guards, not exposed publicly):
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
wardproof serve --port 8787
|
|
182
|
+
# GET /health -> {"status": "ok", "version": "..."}
|
|
183
|
+
# POST /check gates one input or tool call:
|
|
184
|
+
curl -s -X POST http://127.0.0.1:8787/check \
|
|
185
|
+
-d '{"kind":"input","content":"ignore all previous instructions"}'
|
|
186
|
+
# -> {"verdict": "block", "allowed": false, "risk": 1.0, "reasons": [...]}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
`/check` replies with `allowed: true` only when the verdict is `ALLOW`, so a
|
|
190
|
+
host can gate on one field.
|
|
191
|
+
|
|
173
192
|
---
|
|
174
193
|
|
|
175
194
|
## Architecture
|
|
@@ -16,7 +16,7 @@ from wardproof.sandbox.executor import SandboxExecutor, ToolRegistry
|
|
|
16
16
|
from wardproof.sandbox.permissions import PermissionBroker, ToolGrant
|
|
17
17
|
from wardproof.schema import Decision, Event, Finding, Severity, Verdict
|
|
18
18
|
|
|
19
|
-
__version__ = "0.3.
|
|
19
|
+
__version__ = "0.3.3"
|
|
20
20
|
__all__ = [
|
|
21
21
|
"Event",
|
|
22
22
|
"Decision",
|
|
@@ -101,6 +101,9 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
101
101
|
cp.add_argument("--source", default="agent", help="who originated the event")
|
|
102
102
|
cp.add_argument("--args", default=None, help="tool-call arguments as a JSON string")
|
|
103
103
|
cp.add_argument("--json", action="store_true", help="print a JSON object instead of text")
|
|
104
|
+
rp = sub.add_parser("serve", help="run a local HTTP service that screens one event per request")
|
|
105
|
+
rp.add_argument("--host", default="127.0.0.1", help="bind address (default localhost)")
|
|
106
|
+
rp.add_argument("--port", type=int, default=8787, help="bind port (default 8787)")
|
|
104
107
|
args = parser.parse_args(argv)
|
|
105
108
|
if args.cmd == "verify-ledger":
|
|
106
109
|
return _verify_file(args.path, args.pubkey)
|
|
@@ -108,6 +111,11 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
108
111
|
return _export_stix(args.path, args.out)
|
|
109
112
|
if args.cmd == "check":
|
|
110
113
|
return _check(args.kind, args.content, args.source, args.args, args.json)
|
|
114
|
+
if args.cmd == "serve":
|
|
115
|
+
from wardproof.server import serve
|
|
116
|
+
|
|
117
|
+
serve(host=args.host, port=args.port)
|
|
118
|
+
return 0
|
|
111
119
|
return 1
|
|
112
120
|
|
|
113
121
|
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"""A small local HTTP service that screens one event per request.
|
|
2
|
+
|
|
3
|
+
Stdlib only, no third-party dependencies. It builds the default swarm once at
|
|
4
|
+
startup and reuses it, so a host (an agent, a bot, any language) can gate a tool
|
|
5
|
+
call or input with a single HTTP request instead of spawning a process and
|
|
6
|
+
re-importing on every call.
|
|
7
|
+
|
|
8
|
+
The service binds to localhost by default and is meant to run next to the agent
|
|
9
|
+
it guards, not to be exposed to the public internet.
|
|
10
|
+
|
|
11
|
+
Endpoints:
|
|
12
|
+
GET /health -> {"status": "ok", "version": "..."}
|
|
13
|
+
POST /check -> body: {"kind": "tool_call"|"input", "content": "...",
|
|
14
|
+
"source": "...", "args": {...}}
|
|
15
|
+
reply: {"verdict": "...", "allowed": true|false,
|
|
16
|
+
"risk": 0.0, "reasons": [...]}
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
from wardproof import __version__
|
|
26
|
+
from wardproof.orchestration.factory import build_default_swarm
|
|
27
|
+
from wardproof.schema import Event, Verdict
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def screen_event(swarm: Any, payload: dict[str, Any]) -> dict[str, Any]:
|
|
31
|
+
"""Screen one payload with the given swarm and return a JSON-able verdict.
|
|
32
|
+
|
|
33
|
+
Shared by the HTTP handler and the tests so both exercise identical logic.
|
|
34
|
+
"""
|
|
35
|
+
kind = str(payload.get("kind", "tool_call"))
|
|
36
|
+
content = str(payload.get("content", ""))
|
|
37
|
+
source = str(payload.get("source", "agent"))
|
|
38
|
+
metadata: dict[str, Any] = {}
|
|
39
|
+
args = payload.get("args")
|
|
40
|
+
if isinstance(args, dict):
|
|
41
|
+
metadata["args"] = args
|
|
42
|
+
|
|
43
|
+
out = swarm.handle(Event(kind=kind, source=source, content=content, metadata=metadata))
|
|
44
|
+
|
|
45
|
+
seen: set[str] = set()
|
|
46
|
+
reasons: list[str] = []
|
|
47
|
+
for decision in (out.detector, out.verifier):
|
|
48
|
+
for finding in decision.findings:
|
|
49
|
+
if finding.triggered and finding.reason and finding.reason not in seen:
|
|
50
|
+
seen.add(finding.reason)
|
|
51
|
+
reasons.append(finding.reason)
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
"verdict": out.verdict.value,
|
|
55
|
+
"allowed": out.verdict is Verdict.ALLOW,
|
|
56
|
+
"risk": round(out.risk, 3),
|
|
57
|
+
"reasons": reasons,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _make_handler(swarm: Any) -> type[BaseHTTPRequestHandler]:
|
|
62
|
+
class Handler(BaseHTTPRequestHandler):
|
|
63
|
+
# one shared swarm for every request
|
|
64
|
+
_swarm = swarm
|
|
65
|
+
|
|
66
|
+
def _send(self, code: int, body: dict[str, Any]) -> None:
|
|
67
|
+
raw = json.dumps(body).encode("utf-8")
|
|
68
|
+
self.send_response(code)
|
|
69
|
+
self.send_header("Content-Type", "application/json")
|
|
70
|
+
self.send_header("Content-Length", str(len(raw)))
|
|
71
|
+
self.end_headers()
|
|
72
|
+
self.wfile.write(raw)
|
|
73
|
+
|
|
74
|
+
def do_GET(self) -> None: # noqa: N802 (stdlib name)
|
|
75
|
+
if self.path.rstrip("/") == "/health":
|
|
76
|
+
self._send(200, {"status": "ok", "version": __version__})
|
|
77
|
+
else:
|
|
78
|
+
self._send(404, {"error": "not found"})
|
|
79
|
+
|
|
80
|
+
def do_POST(self) -> None: # noqa: N802 (stdlib name)
|
|
81
|
+
if self.path.rstrip("/") != "/check":
|
|
82
|
+
self._send(404, {"error": "not found"})
|
|
83
|
+
return
|
|
84
|
+
try:
|
|
85
|
+
length = int(self.headers.get("Content-Length", 0))
|
|
86
|
+
except (TypeError, ValueError):
|
|
87
|
+
length = 0
|
|
88
|
+
raw = self.rfile.read(length) if length else b""
|
|
89
|
+
try:
|
|
90
|
+
payload = json.loads(raw or b"{}")
|
|
91
|
+
if not isinstance(payload, dict):
|
|
92
|
+
raise ValueError("body must be a JSON object")
|
|
93
|
+
except (json.JSONDecodeError, ValueError) as exc:
|
|
94
|
+
self._send(400, {"error": f"invalid JSON body: {exc}"})
|
|
95
|
+
return
|
|
96
|
+
try:
|
|
97
|
+
self._send(200, screen_event(self._swarm, payload))
|
|
98
|
+
except Exception as exc: # fail closed: report, do not 200 a non-screen
|
|
99
|
+
self._send(500, {"error": f"screen failed: {exc}"})
|
|
100
|
+
|
|
101
|
+
def log_message(self, *_args: Any) -> None:
|
|
102
|
+
# stay quiet by default; the host decides what to log
|
|
103
|
+
return
|
|
104
|
+
|
|
105
|
+
return Handler
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def serve(host: str = "127.0.0.1", port: int = 8787) -> None:
|
|
109
|
+
"""Build the swarm once and serve until interrupted."""
|
|
110
|
+
swarm = build_default_swarm()
|
|
111
|
+
handler = _make_handler(swarm)
|
|
112
|
+
httpd = ThreadingHTTPServer((host, port), handler)
|
|
113
|
+
print(f"wardproof serve: screening on http://{host}:{port} (POST /check, GET /health)")
|
|
114
|
+
try:
|
|
115
|
+
httpd.serve_forever()
|
|
116
|
+
except KeyboardInterrupt:
|
|
117
|
+
pass
|
|
118
|
+
finally:
|
|
119
|
+
httpd.server_close()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|