chitmark 0.6.0__tar.gz → 0.6.2__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.
- {chitmark-0.6.0 → chitmark-0.6.2}/.gitignore +5 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/PKG-INFO +2 -2
- {chitmark-0.6.0 → chitmark-0.6.2}/pyproject.toml +7 -1
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/client.py +6 -1
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/types.py +1 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/verify_token.py +27 -9
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_verify_token.py +29 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/LICENSE +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/README.md +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/__init__.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/errors.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/pii.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/src/chitmark/py.typed +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_client.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_client_extra.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_errors.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_pii.py +0 -0
- {chitmark-0.6.0 → chitmark-0.6.2}/tests/test_verify_token_extra.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: chitmark
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Official Python SDK for Chitmark by Open Agent Ledger: trust decisions on agent-mediated actions (verify, feedback, challenge)
|
|
5
5
|
Project-URL: Homepage, https://chitmark.com
|
|
6
6
|
Project-URL: Documentation, https://chitmark.com/docs
|
|
7
7
|
Author-email: Open Agent Ledger <dev@chitmark.com>
|
|
8
8
|
License: Proprietary: no open-source grant. Copyright (c) 2026 Open Agent Ledger. All rights reserved.
|
|
9
9
|
License-File: LICENSE
|
|
10
|
-
Keywords: agent-actions,anti-abuse,chitmark,open-agent-ledger,trust-decisions
|
|
10
|
+
Keywords: agent-actions,allow-challenge-deny,anti-abuse,chitmark,credit-abuse,free-tier-abuse,free-trial-abuse,open-agent-ledger,signup-abuse,trial-farming,trust-decisions
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "chitmark"
|
|
7
|
-
version = "0.6.
|
|
7
|
+
version = "0.6.2"
|
|
8
8
|
description = "Official Python SDK for Chitmark by Open Agent Ledger: trust decisions on agent-mediated actions (verify, feedback, challenge)"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Proprietary: no open-source grant. Copyright (c) 2026 Open Agent Ledger. All rights reserved." }
|
|
@@ -18,6 +18,12 @@ keywords = [
|
|
|
18
18
|
"agent-actions",
|
|
19
19
|
"trust-decisions",
|
|
20
20
|
"anti-abuse",
|
|
21
|
+
"free-trial-abuse",
|
|
22
|
+
"trial-farming",
|
|
23
|
+
"signup-abuse",
|
|
24
|
+
"credit-abuse",
|
|
25
|
+
"free-tier-abuse",
|
|
26
|
+
"allow-challenge-deny",
|
|
21
27
|
]
|
|
22
28
|
classifiers = [
|
|
23
29
|
"Development Status :: 3 - Alpha",
|
|
@@ -272,7 +272,12 @@ class Chitmark:
|
|
|
272
272
|
body["idempotencyKey"] = idempotency_key
|
|
273
273
|
return cast(
|
|
274
274
|
FeedbackResponse,
|
|
275
|
-
self._request(
|
|
275
|
+
self._request(
|
|
276
|
+
"POST",
|
|
277
|
+
"/v1/feedback",
|
|
278
|
+
body=body,
|
|
279
|
+
idempotency_key=idempotency_key,
|
|
280
|
+
),
|
|
276
281
|
)
|
|
277
282
|
|
|
278
283
|
def challenge(
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
"""Client-side verdictToken verification (MVP-016).
|
|
2
2
|
|
|
3
3
|
A verdictToken is an ES256 JWT decision receipt minted by the edge and bound
|
|
4
|
-
to session (``sub``), origin (``aud``),
|
|
5
|
-
module validates shape, signature (against
|
|
6
|
-
``GET /.well-known/jwks.json``), expiry, session
|
|
7
|
-
expected
|
|
8
|
-
|
|
4
|
+
to session (``sub``), origin hint (``aud``), issuing tenant (``tid``), and
|
|
5
|
+
``jti`` with a short ``exp``. This module validates shape, signature (against
|
|
6
|
+
the JWKS served from ``GET /.well-known/jwks.json``), expiry, session
|
|
7
|
+
binding, and, when expected values are supplied, tenant and origin binding.
|
|
8
|
+
``aud`` reflects request headers and is attacker influenced: always verify
|
|
9
|
+
``session`` and ``tenant_id``, never ``aud`` alone. Rejection reasons are
|
|
10
|
+
surfaced as ``VerdictTokenError.code``.
|
|
9
11
|
|
|
10
12
|
Requires the optional extras ``PyJWT`` and ``cryptography``
|
|
11
13
|
(``uv sync --extra verdict``).
|
|
@@ -46,6 +48,7 @@ def verify_verdict_token(
|
|
|
46
48
|
token: str,
|
|
47
49
|
*,
|
|
48
50
|
session: str | None = None,
|
|
51
|
+
tenant_id: str | None = None,
|
|
49
52
|
aud: str | None = None,
|
|
50
53
|
jwks: Mapping[str, Any] | None = None,
|
|
51
54
|
jwks_url: str = DEFAULT_JWKS_URL,
|
|
@@ -57,7 +60,8 @@ def verify_verdict_token(
|
|
|
57
60
|
Args:
|
|
58
61
|
token: The ES256 JWT from a verify verdict.
|
|
59
62
|
session: Expected session; token ``sub`` must equal this.
|
|
60
|
-
|
|
63
|
+
tenant_id: Expected issuing tenant; token ``tid`` must equal this.
|
|
64
|
+
aud: Expected origin hint; token ``aud`` must equal this when provided.
|
|
61
65
|
jwks: Pre-fetched JWKS mapping; skips the network fetch.
|
|
62
66
|
jwks_url: JWKS endpoint; defaults to the production API.
|
|
63
67
|
http_client: Reusable ``httpx.Client`` for the JWKS fetch.
|
|
@@ -65,9 +69,10 @@ def verify_verdict_token(
|
|
|
65
69
|
|
|
66
70
|
Raises:
|
|
67
71
|
VerdictTokenError: with a machine-readable ``code``:
|
|
68
|
-
``malformed_token``, ``wrong_algorithm``, ``
|
|
69
|
-
``
|
|
70
|
-
``not_yet_valid``, ``wrong_session``,
|
|
72
|
+
``malformed_token``, ``wrong_algorithm``, ``wrong_type``,
|
|
73
|
+
``jwks_unavailable``, ``unknown_kid``, ``invalid_signature``,
|
|
74
|
+
``expired``, ``not_yet_valid``, ``wrong_session``,
|
|
75
|
+
``wrong_tenant``, or ``wrong_aud``.
|
|
71
76
|
"""
|
|
72
77
|
if pyjwt is None: # pragma: no cover
|
|
73
78
|
raise VerdictTokenError(
|
|
@@ -84,6 +89,14 @@ def verify_verdict_token(
|
|
|
84
89
|
f"verdictToken must use ES256, got {alg!r}",
|
|
85
90
|
code="wrong_algorithm",
|
|
86
91
|
)
|
|
92
|
+
# RFC 8725 section 3.11: explicit typing keeps this receipt distinct
|
|
93
|
+
# from any other JWT a tenant might accept. The edge always mints
|
|
94
|
+
# typ "JWT".
|
|
95
|
+
if header.get("typ") != "JWT":
|
|
96
|
+
raise VerdictTokenError(
|
|
97
|
+
f"verdictToken must be a JWT, got {header.get('typ')!r}",
|
|
98
|
+
code="wrong_type",
|
|
99
|
+
)
|
|
87
100
|
kid = header.get("kid")
|
|
88
101
|
if not isinstance(kid, str) or not kid:
|
|
89
102
|
raise VerdictTokenError(
|
|
@@ -132,6 +145,11 @@ def verify_verdict_token(
|
|
|
132
145
|
"verdictToken was minted for a different session",
|
|
133
146
|
code="wrong_session",
|
|
134
147
|
)
|
|
148
|
+
if tenant_id is not None and claims.get("tid") != tenant_id:
|
|
149
|
+
raise VerdictTokenError(
|
|
150
|
+
"verdictToken was minted for a different tenant",
|
|
151
|
+
code="wrong_tenant",
|
|
152
|
+
)
|
|
135
153
|
if aud is not None and claims.get("aud") != aud:
|
|
136
154
|
raise VerdictTokenError(
|
|
137
155
|
"verdictToken was minted for a different origin",
|
|
@@ -94,6 +94,7 @@ def _mint(keys: dict, **overrides: object) -> str:
|
|
|
94
94
|
"confidence": 0.56,
|
|
95
95
|
"aud": "https://app.acme.com",
|
|
96
96
|
"sub": "sess_token_a",
|
|
97
|
+
"tid": "org_unit",
|
|
97
98
|
"jti": "jti_1",
|
|
98
99
|
"iat": now,
|
|
99
100
|
"exp": now + 300,
|
|
@@ -144,6 +145,23 @@ def test_rejects_wrong_audience(keys: dict) -> None:
|
|
|
144
145
|
assert exc.value.code == "wrong_aud"
|
|
145
146
|
|
|
146
147
|
|
|
148
|
+
def test_rejects_wrong_tenant(keys: dict) -> None:
|
|
149
|
+
token = _mint(keys, tid="org_other")
|
|
150
|
+
|
|
151
|
+
with pytest.raises(VerdictTokenError) as exc:
|
|
152
|
+
verify_verdict_token(token, tenant_id="org_unit", jwks=_jwks(keys))
|
|
153
|
+
assert exc.value.code == "wrong_tenant"
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def test_accepts_expected_tenant(keys: dict) -> None:
|
|
157
|
+
token = _mint(keys, tid="org_unit", sub="sess_token_a")
|
|
158
|
+
|
|
159
|
+
claims = verify_verdict_token(
|
|
160
|
+
token, session="sess_token_a", tenant_id="org_unit", jwks=_jwks(keys)
|
|
161
|
+
)
|
|
162
|
+
assert claims["tid"] == "org_unit"
|
|
163
|
+
|
|
164
|
+
|
|
147
165
|
def test_rejects_expired_token(keys: dict) -> None:
|
|
148
166
|
token = _mint(keys, exp=int(time.time()) - 120)
|
|
149
167
|
|
|
@@ -184,6 +202,17 @@ def test_rejects_wrong_algorithm(keys: dict) -> None:
|
|
|
184
202
|
assert exc.value.code == "wrong_algorithm"
|
|
185
203
|
|
|
186
204
|
|
|
205
|
+
def test_rejects_missing_jwt_typ(keys: dict) -> None:
|
|
206
|
+
token = _mint(keys)
|
|
207
|
+
header, payload, signature = token.split(".")
|
|
208
|
+
wrong_header = _b64url_json({"alg": "ES256", "typ": "at+jwt", "kid": keys["kid"]})
|
|
209
|
+
wrong = f"{wrong_header}.{payload}.{signature}"
|
|
210
|
+
|
|
211
|
+
with pytest.raises(VerdictTokenError) as exc:
|
|
212
|
+
verify_verdict_token(wrong, jwks=_jwks(keys))
|
|
213
|
+
assert exc.value.code == "wrong_type"
|
|
214
|
+
|
|
215
|
+
|
|
187
216
|
def test_rejects_malformed_token() -> None:
|
|
188
217
|
with pytest.raises(VerdictTokenError) as exc:
|
|
189
218
|
verify_verdict_token("not-a-jwt", jwks={"keys": []})
|
|
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
|