lockstock-integrations 1.0.1__py3-none-any.whl → 1.1.0__py3-none-any.whl
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.
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.0.dist-info}/METADATA +1 -1
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.0.dist-info}/RECORD +4 -4
- lockstock_openai/guardrails.py +213 -100
- {lockstock_integrations-1.0.1.dist-info → lockstock_integrations-1.1.0.dist-info}/WHEEL +0 -0
|
@@ -9,8 +9,8 @@ lockstock_langgraph/__init__.py,sha256=7fBIYNCQygvUvG7IQOVY042sNNmu4UwkJoy0VLQn4
|
|
|
9
9
|
lockstock_langgraph/checkpointer.py,sha256=XJ2c_92OSBq4aNpOT112nTy3yI3stFcSCb8OTx04nos,6317
|
|
10
10
|
lockstock_langgraph/middleware.py,sha256=ZSwoxdkn3cC6aIBnMgd1jYGHYKa1yQXRwnts3Ds4sfQ,9174
|
|
11
11
|
lockstock_openai/__init__.py,sha256=Pc4phRUJEqrnREggyGJnc1HDKa7puVAR6-G9WZ4dtXM,402
|
|
12
|
-
lockstock_openai/guardrails.py,sha256=
|
|
12
|
+
lockstock_openai/guardrails.py,sha256=fy84bDrhy6CVk2b4_yOl5XTxw9Lp1HT4Mw9WcaAHOsg,11920
|
|
13
13
|
lockstock_openai/tracing.py,sha256=VQWzG0y6t8q5rJZFbbP-bi9tsV0KaUqqH4Kr9-vr_e8,6025
|
|
14
|
-
lockstock_integrations-1.0.
|
|
15
|
-
lockstock_integrations-1.0.
|
|
16
|
-
lockstock_integrations-1.0.
|
|
14
|
+
lockstock_integrations-1.1.0.dist-info/METADATA,sha256=2oQWjepWJDR6-Q_L5tOmiaDCg4gGm6K5-JcEIRYtRSE,5629
|
|
15
|
+
lockstock_integrations-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
16
|
+
lockstock_integrations-1.1.0.dist-info/RECORD,,
|
lockstock_openai/guardrails.py
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
1
|
"""
|
|
2
2
|
LockStock OpenAI Agents SDK Guardrails
|
|
3
3
|
---------------------------------------
|
|
4
|
-
|
|
4
|
+
Chain-based guardrail implementations for OpenAI Agents SDK.
|
|
5
|
+
|
|
6
|
+
NO SECRETS. ONLY CHAIN STATE.
|
|
7
|
+
|
|
8
|
+
The Guard daemon tracks chain state (hash, matrix, sequence).
|
|
9
|
+
When signing, Guard uses current_hash as the HMAC key.
|
|
5
10
|
|
|
6
11
|
Usage:
|
|
7
12
|
from agents import Agent
|
|
8
13
|
from lockstock_openai import LockStockGuardrail
|
|
9
14
|
|
|
10
|
-
|
|
15
|
+
# Chain Authority Mode (recommended)
|
|
16
|
+
guardrail = LockStockGuardrail.from_liberty("agent_abc123")
|
|
11
17
|
|
|
12
18
|
agent = Agent(
|
|
13
19
|
name="my-agent",
|
|
@@ -16,11 +22,10 @@ Usage:
|
|
|
16
22
|
)
|
|
17
23
|
"""
|
|
18
24
|
|
|
19
|
-
from typing import Any, Dict, Optional
|
|
25
|
+
from typing import Any, Dict, Optional
|
|
20
26
|
from dataclasses import dataclass
|
|
21
27
|
|
|
22
|
-
from lockstock_core import
|
|
23
|
-
from lockstock_core.types import map_tool_to_capability, VerifyStatus
|
|
28
|
+
from lockstock_core.types import map_tool_to_capability
|
|
24
29
|
|
|
25
30
|
|
|
26
31
|
@dataclass
|
|
@@ -33,41 +38,56 @@ class GuardrailResult:
|
|
|
33
38
|
|
|
34
39
|
class LockStockGuardrail:
|
|
35
40
|
"""
|
|
36
|
-
|
|
41
|
+
Chain-based guardrail for OpenAI Agents SDK.
|
|
42
|
+
|
|
43
|
+
NO SECRETS STORED. Chain state is the authentication.
|
|
37
44
|
|
|
38
|
-
|
|
45
|
+
The Guard daemon:
|
|
46
|
+
1. Tracks current chain state (hash, matrix, sequence)
|
|
47
|
+
2. Uses current_hash as HMAC key for signatures
|
|
48
|
+
3. Returns signatures (not secrets!)
|
|
39
49
|
"""
|
|
40
50
|
|
|
41
51
|
name = "lockstock"
|
|
42
|
-
description = "LockStock
|
|
52
|
+
description = "LockStock chain-based authorization guardrail"
|
|
43
53
|
|
|
44
54
|
def __init__(
|
|
45
55
|
self,
|
|
46
56
|
agent_id: str,
|
|
47
|
-
|
|
48
|
-
api_key: Optional[str] = None,
|
|
57
|
+
guard_socket: str = "/var/run/lockstock-guard/guard.sock",
|
|
49
58
|
endpoint: str = "https://lockstock-api-i9kp.onrender.com",
|
|
50
59
|
block_on_failure: bool = True
|
|
51
60
|
):
|
|
52
61
|
"""
|
|
53
|
-
Initialize
|
|
62
|
+
Initialize chain-based guardrail.
|
|
54
63
|
|
|
55
64
|
Args:
|
|
56
65
|
agent_id: The agent's unique identifier
|
|
57
|
-
|
|
58
|
-
api_key: Admin API key
|
|
66
|
+
guard_socket: Path to Guard daemon Unix socket
|
|
59
67
|
endpoint: LockStock API endpoint
|
|
60
68
|
block_on_failure: If True, block execution on auth failure
|
|
69
|
+
|
|
70
|
+
NO SECRET PARAMETER! Secrets don't exist in this system.
|
|
61
71
|
"""
|
|
62
|
-
self.client = LockStockClient(
|
|
63
|
-
agent_id=agent_id,
|
|
64
|
-
secret=secret,
|
|
65
|
-
api_key=api_key,
|
|
66
|
-
endpoint=endpoint
|
|
67
|
-
)
|
|
68
72
|
self.agent_id = agent_id
|
|
73
|
+
self.guard_socket = guard_socket
|
|
74
|
+
self.endpoint = endpoint
|
|
69
75
|
self.block_on_failure = block_on_failure
|
|
70
76
|
|
|
77
|
+
# Chain state (cached locally, synced from Guard)
|
|
78
|
+
self.current_hash: Optional[str] = None
|
|
79
|
+
self.current_sequence: int = 0
|
|
80
|
+
self.current_matrix: Optional[Dict[str, int]] = None
|
|
81
|
+
self._chain_initialized = False
|
|
82
|
+
|
|
83
|
+
# Guard client (for chain operations)
|
|
84
|
+
from lockstock_guard.client import LibertyClient
|
|
85
|
+
self._guard = LibertyClient(socket_path=guard_socket)
|
|
86
|
+
|
|
87
|
+
# HTTP client (for server communication)
|
|
88
|
+
import httpx
|
|
89
|
+
self._http = httpx.AsyncClient(timeout=30.0)
|
|
90
|
+
|
|
71
91
|
@classmethod
|
|
72
92
|
def from_liberty(
|
|
73
93
|
cls,
|
|
@@ -77,52 +97,77 @@ class LockStockGuardrail:
|
|
|
77
97
|
block_on_failure: bool = True
|
|
78
98
|
):
|
|
79
99
|
"""
|
|
80
|
-
Create guardrail
|
|
100
|
+
Create guardrail using Chain Authority Mode.
|
|
101
|
+
|
|
102
|
+
NO SECRET RETRIEVAL. NO SECRET STORAGE.
|
|
81
103
|
|
|
82
|
-
|
|
83
|
-
are hardware-bound and managed by the lockstock-guard daemon.
|
|
104
|
+
The Guard daemon manages chain state. We just track it locally.
|
|
84
105
|
|
|
85
106
|
Args:
|
|
86
|
-
agent_id:
|
|
87
|
-
socket_path: Path to
|
|
107
|
+
agent_id: Agent identifier (from provisioning)
|
|
108
|
+
socket_path: Path to Guard daemon Unix socket
|
|
88
109
|
endpoint: LockStock API endpoint
|
|
89
|
-
block_on_failure:
|
|
110
|
+
block_on_failure: Whether to block on auth failure
|
|
90
111
|
|
|
91
112
|
Returns:
|
|
92
|
-
LockStockGuardrail instance
|
|
113
|
+
LockStockGuardrail instance using chain-based auth
|
|
93
114
|
|
|
94
115
|
Example:
|
|
95
116
|
>>> guardrail = LockStockGuardrail.from_liberty("agent_abc123")
|
|
96
117
|
>>> agent = Agent(guardrails=[guardrail])
|
|
97
118
|
"""
|
|
98
|
-
import json
|
|
99
|
-
from lockstock_guard.client import LibertyClient
|
|
100
|
-
|
|
101
|
-
# Retrieve secret from Guard daemon
|
|
102
|
-
guard_client = LibertyClient(socket_path=socket_path)
|
|
103
|
-
try:
|
|
104
|
-
secret_data = json.loads(guard_client.get(agent_id))
|
|
105
|
-
secret = secret_data["value"]
|
|
106
|
-
finally:
|
|
107
|
-
guard_client.close()
|
|
108
|
-
|
|
109
|
-
# Create guardrail with retrieved secret
|
|
110
119
|
return cls(
|
|
111
120
|
agent_id=agent_id,
|
|
112
|
-
|
|
121
|
+
guard_socket=socket_path,
|
|
113
122
|
endpoint=endpoint,
|
|
114
123
|
block_on_failure=block_on_failure
|
|
115
124
|
)
|
|
116
125
|
|
|
126
|
+
async def _ensure_chain_initialized(self):
|
|
127
|
+
"""
|
|
128
|
+
Sync chain state from Guard daemon on first use.
|
|
129
|
+
|
|
130
|
+
Guard fetches current state from server and caches it.
|
|
131
|
+
We sync that cached state to our local tracking.
|
|
132
|
+
"""
|
|
133
|
+
if self._chain_initialized:
|
|
134
|
+
return
|
|
135
|
+
|
|
136
|
+
try:
|
|
137
|
+
# Ask Guard to sync from server
|
|
138
|
+
sync_result = self._guard.sync_chain(self.agent_id)
|
|
139
|
+
|
|
140
|
+
if not sync_result.get("success"):
|
|
141
|
+
raise Exception(f"Chain sync failed: {sync_result}")
|
|
142
|
+
|
|
143
|
+
# Update local chain state cache
|
|
144
|
+
self.current_hash = sync_result.get("current_hash")
|
|
145
|
+
self.current_sequence = sync_result.get("sequence", 0)
|
|
146
|
+
self.current_matrix = sync_result.get("state_matrix")
|
|
147
|
+
self._chain_initialized = True
|
|
148
|
+
|
|
149
|
+
except Exception as e:
|
|
150
|
+
raise Exception(
|
|
151
|
+
f"Failed to initialize chain state for {self.agent_id}. "
|
|
152
|
+
f"Ensure Guard daemon is running and agent is provisioned. "
|
|
153
|
+
f"Error: {e}"
|
|
154
|
+
)
|
|
155
|
+
|
|
117
156
|
async def validate(
|
|
118
157
|
self,
|
|
119
158
|
agent: Any,
|
|
120
159
|
message: Dict[str, Any]
|
|
121
160
|
) -> GuardrailResult:
|
|
122
161
|
"""
|
|
123
|
-
Validate a message
|
|
162
|
+
Validate a message using chain-based authentication.
|
|
163
|
+
|
|
164
|
+
Flow:
|
|
165
|
+
1. Check if action requires authorization
|
|
166
|
+
2. Ask Guard to sign (Guard uses current_hash as HMAC key)
|
|
167
|
+
3. Send Guard's signature to server for verification
|
|
168
|
+
4. Update local chain state if verified
|
|
124
169
|
|
|
125
|
-
|
|
170
|
+
NO SECRETS are involved. Only chain state.
|
|
126
171
|
|
|
127
172
|
Args:
|
|
128
173
|
agent: The agent instance
|
|
@@ -134,42 +179,146 @@ class LockStockGuardrail:
|
|
|
134
179
|
# Check for tool calls in assistant messages
|
|
135
180
|
if message.get("role") == "assistant" and "tool_calls" in message:
|
|
136
181
|
for tool_call in message.get("tool_calls", []):
|
|
137
|
-
if "function" in tool_call:
|
|
138
|
-
|
|
182
|
+
if "function" not in tool_call:
|
|
183
|
+
continue
|
|
184
|
+
|
|
185
|
+
function_name = tool_call["function"].get("name", "")
|
|
186
|
+
|
|
187
|
+
# Map tool to capability
|
|
188
|
+
capability = map_tool_to_capability(function_name)
|
|
189
|
+
|
|
190
|
+
# Ensure chain state is synced
|
|
191
|
+
await self._ensure_chain_initialized()
|
|
192
|
+
|
|
193
|
+
try:
|
|
194
|
+
# ========================================================
|
|
195
|
+
# CHAIN AUTHORITY MODE
|
|
196
|
+
# ========================================================
|
|
197
|
+
# Guard daemon:
|
|
198
|
+
# 1. Looks up current chain state for this agent
|
|
199
|
+
# 2. Calculates: new_matrix = current_matrix × generator[task]
|
|
200
|
+
# 3. Signs with: HMAC(current_hash, message)
|
|
201
|
+
# ↑ current_hash IS the key! No separate secret!
|
|
202
|
+
# 4. Calculates: new_hash = SHA256(...)
|
|
203
|
+
# 5. Returns: {signature, new_hash, new_matrix, ...}
|
|
204
|
+
# ========================================================
|
|
205
|
+
|
|
206
|
+
sign_result = self._guard.sign_and_advance(
|
|
207
|
+
agent_id=self.agent_id,
|
|
208
|
+
task=capability,
|
|
209
|
+
parent_hash=self.current_hash
|
|
210
|
+
)
|
|
139
211
|
|
|
140
|
-
#
|
|
141
|
-
|
|
212
|
+
# Guard returned:
|
|
213
|
+
# - signature: HMAC computed using current_hash as key
|
|
214
|
+
# - new_hash: Next chain head
|
|
215
|
+
# - new_sequence: Incremented sequence
|
|
216
|
+
# - new_matrix: After applying generator
|
|
217
|
+
# - timestamp: Unix timestamp used in signature
|
|
218
|
+
|
|
219
|
+
except Exception as e:
|
|
220
|
+
return GuardrailResult(
|
|
221
|
+
passed=False,
|
|
222
|
+
reason=f"Guard signing failed: {e}"
|
|
223
|
+
)
|
|
142
224
|
|
|
143
|
-
|
|
144
|
-
|
|
225
|
+
# Send Guard's signature to LockStock server for verification
|
|
226
|
+
try:
|
|
227
|
+
verify_result = await self._verify_with_server(
|
|
145
228
|
task=capability,
|
|
146
|
-
|
|
229
|
+
signature=sign_result["signature"],
|
|
230
|
+
parent_hash=self.current_hash,
|
|
231
|
+
state_matrix=sign_result["new_matrix"],
|
|
232
|
+
timestamp=sign_result["timestamp"],
|
|
233
|
+
sequence=sign_result["new_sequence"]
|
|
147
234
|
)
|
|
148
235
|
|
|
149
|
-
if not
|
|
236
|
+
if not verify_result["authorized"]:
|
|
150
237
|
if self.block_on_failure:
|
|
151
238
|
return GuardrailResult(
|
|
152
239
|
passed=False,
|
|
153
240
|
reason=f"LockStock DENIED: {function_name} requires {capability}. "
|
|
154
|
-
f"
|
|
241
|
+
f"Reason: {verify_result.get('reason', 'Unknown')}"
|
|
155
242
|
)
|
|
156
243
|
|
|
244
|
+
# Update local chain state cache
|
|
245
|
+
self.current_hash = sign_result["new_hash"]
|
|
246
|
+
self.current_sequence = sign_result["new_sequence"]
|
|
247
|
+
self.current_matrix = sign_result["new_matrix"]
|
|
248
|
+
|
|
249
|
+
except Exception as e:
|
|
250
|
+
return GuardrailResult(
|
|
251
|
+
passed=False,
|
|
252
|
+
reason=f"Server verification failed: {e}"
|
|
253
|
+
)
|
|
254
|
+
|
|
157
255
|
return GuardrailResult(passed=True)
|
|
158
256
|
|
|
257
|
+
async def _verify_with_server(
|
|
258
|
+
self,
|
|
259
|
+
task: str,
|
|
260
|
+
signature: str,
|
|
261
|
+
parent_hash: str,
|
|
262
|
+
state_matrix: Dict[str, int],
|
|
263
|
+
timestamp: int,
|
|
264
|
+
sequence: int
|
|
265
|
+
) -> Dict[str, Any]:
|
|
266
|
+
"""
|
|
267
|
+
Send Guard-signed request to LockStock server.
|
|
268
|
+
|
|
269
|
+
The signature was created by Guard using current_hash as HMAC key.
|
|
270
|
+
Server will verify by:
|
|
271
|
+
1. Looking up its current_hash for this agent
|
|
272
|
+
2. Recalculating HMAC using that hash
|
|
273
|
+
3. Comparing to signature we send
|
|
274
|
+
|
|
275
|
+
Args:
|
|
276
|
+
task: Capability being verified
|
|
277
|
+
signature: HMAC signature from Guard (signed with current_hash)
|
|
278
|
+
parent_hash: Parent hash in chain
|
|
279
|
+
state_matrix: New state matrix
|
|
280
|
+
timestamp: Timestamp used in signature
|
|
281
|
+
sequence: Sequence number
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Dict with authorized (bool) and reason (str)
|
|
285
|
+
"""
|
|
286
|
+
payload = {
|
|
287
|
+
"client_id": self.agent_id,
|
|
288
|
+
"task": task.lower(),
|
|
289
|
+
"parent_hash": parent_hash,
|
|
290
|
+
"state_matrix": state_matrix,
|
|
291
|
+
"signature": signature, # ← From Guard, signed with current_hash
|
|
292
|
+
"timestamp": timestamp,
|
|
293
|
+
"sequence": sequence
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
response = await self._http.post(
|
|
297
|
+
f"{self.endpoint}/verify",
|
|
298
|
+
json=payload
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
data = response.json()
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"authorized": data.get("status") == "accepted",
|
|
305
|
+
"reason": data.get("reason"),
|
|
306
|
+
"state_hash": data.get("state_hash")
|
|
307
|
+
}
|
|
308
|
+
|
|
159
309
|
async def close(self):
|
|
160
|
-
"""Close
|
|
161
|
-
await self.
|
|
310
|
+
"""Close connections."""
|
|
311
|
+
await self._http.aclose()
|
|
312
|
+
self._guard.close()
|
|
162
313
|
|
|
163
314
|
|
|
164
315
|
class LockStockInputGuardrail(LockStockGuardrail):
|
|
165
316
|
"""
|
|
166
|
-
Input guardrail
|
|
167
|
-
|
|
168
|
-
Validates incoming messages/requests before processing.
|
|
317
|
+
Input guardrail using chain-based authentication.
|
|
169
318
|
"""
|
|
170
319
|
|
|
171
320
|
name = "lockstock_input"
|
|
172
|
-
description = "LockStock input validation guardrail"
|
|
321
|
+
description = "LockStock chain-based input validation guardrail"
|
|
173
322
|
|
|
174
323
|
async def validate_input(
|
|
175
324
|
self,
|
|
@@ -177,35 +326,19 @@ class LockStockInputGuardrail(LockStockGuardrail):
|
|
|
177
326
|
user_input: str,
|
|
178
327
|
context: Optional[Dict[str, Any]] = None
|
|
179
328
|
) -> GuardrailResult:
|
|
180
|
-
"""
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Args:
|
|
184
|
-
agent: The agent instance
|
|
185
|
-
user_input: The user's input string
|
|
186
|
-
context: Optional context dictionary
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
GuardrailResult indicating if validation passed
|
|
190
|
-
"""
|
|
191
|
-
# Input validation can check for:
|
|
192
|
-
# - Sensitive data patterns
|
|
193
|
-
# - Rate limiting
|
|
194
|
-
# - Input size limits
|
|
195
|
-
|
|
196
|
-
# For now, pass through - capability checks happen at tool use
|
|
329
|
+
"""Validate user input."""
|
|
330
|
+
# Input validation - pass through for now
|
|
331
|
+
# Capability checks happen at tool use
|
|
197
332
|
return GuardrailResult(passed=True)
|
|
198
333
|
|
|
199
334
|
|
|
200
335
|
class LockStockOutputGuardrail(LockStockGuardrail):
|
|
201
336
|
"""
|
|
202
|
-
Output guardrail
|
|
203
|
-
|
|
204
|
-
Validates agent outputs before returning to user.
|
|
337
|
+
Output guardrail using chain-based authentication.
|
|
205
338
|
"""
|
|
206
339
|
|
|
207
340
|
name = "lockstock_output"
|
|
208
|
-
description = "LockStock output validation guardrail"
|
|
341
|
+
description = "LockStock chain-based output validation guardrail"
|
|
209
342
|
|
|
210
343
|
async def validate_output(
|
|
211
344
|
self,
|
|
@@ -213,27 +346,7 @@ class LockStockOutputGuardrail(LockStockGuardrail):
|
|
|
213
346
|
output: str,
|
|
214
347
|
context: Optional[Dict[str, Any]] = None
|
|
215
348
|
) -> GuardrailResult:
|
|
216
|
-
"""
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
Args:
|
|
220
|
-
agent: The agent instance
|
|
221
|
-
output: The agent's output string
|
|
222
|
-
context: Optional context dictionary
|
|
223
|
-
|
|
224
|
-
Returns:
|
|
225
|
-
GuardrailResult indicating if validation passed
|
|
226
|
-
"""
|
|
227
|
-
# Output validation can check for:
|
|
228
|
-
# - Sensitive data leakage
|
|
229
|
-
# - Compliance requirements
|
|
230
|
-
# - Output format requirements
|
|
231
|
-
|
|
232
|
-
# Log the output to audit trail
|
|
233
|
-
await self.client.log_audit(
|
|
234
|
-
action="output",
|
|
235
|
-
status="COMPLETED",
|
|
236
|
-
metadata={"output_length": len(output)}
|
|
237
|
-
)
|
|
238
|
-
|
|
349
|
+
"""Validate agent output."""
|
|
350
|
+
# Output validation - pass through for now
|
|
351
|
+
# Could log to audit trail here
|
|
239
352
|
return GuardrailResult(passed=True)
|
|
File without changes
|