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.
Files changed (67) hide show
  1. {wardproof-0.3.2 → wardproof-0.3.3}/PKG-INFO +20 -1
  2. {wardproof-0.3.2 → wardproof-0.3.3}/README.md +19 -0
  3. {wardproof-0.3.2 → wardproof-0.3.3}/pyproject.toml +1 -1
  4. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/__init__.py +1 -1
  5. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/cli.py +8 -0
  6. wardproof-0.3.3/wardproof/server.py +119 -0
  7. {wardproof-0.3.2 → wardproof-0.3.3}/.gitignore +0 -0
  8. {wardproof-0.3.2 → wardproof-0.3.3}/CONTRIBUTING.md +0 -0
  9. {wardproof-0.3.2 → wardproof-0.3.3}/LICENSE +0 -0
  10. {wardproof-0.3.2 → wardproof-0.3.3}/SECURITY.md +0 -0
  11. {wardproof-0.3.2 → wardproof-0.3.3}/THREAT_MODEL.md +0 -0
  12. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/README.md +0 -0
  13. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/corpus.jsonl +0 -0
  14. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/README.md +0 -0
  15. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/__init__.py +0 -0
  16. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/_screen.py +0 -0
  17. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/agentdojo.py +0 -0
  18. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/fetch_data.py +0 -0
  19. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/external/injecagent.py +0 -0
  20. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/heldout.py +0 -0
  21. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/latency.py +0 -0
  22. {wardproof-0.3.2 → wardproof-0.3.3}/benchmarks/run_benchmark.py +0 -0
  23. {wardproof-0.3.2 → wardproof-0.3.3}/examples/agent_to_agent_transfer.py +0 -0
  24. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/README.md +0 -0
  25. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/agentkit_guarded.py +0 -0
  26. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/anthropic_tools_guarded.py +0 -0
  27. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/crewai_guarded.py +0 -0
  28. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/langgraph_guarded.py +0 -0
  29. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/mcp_guarded.py +0 -0
  30. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/openai_tools_guarded.py +0 -0
  31. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/skills_guard.py +0 -0
  32. {wardproof-0.3.2 → wardproof-0.3.3}/examples/integrations/venice_guarded.py +0 -0
  33. {wardproof-0.3.2 → wardproof-0.3.3}/examples/morse_injection_blocked_at_action.py +0 -0
  34. {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_defi_agent.py +0 -0
  35. {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_mcp_agent.py +0 -0
  36. {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_rag_app.py +0 -0
  37. {wardproof-0.3.2 → wardproof-0.3.3}/examples/protect_x402_payments.py +0 -0
  38. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/__init__.py +0 -0
  39. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/base.py +0 -0
  40. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/detector.py +0 -0
  41. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/responder.py +0 -0
  42. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/agents/verifier.py +0 -0
  43. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/__init__.py +0 -0
  44. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/ledger.py +0 -0
  45. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/audit/stix.py +0 -0
  46. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/config.py +0 -0
  47. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/__init__.py +0 -0
  48. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/_normalize.py +0 -0
  49. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/base.py +0 -0
  50. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/mcp_guard.py +0 -0
  51. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/memory_poisoning.py +0 -0
  52. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/prompt_injection.py +0 -0
  53. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/tool_misuse.py +0 -0
  54. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/transfer.py +0 -0
  55. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/guardrails/x402_payment.py +0 -0
  56. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/__init__.py +0 -0
  57. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/base.py +0 -0
  58. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/null.py +0 -0
  59. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/llm/ollama_client.py +0 -0
  60. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/__init__.py +0 -0
  61. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/engine.py +0 -0
  62. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/orchestration/factory.py +0 -0
  63. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/__init__.py +0 -0
  64. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/executor.py +0 -0
  65. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/sandbox/permissions.py +0 -0
  66. {wardproof-0.3.2 → wardproof-0.3.3}/wardproof/schema.py +0 -0
  67. {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.2
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wardproof"
7
- version = "0.3.2"
7
+ version = "0.3.3"
8
8
  description = "Local-first, verifiable defensive AI agent swarms that protect other AI agent systems."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -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.2"
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