wardproof 0.3.3__tar.gz → 0.3.4__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 (68) hide show
  1. {wardproof-0.3.3 → wardproof-0.3.4}/PKG-INFO +15 -4
  2. {wardproof-0.3.3 → wardproof-0.3.4}/README.md +14 -3
  3. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/README.md +23 -0
  4. wardproof-0.3.4/examples/integrations/swarms_guarded.py +186 -0
  5. {wardproof-0.3.3 → wardproof-0.3.4}/pyproject.toml +1 -1
  6. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/__init__.py +1 -1
  7. {wardproof-0.3.3 → wardproof-0.3.4}/.gitignore +0 -0
  8. {wardproof-0.3.3 → wardproof-0.3.4}/CONTRIBUTING.md +0 -0
  9. {wardproof-0.3.3 → wardproof-0.3.4}/LICENSE +0 -0
  10. {wardproof-0.3.3 → wardproof-0.3.4}/SECURITY.md +0 -0
  11. {wardproof-0.3.3 → wardproof-0.3.4}/THREAT_MODEL.md +0 -0
  12. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/README.md +0 -0
  13. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/corpus.jsonl +0 -0
  14. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/README.md +0 -0
  15. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/__init__.py +0 -0
  16. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/_screen.py +0 -0
  17. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/agentdojo.py +0 -0
  18. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/fetch_data.py +0 -0
  19. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/external/injecagent.py +0 -0
  20. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/heldout.py +0 -0
  21. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/latency.py +0 -0
  22. {wardproof-0.3.3 → wardproof-0.3.4}/benchmarks/run_benchmark.py +0 -0
  23. {wardproof-0.3.3 → wardproof-0.3.4}/examples/agent_to_agent_transfer.py +0 -0
  24. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/agentkit_guarded.py +0 -0
  25. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/anthropic_tools_guarded.py +0 -0
  26. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/crewai_guarded.py +0 -0
  27. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/langgraph_guarded.py +0 -0
  28. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/mcp_guarded.py +0 -0
  29. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/openai_tools_guarded.py +0 -0
  30. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/skills_guard.py +0 -0
  31. {wardproof-0.3.3 → wardproof-0.3.4}/examples/integrations/venice_guarded.py +0 -0
  32. {wardproof-0.3.3 → wardproof-0.3.4}/examples/morse_injection_blocked_at_action.py +0 -0
  33. {wardproof-0.3.3 → wardproof-0.3.4}/examples/protect_defi_agent.py +0 -0
  34. {wardproof-0.3.3 → wardproof-0.3.4}/examples/protect_mcp_agent.py +0 -0
  35. {wardproof-0.3.3 → wardproof-0.3.4}/examples/protect_rag_app.py +0 -0
  36. {wardproof-0.3.3 → wardproof-0.3.4}/examples/protect_x402_payments.py +0 -0
  37. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/agents/__init__.py +0 -0
  38. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/agents/base.py +0 -0
  39. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/agents/detector.py +0 -0
  40. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/agents/responder.py +0 -0
  41. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/agents/verifier.py +0 -0
  42. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/audit/__init__.py +0 -0
  43. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/audit/ledger.py +0 -0
  44. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/audit/stix.py +0 -0
  45. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/cli.py +0 -0
  46. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/config.py +0 -0
  47. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/__init__.py +0 -0
  48. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/_normalize.py +0 -0
  49. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/base.py +0 -0
  50. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/mcp_guard.py +0 -0
  51. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/memory_poisoning.py +0 -0
  52. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/prompt_injection.py +0 -0
  53. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/tool_misuse.py +0 -0
  54. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/transfer.py +0 -0
  55. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/guardrails/x402_payment.py +0 -0
  56. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/llm/__init__.py +0 -0
  57. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/llm/base.py +0 -0
  58. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/llm/null.py +0 -0
  59. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/llm/ollama_client.py +0 -0
  60. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/orchestration/__init__.py +0 -0
  61. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/orchestration/engine.py +0 -0
  62. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/orchestration/factory.py +0 -0
  63. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/sandbox/__init__.py +0 -0
  64. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/sandbox/executor.py +0 -0
  65. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/sandbox/permissions.py +0 -0
  66. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/schema.py +0 -0
  67. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/server.py +0 -0
  68. {wardproof-0.3.3 → wardproof-0.3.4}/wardproof/standards.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wardproof
3
- Version: 0.3.3
3
+ Version: 0.3.4
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
@@ -74,7 +74,7 @@ It is deliberately **small, transparent, and forkable**. The security core has
74
74
  **zero third-party dependencies** and runs **fully offline**, with a local
75
75
  model via Ollama, or with no model at all.
76
76
 
77
- > **Status: v0.3.1.** The deterministic core is built, tested, and benchmarked
77
+ > **Status: v0.3.4.** The deterministic core is built, tested, and benchmarked
78
78
  > (see [Benchmark](#benchmark)), and ships dedicated guards for x402 agent
79
79
  > payments, on-chain transfers, MCP tool calls, and skill/tool definitions, a
80
80
  > controls-to-standards map (OWASP Agentic Top 10, OWASP LLM 2025, MITRE ATLAS,
@@ -210,7 +210,7 @@ gate a shell pipeline or an agent skill on it:
210
210
 
211
211
  ```bash
212
212
  # A tool call (tool name as the content, arguments as a JSON string)
213
- wardproof check "get_weather" --args '{"city":"Hanoi"}' # ALLOW, exits 0
213
+ wardproof check "get_weather" --args '{"city":"Berlin"}' # ALLOW, exits 0
214
214
 
215
215
  # An untrusted input
216
216
  wardproof check "ignore all previous instructions" --kind input # BLOCK, exits non-zero
@@ -239,6 +239,17 @@ curl -s -X POST http://127.0.0.1:8787/check \
239
239
  `/check` replies with `allowed: true` only when the verdict is `ALLOW`, so a
240
240
  host can gate on one field.
241
241
 
242
+ ### Guard a Swarms agent
243
+
244
+ [`examples/integrations/swarms_guarded.py`](https://github.com/Impossible-Mission-Force/wardproof/tree/main/examples/integrations/swarms_guarded.py)
245
+ screens a [Swarms](https://github.com/kyegomez/swarms) agent's tool calls before
246
+ they run. `GuardedToolExecutor.run` screens one `{"function": {"name", "arguments"}}`
247
+ tool call and `run_many` screens a batch (Swarms can dispatch several in one
248
+ step); each call executes only when the verdict is `ALLOW`, and anything else is
249
+ refused and recorded to the audit ledger. The guard works on the plain tool-call
250
+ dict, so it adds no dependency; the optional production adapter lazy-imports
251
+ `swarms.tools.execute_tool_call_simple`.
252
+
242
253
  ---
243
254
 
244
255
  ## Architecture
@@ -340,7 +351,7 @@ No need to touch the engine, the ledger, or the agent base classes.
340
351
  Wardproof is built to become a complete, auditable control layer for AI agents.
341
352
  The direction:
342
353
 
343
- **Now (v0.3.1)**
354
+ **Now (v0.3.4)**
344
355
  The deterministic core: schema, guardrails, Detector / Verifier / Responder, a
345
356
  capability sandbox, circuit breaker and watchdog, a hash-chained and optionally
346
357
  signed audit ledger, a reproducible adversarial benchmark, a published threat
@@ -24,7 +24,7 @@ It is deliberately **small, transparent, and forkable**. The security core has
24
24
  **zero third-party dependencies** and runs **fully offline**, with a local
25
25
  model via Ollama, or with no model at all.
26
26
 
27
- > **Status: v0.3.1.** The deterministic core is built, tested, and benchmarked
27
+ > **Status: v0.3.4.** The deterministic core is built, tested, and benchmarked
28
28
  > (see [Benchmark](#benchmark)), and ships dedicated guards for x402 agent
29
29
  > payments, on-chain transfers, MCP tool calls, and skill/tool definitions, a
30
30
  > controls-to-standards map (OWASP Agentic Top 10, OWASP LLM 2025, MITRE ATLAS,
@@ -160,7 +160,7 @@ gate a shell pipeline or an agent skill on it:
160
160
 
161
161
  ```bash
162
162
  # A tool call (tool name as the content, arguments as a JSON string)
163
- wardproof check "get_weather" --args '{"city":"Hanoi"}' # ALLOW, exits 0
163
+ wardproof check "get_weather" --args '{"city":"Berlin"}' # ALLOW, exits 0
164
164
 
165
165
  # An untrusted input
166
166
  wardproof check "ignore all previous instructions" --kind input # BLOCK, exits non-zero
@@ -189,6 +189,17 @@ curl -s -X POST http://127.0.0.1:8787/check \
189
189
  `/check` replies with `allowed: true` only when the verdict is `ALLOW`, so a
190
190
  host can gate on one field.
191
191
 
192
+ ### Guard a Swarms agent
193
+
194
+ [`examples/integrations/swarms_guarded.py`](https://github.com/Impossible-Mission-Force/wardproof/tree/main/examples/integrations/swarms_guarded.py)
195
+ screens a [Swarms](https://github.com/kyegomez/swarms) agent's tool calls before
196
+ they run. `GuardedToolExecutor.run` screens one `{"function": {"name", "arguments"}}`
197
+ tool call and `run_many` screens a batch (Swarms can dispatch several in one
198
+ step); each call executes only when the verdict is `ALLOW`, and anything else is
199
+ refused and recorded to the audit ledger. The guard works on the plain tool-call
200
+ dict, so it adds no dependency; the optional production adapter lazy-imports
201
+ `swarms.tools.execute_tool_call_simple`.
202
+
192
203
  ---
193
204
 
194
205
  ## Architecture
@@ -290,7 +301,7 @@ No need to touch the engine, the ledger, or the agent base classes.
290
301
  Wardproof is built to become a complete, auditable control layer for AI agents.
291
302
  The direction:
292
303
 
293
- **Now (v0.3.1)**
304
+ **Now (v0.3.4)**
294
305
  The deterministic core: schema, guardrails, Detector / Verifier / Responder, a
295
306
  capability sandbox, circuit breaker and watchdog, a hash-chained and optionally
296
307
  signed audit ledger, a reproducible adversarial benchmark, a published threat
@@ -266,6 +266,29 @@ as `action.invoke(args_dict)`; the only abstract method on `ActionProvider` is
266
266
  construction and each invoke, so the example no-ops that one call to stay
267
267
  offline; it touches telemetry only, never wallet logic.
268
268
 
269
+ ## Swarms (`swarms_guarded.py`)
270
+
271
+ Screen a Swarms agent's tool calls before they run. Swarms passes a tool call as
272
+ `{"function": {"name", "arguments"}}` (the shape it uses for native and MCP
273
+ tools). `GuardedToolExecutor.run` screens one such call and `run_many` screens a
274
+ batch (Swarms can dispatch several tool calls in one step); each runs only on an
275
+ `ALLOW` verdict, and anything else is refused and recorded to the ledger.
276
+
277
+ ```python
278
+ from wardproof import build_default_swarm, AuditLedger
279
+ from swarms_guarded import GuardedToolExecutor
280
+
281
+ guarded = GuardedToolExecutor(my_executor) # my_executor(tool_call) -> str
282
+ guarded.run({"function": {"name": "get_weather", "arguments": {"city": "Berlin"}}})
283
+ guarded.run_many([call_a, call_b]) # safe ones run, dangerous ones refused
284
+ ```
285
+
286
+ The guard works on the plain tool-call dict, so the module imports nothing from
287
+ `swarms`; only the optional `make_swarms_executor` adapter lazy-imports
288
+ `swarms.tools.execute_tool_call_simple`. The example runs fully offline with a
289
+ stub executor (one benign `get_weather`, one `run_command` carrying `rm -rf /`
290
+ that is blocked, one `send_email`).
291
+
269
292
  ## x402 payments (`../protect_x402_payments.py`)
270
293
 
271
294
  Not a framework wrapper, but the same pattern for paid APIs: decode a real
@@ -0,0 +1,186 @@
1
+ """Put Wardproof in front of a Swarms agent's tool calls.
2
+
3
+ Swarms (https://github.com/kyegomez/swarms) is a multi-agent orchestration
4
+ framework. An agent calls tools, and tool calls flow as objects shaped like
5
+ ``{"function": {"name": ..., "arguments": {...}}}`` (the OpenAI tool-call shape
6
+ Swarms uses for both native tools and MCP tools). The clean place to add a
7
+ deterministic safety layer is right before a tool call is executed: screen the
8
+ call with Wardproof's default swarm and only run it when the verdict is ALLOW.
9
+
10
+ How the interception works (verified against the Swarms MCP tool API at
11
+ https://docs.swarms.world/integrations/mcp, not from memory). Swarms executes a
12
+ single tool call with ``execute_tool_call_simple(response=tool_call, ...)`` where
13
+ ``tool_call`` is ``{"function": {"name", "arguments"}}``. So the uniform
14
+ interception point is a thin wrapper that takes that same tool-call dict, screens
15
+ ``(name, arguments)`` through ``swarm.handle(Event(kind="tool_call", ...))``, and
16
+ only forwards to the real executor on ALLOW. Anything else is refused and recorded
17
+ to the audit ledger; the tool is never executed.
18
+
19
+ What this example is, and is NOT. It runs the REAL screening engine and a REAL
20
+ tamper-evident ledger. It does NOT spin up a live Swarms agent or an LLM: building
21
+ and screening a tool call needs no model, so the guard, the verdicts, and the
22
+ audit record are all real. The "executor" here is an offline stub that returns a
23
+ canned string and runs no real tool, so the example needs no network and no API
24
+ keys. In a real deployment you would pass Swarms' ``execute_tool_call_simple`` (or
25
+ your own tool dispatcher) as the executor instead of the stub.
26
+
27
+ Honest note on detection. Screening uses the default swarm's deterministic
28
+ guardrails (prompt-injection and tool-misuse baselines). They catch a destructive
29
+ command in the tool name or arguments (for example ``rm -rf /``) and an injection
30
+ that rides in the tool name; they are a transparent baseline, not a guarantee
31
+ against a novel phrasing. One known gap: an injection hidden inside an argument
32
+ *value* (plain prose in ``arguments``) is not caught by this deterministic
33
+ tool-call screen on its own; that is the job of the optional LLM second opinion
34
+ (``build_default_swarm(llm=...)``). The baseline's value is screening the concrete
35
+ tool call and its arguments at the moment of invocation, and recording every
36
+ decision so the trail is verifiable afterwards.
37
+
38
+ Swarms is an OPTIONAL dependency. This example's guard does not import swarms at
39
+ all (it operates on the plain tool-call dict), so it runs as-is. To wire it to a
40
+ real Swarms executor:
41
+
42
+ pip install -U swarms wardproof
43
+
44
+ # then pass swarms.tools.execute_tool_call_simple as the executor
45
+
46
+ Run the offline demonstration:
47
+
48
+ python examples/integrations/swarms_guarded.py
49
+ """
50
+
51
+ from __future__ import annotations
52
+
53
+ from typing import Any, Callable
54
+
55
+ from wardproof import AuditLedger, Event, Verdict, build_default_swarm
56
+
57
+ # A tool call as Swarms passes it: {"function": {"name": ..., "arguments": {...}}}
58
+ ToolCall = dict[str, Any]
59
+ # An executor takes a tool call and returns the tool's result string.
60
+ Executor = Callable[[ToolCall], str]
61
+
62
+
63
+ class GuardedToolExecutor:
64
+ """Screen each Swarms tool call before executing it.
65
+
66
+ Wrap any executor that takes a ``{"function": {"name", "arguments"}}`` dict
67
+ and returns a string (for example ``swarms.tools.execute_tool_call_simple``,
68
+ adapted to a sync call). On a verdict other than ALLOW the wrapped executor
69
+ is never called; a refusal string is returned and the decision is recorded.
70
+ """
71
+
72
+ def __init__(
73
+ self,
74
+ executor: Executor,
75
+ *,
76
+ swarm: Any | None = None,
77
+ ledger: AuditLedger | None = None,
78
+ agent_name: str = "swarms-agent",
79
+ ) -> None:
80
+ self._executor = executor
81
+ self._ledger = ledger if ledger is not None else AuditLedger()
82
+ self._swarm = swarm if swarm is not None else build_default_swarm(ledger=self._ledger)
83
+ self._agent_name = agent_name
84
+
85
+ def run(self, tool_call: ToolCall) -> str:
86
+ function = tool_call.get("function", {}) or {}
87
+ name = str(function.get("name", ""))
88
+ arguments = function.get("arguments", {}) or {}
89
+ if not isinstance(arguments, dict):
90
+ arguments = {"_raw": arguments}
91
+
92
+ out = self._swarm.handle(
93
+ Event(
94
+ kind="tool_call",
95
+ source=self._agent_name,
96
+ content=name,
97
+ metadata={"args": arguments},
98
+ )
99
+ )
100
+
101
+ if out.verdict is not Verdict.ALLOW:
102
+ seen: set[str] = set()
103
+ ordered: list[str] = []
104
+ for d in (out.detector, out.verifier):
105
+ for f in d.findings:
106
+ if f.triggered and f.reason and f.reason not in seen:
107
+ seen.add(f.reason)
108
+ ordered.append(f.reason)
109
+ reasons = "; ".join(ordered)
110
+ return (
111
+ f"BLOCKED by Wardproof: verdict={out.verdict.value}. "
112
+ f"The tool '{name}' was not executed. {reasons}".strip()
113
+ )
114
+
115
+ return self._executor(tool_call)
116
+
117
+ def run_many(self, tool_calls: list[ToolCall]) -> list[str]:
118
+ """Screen and run a batch of tool calls, one verdict each.
119
+
120
+ Swarms can execute several tool calls in one step (see
121
+ ``execute_multiple_tools_on_multiple_mcp_servers``). Each call is screened
122
+ independently: the allowed ones run, the rest are refused and recorded,
123
+ and the returned list lines up one-to-one with ``tool_calls``. One
124
+ poisoned call in a batch never blocks the safe ones, and never runs.
125
+ """
126
+ return [self.run(call) for call in tool_calls]
127
+
128
+ @property
129
+ def ledger(self) -> AuditLedger:
130
+ return self._ledger
131
+
132
+
133
+ def make_swarms_executor(server_path: str, *, transport: str = "streamable-http") -> Executor:
134
+ """Build an executor backed by Swarms' real MCP tool dispatcher.
135
+
136
+ This is the production path: it wraps ``swarms.tools.execute_tool_call_simple``
137
+ (verified against the Swarms MCP API) so a guarded call that passes screening
138
+ is executed against a real MCP server. Swarms' function is async, so it is run
139
+ to completion here for the simple synchronous executor shape. Importing swarms
140
+ happens only when this is called, so the rest of this module stays import-free.
141
+ """
142
+ import asyncio
143
+
144
+ from swarms.tools import execute_tool_call_simple # imported lazily on purpose
145
+
146
+ def _executor(tool_call: ToolCall) -> str:
147
+ result = asyncio.run(
148
+ execute_tool_call_simple(
149
+ response=tool_call,
150
+ server_path=server_path,
151
+ output_type="str",
152
+ transport=transport,
153
+ )
154
+ )
155
+ return result if isinstance(result, str) else str(result)
156
+
157
+ return _executor
158
+
159
+
160
+ def _stub_executor(tool_call: ToolCall) -> str:
161
+ """Offline stand-in for a real Swarms tool dispatcher. Runs no real tool."""
162
+ name = tool_call.get("function", {}).get("name", "")
163
+ return f"[stub] executed {name} and returned a canned result"
164
+
165
+
166
+ def _demo() -> None:
167
+ ledger = AuditLedger()
168
+ guarded = GuardedToolExecutor(_stub_executor, ledger=ledger, agent_name="swarms-agent")
169
+
170
+ # a batch as Swarms might dispatch in one step: one of them is dangerous
171
+ calls: list[ToolCall] = [
172
+ {"function": {"name": "get_weather", "arguments": {"city": "Berlin"}}},
173
+ {"function": {"name": "run_command", "arguments": {"cmd": "rm -rf /"}}},
174
+ {"function": {"name": "send_email", "arguments": {"to": "a@b.com", "subject": "hi"}}},
175
+ ]
176
+
177
+ for call, result in zip(calls, guarded.run_many(calls)):
178
+ name = call["function"]["name"]
179
+ print(f"{name:14} -> {result}")
180
+
181
+ ok, detail = ledger.verify()
182
+ print(f"\nledger: {'OK' if ok else 'FAIL'} - {detail}")
183
+
184
+
185
+ if __name__ == "__main__":
186
+ _demo()
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "wardproof"
7
- version = "0.3.3"
7
+ version = "0.3.4"
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.3"
19
+ __version__ = "0.3.4"
20
20
  __all__ = [
21
21
  "Event",
22
22
  "Decision",
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