attest-sdk 0.1.0b1__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.
- attest_sdk-0.1.0b1/.gitignore +79 -0
- attest_sdk-0.1.0b1/PKG-INFO +16 -0
- attest_sdk-0.1.0b1/README.md +148 -0
- attest_sdk-0.1.0b1/attest/__init__.py +108 -0
- attest_sdk-0.1.0b1/attest/checksum.py +15 -0
- attest_sdk-0.1.0b1/attest/client.py +419 -0
- attest_sdk-0.1.0b1/attest/integrations/__init__.py +1 -0
- attest_sdk-0.1.0b1/attest/integrations/anthropic_sdk.py +519 -0
- attest_sdk-0.1.0b1/attest/integrations/langgraph.py +688 -0
- attest_sdk-0.1.0b1/attest/integrations/openai_agents.py +323 -0
- attest_sdk-0.1.0b1/attest/scope.py +73 -0
- attest_sdk-0.1.0b1/attest/types.py +140 -0
- attest_sdk-0.1.0b1/attest/verifier.py +183 -0
- attest_sdk-0.1.0b1/pyproject.toml +21 -0
- attest_sdk-0.1.0b1/tests/test_anthropic_sdk.py +818 -0
- attest_sdk-0.1.0b1/tests/test_langgraph.py +639 -0
- attest_sdk-0.1.0b1/tests/test_openai_agents.py +344 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.so
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
pip-wheel-metadata/
|
|
20
|
+
share/python-wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
MANIFEST
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.coverage
|
|
27
|
+
*.cover
|
|
28
|
+
.hypothesis/
|
|
29
|
+
.venv/
|
|
30
|
+
venv/
|
|
31
|
+
ENV/
|
|
32
|
+
env/
|
|
33
|
+
.env
|
|
34
|
+
.env.local
|
|
35
|
+
|
|
36
|
+
# Node / TypeScript
|
|
37
|
+
node_modules/
|
|
38
|
+
dist/
|
|
39
|
+
build/
|
|
40
|
+
*.tsbuildinfo
|
|
41
|
+
.npm
|
|
42
|
+
npm-debug.log*
|
|
43
|
+
yarn-debug.log*
|
|
44
|
+
yarn-error.log*
|
|
45
|
+
|
|
46
|
+
# Go
|
|
47
|
+
*.o
|
|
48
|
+
*.a
|
|
49
|
+
*.so
|
|
50
|
+
*.exe
|
|
51
|
+
*.exe~
|
|
52
|
+
*.dll
|
|
53
|
+
*.so
|
|
54
|
+
*.dylib
|
|
55
|
+
server/attest
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
# IDEs
|
|
59
|
+
.vscode/
|
|
60
|
+
.idea/
|
|
61
|
+
*.swp
|
|
62
|
+
*.swo
|
|
63
|
+
*~
|
|
64
|
+
.DS_Store
|
|
65
|
+
*.sublime-project
|
|
66
|
+
*.sublime-workspace
|
|
67
|
+
|
|
68
|
+
# OS
|
|
69
|
+
.DS_Store
|
|
70
|
+
Thumbs.db
|
|
71
|
+
|
|
72
|
+
# Environment
|
|
73
|
+
.env
|
|
74
|
+
.env.local
|
|
75
|
+
.env.*.local
|
|
76
|
+
|
|
77
|
+
# Build outputs
|
|
78
|
+
bin/
|
|
79
|
+
out/
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: attest-sdk
|
|
3
|
+
Version: 0.1.0b1
|
|
4
|
+
Summary: Python SDK for the Attest cryptographic agent credential service
|
|
5
|
+
Requires-Python: >=3.11
|
|
6
|
+
Requires-Dist: httpx>=0.27
|
|
7
|
+
Requires-Dist: pyjwt[crypto]>=2.8
|
|
8
|
+
Provides-Extra: anthropic
|
|
9
|
+
Requires-Dist: anthropic>=0.25; extra == 'anthropic'
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
12
|
+
Requires-Dist: pytest-asyncio; extra == 'dev'
|
|
13
|
+
Requires-Dist: respx; extra == 'dev'
|
|
14
|
+
Provides-Extra: langgraph
|
|
15
|
+
Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
|
|
16
|
+
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# attest-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for the [Attest](https://github.com/attest-dev/attest) cryptographic agent credential service.
|
|
4
|
+
|
|
5
|
+
Attest issues RS256-signed JWTs to AI agents. Each token carries:
|
|
6
|
+
- `att_scope` — list of `"resource:action"` permission strings
|
|
7
|
+
- `att_chain` — ordered delegation lineage (list of JTIs)
|
|
8
|
+
- `att_depth` — delegation depth (0 = root)
|
|
9
|
+
- `att_intent` — SHA-256 hex of the original instruction
|
|
10
|
+
- `att_tid` — task tree UUID shared across the chain
|
|
11
|
+
- `att_uid` — originating human user ID
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pip install attest-sdk
|
|
17
|
+
|
|
18
|
+
# With LangGraph integration
|
|
19
|
+
pip install "attest-sdk[langgraph]"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Basic usage
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from attest import AttestClient, IssueParams, DelegateParams
|
|
26
|
+
|
|
27
|
+
client = AttestClient(
|
|
28
|
+
base_url="http://localhost:8080",
|
|
29
|
+
api_key="your-api-key",
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Issue a root credential
|
|
33
|
+
token = client.issue(IssueParams(
|
|
34
|
+
agent_id="orchestrator-v1",
|
|
35
|
+
user_id="user-42",
|
|
36
|
+
scope=["research:read", "gmail:send"],
|
|
37
|
+
instruction="Draft and send the quarterly report",
|
|
38
|
+
ttl_seconds=3600,
|
|
39
|
+
))
|
|
40
|
+
print(token.claims.att_tid) # task tree UUID
|
|
41
|
+
print(token.claims.att_scope) # ["research:read", "gmail:send"]
|
|
42
|
+
|
|
43
|
+
# Delegate to a child agent (scope must be a subset)
|
|
44
|
+
child = client.delegate(DelegateParams(
|
|
45
|
+
parent_token=token.token,
|
|
46
|
+
child_agent="email-agent-v1",
|
|
47
|
+
child_scope=["gmail:send"],
|
|
48
|
+
))
|
|
49
|
+
|
|
50
|
+
# Offline verify — fetch JWKS once, reuse it
|
|
51
|
+
jwks = client.fetch_jwks()
|
|
52
|
+
result = client.verify(token.token, jwks=jwks) # no server call needed
|
|
53
|
+
if result.valid:
|
|
54
|
+
print("Issuer:", result.claims.iss)
|
|
55
|
+
else:
|
|
56
|
+
print("Invalid:", result.warnings)
|
|
57
|
+
|
|
58
|
+
# Check revocation
|
|
59
|
+
is_revoked = client.check_revoked(token.claims.jti)
|
|
60
|
+
|
|
61
|
+
# Revoke (cascades to all descendants)
|
|
62
|
+
client.revoke(token.claims.jti, revoked_by="orchestrator")
|
|
63
|
+
|
|
64
|
+
# Audit trail for the whole task tree
|
|
65
|
+
chain = client.audit(token.claims.att_tid)
|
|
66
|
+
for event in chain.events:
|
|
67
|
+
print(event.event_type, event.agent_id, event.created_at)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Async client
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
import asyncio
|
|
74
|
+
from attest import AsyncAttestClient, IssueParams
|
|
75
|
+
|
|
76
|
+
async def main():
|
|
77
|
+
async with AsyncAttestClient(api_key="your-api-key") as client:
|
|
78
|
+
token = await client.issue(IssueParams(
|
|
79
|
+
agent_id="async-agent",
|
|
80
|
+
user_id="user-1",
|
|
81
|
+
scope=["files:read"],
|
|
82
|
+
instruction="Read the file",
|
|
83
|
+
))
|
|
84
|
+
jwks = await client.fetch_jwks()
|
|
85
|
+
result = await client.verify(token.token, jwks=jwks)
|
|
86
|
+
print(result.valid)
|
|
87
|
+
|
|
88
|
+
asyncio.run(main())
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## LangGraph integration
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from typing import TypedDict
|
|
95
|
+
from attest import AttestClient
|
|
96
|
+
from attest.integrations.langgraph import AttestState, attest_tool, AttestNodes
|
|
97
|
+
|
|
98
|
+
client = AttestClient(api_key="your-api-key")
|
|
99
|
+
|
|
100
|
+
# 1. Extend AttestState with your own fields
|
|
101
|
+
class MyState(AttestState):
|
|
102
|
+
messages: list
|
|
103
|
+
instruction: str
|
|
104
|
+
user_id: str
|
|
105
|
+
|
|
106
|
+
# 2. Issue at graph entry — stores JWT in state["attest_tokens"]["orchestrator-v1"]
|
|
107
|
+
graph.add_node("issue", AttestNodes.issue(
|
|
108
|
+
client=client,
|
|
109
|
+
agent_id="orchestrator-v1",
|
|
110
|
+
scope=["research:read", "gmail:send"],
|
|
111
|
+
instruction_key="instruction",
|
|
112
|
+
user_id_key="user_id",
|
|
113
|
+
))
|
|
114
|
+
|
|
115
|
+
# 3. Enforce scope at tool call — raises AttestScopeError if not covered
|
|
116
|
+
@attest_tool(scope="gmail:send", agent_id="email-agent-v1")
|
|
117
|
+
def send_email(state: MyState, to: str, body: str) -> str:
|
|
118
|
+
...
|
|
119
|
+
|
|
120
|
+
# 4. Delegate when spawning a sub-agent
|
|
121
|
+
graph.add_node("spawn_email_agent", AttestNodes.delegate(
|
|
122
|
+
client=client,
|
|
123
|
+
parent_agent_id="orchestrator-v1",
|
|
124
|
+
child_agent_id="email-agent-v1",
|
|
125
|
+
child_scope=["gmail:send"],
|
|
126
|
+
))
|
|
127
|
+
|
|
128
|
+
# 5. Revoke at graph teardown
|
|
129
|
+
graph.add_node("cleanup", AttestNodes.revoke(
|
|
130
|
+
client=client,
|
|
131
|
+
agent_id="orchestrator-v1",
|
|
132
|
+
))
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Offline verification note
|
|
136
|
+
|
|
137
|
+
Once you have fetched the JWKS with `client.fetch_jwks()`, you can verify
|
|
138
|
+
any token from the same server without making additional network calls:
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
jwks = client.fetch_jwks() # one network call
|
|
142
|
+
|
|
143
|
+
for token_str in incoming_tokens:
|
|
144
|
+
result = client.verify(token_str, jwks=jwks) # pure local crypto
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
The public key is stable for the lifetime of the server instance; cache it
|
|
148
|
+
as long as your process runs.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Attest Python SDK.
|
|
2
|
+
|
|
3
|
+
Attest issues RS256-signed JWTs to AI agents, carrying cryptographic scope,
|
|
4
|
+
delegation lineage, and task-tree provenance.
|
|
5
|
+
|
|
6
|
+
Quick start::
|
|
7
|
+
|
|
8
|
+
from attest import AttestClient, IssueParams
|
|
9
|
+
|
|
10
|
+
client = AttestClient(api_key="your-api-key")
|
|
11
|
+
token = client.issue(IssueParams(
|
|
12
|
+
agent_id="my-agent",
|
|
13
|
+
user_id="user-123",
|
|
14
|
+
scope=["files:read", "files:write"],
|
|
15
|
+
instruction="Summarise the quarterly report",
|
|
16
|
+
))
|
|
17
|
+
print(token.claims.att_tid) # task tree UUID
|
|
18
|
+
print(token.claims.att_scope) # ["files:read", "files:write"]
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from attest.checksum import compute_agent_checksum
|
|
22
|
+
from attest.client import (
|
|
23
|
+
AsyncAttestClient,
|
|
24
|
+
AttestAPIError,
|
|
25
|
+
AttestClient,
|
|
26
|
+
AttestError,
|
|
27
|
+
AttestScopeError,
|
|
28
|
+
AttestVerifyError,
|
|
29
|
+
)
|
|
30
|
+
from attest.scope import is_subset, normalise_scope, parse_scope
|
|
31
|
+
from attest.verifier import AttestVerifier
|
|
32
|
+
from attest.types import (
|
|
33
|
+
AuditChain,
|
|
34
|
+
AuditEvent,
|
|
35
|
+
DelegateParams,
|
|
36
|
+
DelegatedToken,
|
|
37
|
+
IssueParams,
|
|
38
|
+
VerifyResult,
|
|
39
|
+
AttestClaims,
|
|
40
|
+
AttestToken,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
# Framework integrations (lazy — only usable when the framework is installed).
|
|
44
|
+
from attest.integrations.langgraph import (
|
|
45
|
+
AttestNodes,
|
|
46
|
+
AttestState,
|
|
47
|
+
AttestStateGraph,
|
|
48
|
+
current_attest_token,
|
|
49
|
+
attest_tool,
|
|
50
|
+
)
|
|
51
|
+
from attest.integrations.openai_agents import (
|
|
52
|
+
AttestContext,
|
|
53
|
+
AttestRunHooks,
|
|
54
|
+
attest_tool_openai,
|
|
55
|
+
)
|
|
56
|
+
from attest.integrations.anthropic_sdk import (
|
|
57
|
+
AttestSession,
|
|
58
|
+
AsyncAttestSession,
|
|
59
|
+
current_attest_session,
|
|
60
|
+
attest_tool_anthropic,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
__version__ = "0.1.0"
|
|
64
|
+
|
|
65
|
+
__all__ = [
|
|
66
|
+
# Clients
|
|
67
|
+
"AttestClient",
|
|
68
|
+
"AsyncAttestClient",
|
|
69
|
+
# Errors
|
|
70
|
+
"AttestError",
|
|
71
|
+
"AttestAPIError",
|
|
72
|
+
"AttestScopeError",
|
|
73
|
+
"AttestVerifyError",
|
|
74
|
+
# Types
|
|
75
|
+
"AttestClaims",
|
|
76
|
+
"AttestToken",
|
|
77
|
+
"DelegatedToken",
|
|
78
|
+
"VerifyResult",
|
|
79
|
+
"AuditChain",
|
|
80
|
+
"AuditEvent",
|
|
81
|
+
"IssueParams",
|
|
82
|
+
"DelegateParams",
|
|
83
|
+
# Scope utilities
|
|
84
|
+
"is_subset",
|
|
85
|
+
"parse_scope",
|
|
86
|
+
"normalise_scope",
|
|
87
|
+
# LangGraph integration
|
|
88
|
+
"AttestState",
|
|
89
|
+
"AttestNodes",
|
|
90
|
+
"AttestStateGraph",
|
|
91
|
+
"attest_tool",
|
|
92
|
+
"current_attest_token",
|
|
93
|
+
# OpenAI Agents SDK integration
|
|
94
|
+
"AttestContext",
|
|
95
|
+
"AttestRunHooks",
|
|
96
|
+
"attest_tool_openai",
|
|
97
|
+
# Anthropic SDK integration
|
|
98
|
+
"AttestSession",
|
|
99
|
+
"AsyncAttestSession",
|
|
100
|
+
"current_attest_session",
|
|
101
|
+
"attest_tool_anthropic",
|
|
102
|
+
# Checksum utility
|
|
103
|
+
"compute_agent_checksum",
|
|
104
|
+
# Standalone verifier
|
|
105
|
+
"AttestVerifier",
|
|
106
|
+
# Version
|
|
107
|
+
"__version__",
|
|
108
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Agent checksum computation for att_ack claim."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def compute_agent_checksum(system_prompt: str, tools: list[dict] | None = None) -> str:
|
|
8
|
+
"""SHA-256 of canonical JSON of {system_prompt, tools}.
|
|
9
|
+
|
|
10
|
+
Uses sort_keys=True and compact separators so the result is deterministic
|
|
11
|
+
regardless of key insertion order or Python version.
|
|
12
|
+
"""
|
|
13
|
+
payload = {"system_prompt": system_prompt, "tools": tools or []}
|
|
14
|
+
canonical = json.dumps(payload, sort_keys=True, separators=(",", ":"))
|
|
15
|
+
return hashlib.sha256(canonical.encode()).hexdigest()
|