vitnify 0.2.2__tar.gz → 0.2.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.
- {vitnify-0.2.2/src/vitnify.egg-info → vitnify-0.2.3}/PKG-INFO +1 -1
- {vitnify-0.2.2 → vitnify-0.2.3}/pyproject.toml +1 -1
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/certificate.py +13 -5
- {vitnify-0.2.2 → vitnify-0.2.3/src/vitnify.egg-info}/PKG-INFO +1 -1
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_verify_hardening.py +21 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/LICENSE +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/NOTICE +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/README.md +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/setup.cfg +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/__init__.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/_vendor/__init__.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/_vendor/pck/__init__.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/_vendor/pck/cas.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/capability.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/deterministic_llm.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/engine.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/events.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/langgraph_adapter.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/mcp_adapter.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/react.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/replayer.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify/viewer.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify.egg-info/SOURCES.txt +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify.egg-info/dependency_links.txt +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify.egg-info/requires.txt +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/src/vitnify.egg-info/top_level.txt +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_adapters.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_attack_matrix.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_capability.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_certificate.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_ed25519.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_events.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_l2_recompute.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_receipt_v2.py +0 -0
- {vitnify-0.2.2 → vitnify-0.2.3}/tests/test_verification.py +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "vitnify"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.3"
|
|
8
8
|
description = "Execution receipts for AI agents: contain, deterministically reconstruct, and cryptographically certify a run."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -184,15 +184,23 @@ def verify_certificate(cert: ExecutionCertificate, log: EventLog, key: bytes | N
|
|
|
184
184
|
cert.sig, hmac.new(key, cert.digest().encode(), hashlib.blake2b).hexdigest())
|
|
185
185
|
checks["sig_valid"] = sig_valid
|
|
186
186
|
|
|
187
|
-
# Capability consistency -- the receipt must PROVE
|
|
188
|
-
# carry a capability list
|
|
189
|
-
#
|
|
187
|
+
# Capability consistency -- the receipt must PROVE no ungranted tool executed,
|
|
188
|
+
# not just carry a capability list. Every tool call must be within the declared,
|
|
189
|
+
# signed-over capability set OR a CLEAN DENIAL (decision exactly "deny",
|
|
190
|
+
# case/space-insensitive, with no result). Keying only off decision=="allow"
|
|
191
|
+
# let a forged log slip an ungranted, result-bearing call through by relabelling
|
|
192
|
+
# the decision ("PERMIT", " allow", omitted...) -- F11. Failing closed on
|
|
193
|
+
# anything that is not a clean denial removes that, whatever string it carries.
|
|
190
194
|
granted = set(cert.capabilities)
|
|
195
|
+
|
|
196
|
+
def _clean_denial(p: dict) -> bool:
|
|
197
|
+
return (str(p.get("decision", "")).strip().lower() == "deny"
|
|
198
|
+
and "result" not in p and "result_hash" not in p)
|
|
199
|
+
|
|
191
200
|
checks["caps_consistent"] = all(
|
|
192
|
-
e.payload.get("tool") in granted
|
|
201
|
+
e.payload.get("tool") in granted or _clean_denial(e.payload)
|
|
193
202
|
for e in log.events
|
|
194
203
|
if e.kind == Kind.TOOL_CALL.value
|
|
195
|
-
and str(e.payload.get("decision", "")).lower() == "allow"
|
|
196
204
|
)
|
|
197
205
|
|
|
198
206
|
# A verified receipt must carry NO data field its version does not sign. The v1
|
|
@@ -74,3 +74,24 @@ def test_f5_signer_pinning():
|
|
|
74
74
|
assert verify_certificate(cert, log, pinned_pubkeys=[pub])["ok"] is True
|
|
75
75
|
# unpinned verification (no allow-list) still succeeds on integrity + self-sig
|
|
76
76
|
assert verify_certificate(cert, log)["ok"] is True
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_f11_containment_not_evaded_by_decision_string():
|
|
80
|
+
# F11: an ungranted, result-bearing tool call must be rejected no matter what
|
|
81
|
+
# string its decision carries -- containment is proven, not keyed off "allow".
|
|
82
|
+
priv, _ = gen_ed25519()
|
|
83
|
+
for decision in ("PERMIT", " allow", "allowed", "Allow", None):
|
|
84
|
+
payload = {"tool": "wire_transfer", "result": "SENT"}
|
|
85
|
+
if decision is not None:
|
|
86
|
+
payload["decision"] = decision
|
|
87
|
+
log = EventLog(); log.append(Kind.TOOL_CALL, payload)
|
|
88
|
+
cert, _ = issue_certificate("prog", ["read_docs"], log, priv=priv) # wire_transfer ungranted
|
|
89
|
+
assert verify_certificate(cert, log)["ok"] is False, repr(decision)
|
|
90
|
+
# an ungranted tool mislabelled DENY but still carrying a result is also rejected
|
|
91
|
+
log = EventLog(); log.append(Kind.TOOL_CALL, {"tool": "wire_transfer", "decision": "DENY", "result": "SENT"})
|
|
92
|
+
cert, _ = issue_certificate("prog", ["read_docs"], log, priv=priv)
|
|
93
|
+
assert verify_certificate(cert, log)["ok"] is False
|
|
94
|
+
# ...but a clean denial of an ungranted tool still verifies (containment working)
|
|
95
|
+
log = EventLog(); log.append(Kind.TOOL_CALL, {"tool": "send_email", "decision": "DENY"})
|
|
96
|
+
cert, _ = issue_certificate("prog", ["read_docs"], log, priv=priv)
|
|
97
|
+
assert verify_certificate(cert, log)["ok"] is True
|
|
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
|