lockstock-integrations 1.0.0__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.0.dist-info → lockstock_integrations-1.1.0.dist-info}/METADATA +1 -1
- {lockstock_integrations-1.0.0.dist-info → lockstock_integrations-1.1.0.dist-info}/RECORD +4 -4
- lockstock_openai/guardrails.py +239 -80
- {lockstock_integrations-1.0.0.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.
|
|
15
|
-
lockstock_integrations-1.
|
|
16
|
-
lockstock_integrations-1.
|
|
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,50 +38,136 @@ 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
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def from_liberty(
|
|
93
|
+
cls,
|
|
94
|
+
agent_id: str,
|
|
95
|
+
socket_path: str = "/var/run/lockstock-guard/guard.sock",
|
|
96
|
+
endpoint: str = "https://lockstock-api-i9kp.onrender.com",
|
|
97
|
+
block_on_failure: bool = True
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Create guardrail using Chain Authority Mode.
|
|
101
|
+
|
|
102
|
+
NO SECRET RETRIEVAL. NO SECRET STORAGE.
|
|
103
|
+
|
|
104
|
+
The Guard daemon manages chain state. We just track it locally.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
agent_id: Agent identifier (from provisioning)
|
|
108
|
+
socket_path: Path to Guard daemon Unix socket
|
|
109
|
+
endpoint: LockStock API endpoint
|
|
110
|
+
block_on_failure: Whether to block on auth failure
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
LockStockGuardrail instance using chain-based auth
|
|
114
|
+
|
|
115
|
+
Example:
|
|
116
|
+
>>> guardrail = LockStockGuardrail.from_liberty("agent_abc123")
|
|
117
|
+
>>> agent = Agent(guardrails=[guardrail])
|
|
118
|
+
"""
|
|
119
|
+
return cls(
|
|
120
|
+
agent_id=agent_id,
|
|
121
|
+
guard_socket=socket_path,
|
|
122
|
+
endpoint=endpoint,
|
|
123
|
+
block_on_failure=block_on_failure
|
|
124
|
+
)
|
|
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
|
+
|
|
71
156
|
async def validate(
|
|
72
157
|
self,
|
|
73
158
|
agent: Any,
|
|
74
159
|
message: Dict[str, Any]
|
|
75
160
|
) -> GuardrailResult:
|
|
76
161
|
"""
|
|
77
|
-
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
|
|
78
169
|
|
|
79
|
-
|
|
170
|
+
NO SECRETS are involved. Only chain state.
|
|
80
171
|
|
|
81
172
|
Args:
|
|
82
173
|
agent: The agent instance
|
|
@@ -88,42 +179,146 @@ class LockStockGuardrail:
|
|
|
88
179
|
# Check for tool calls in assistant messages
|
|
89
180
|
if message.get("role") == "assistant" and "tool_calls" in message:
|
|
90
181
|
for tool_call in message.get("tool_calls", []):
|
|
91
|
-
if "function" in tool_call:
|
|
92
|
-
|
|
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
|
+
)
|
|
93
211
|
|
|
94
|
-
#
|
|
95
|
-
|
|
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
|
+
)
|
|
96
224
|
|
|
97
|
-
|
|
98
|
-
|
|
225
|
+
# Send Guard's signature to LockStock server for verification
|
|
226
|
+
try:
|
|
227
|
+
verify_result = await self._verify_with_server(
|
|
99
228
|
task=capability,
|
|
100
|
-
|
|
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"]
|
|
101
234
|
)
|
|
102
235
|
|
|
103
|
-
if not
|
|
236
|
+
if not verify_result["authorized"]:
|
|
104
237
|
if self.block_on_failure:
|
|
105
238
|
return GuardrailResult(
|
|
106
239
|
passed=False,
|
|
107
240
|
reason=f"LockStock DENIED: {function_name} requires {capability}. "
|
|
108
|
-
f"
|
|
241
|
+
f"Reason: {verify_result.get('reason', 'Unknown')}"
|
|
109
242
|
)
|
|
110
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
|
+
|
|
111
255
|
return GuardrailResult(passed=True)
|
|
112
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
|
+
|
|
113
309
|
async def close(self):
|
|
114
|
-
"""Close
|
|
115
|
-
await self.
|
|
310
|
+
"""Close connections."""
|
|
311
|
+
await self._http.aclose()
|
|
312
|
+
self._guard.close()
|
|
116
313
|
|
|
117
314
|
|
|
118
315
|
class LockStockInputGuardrail(LockStockGuardrail):
|
|
119
316
|
"""
|
|
120
|
-
Input guardrail
|
|
121
|
-
|
|
122
|
-
Validates incoming messages/requests before processing.
|
|
317
|
+
Input guardrail using chain-based authentication.
|
|
123
318
|
"""
|
|
124
319
|
|
|
125
320
|
name = "lockstock_input"
|
|
126
|
-
description = "LockStock input validation guardrail"
|
|
321
|
+
description = "LockStock chain-based input validation guardrail"
|
|
127
322
|
|
|
128
323
|
async def validate_input(
|
|
129
324
|
self,
|
|
@@ -131,35 +326,19 @@ class LockStockInputGuardrail(LockStockGuardrail):
|
|
|
131
326
|
user_input: str,
|
|
132
327
|
context: Optional[Dict[str, Any]] = None
|
|
133
328
|
) -> GuardrailResult:
|
|
134
|
-
"""
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
Args:
|
|
138
|
-
agent: The agent instance
|
|
139
|
-
user_input: The user's input string
|
|
140
|
-
context: Optional context dictionary
|
|
141
|
-
|
|
142
|
-
Returns:
|
|
143
|
-
GuardrailResult indicating if validation passed
|
|
144
|
-
"""
|
|
145
|
-
# Input validation can check for:
|
|
146
|
-
# - Sensitive data patterns
|
|
147
|
-
# - Rate limiting
|
|
148
|
-
# - Input size limits
|
|
149
|
-
|
|
150
|
-
# 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
|
|
151
332
|
return GuardrailResult(passed=True)
|
|
152
333
|
|
|
153
334
|
|
|
154
335
|
class LockStockOutputGuardrail(LockStockGuardrail):
|
|
155
336
|
"""
|
|
156
|
-
Output guardrail
|
|
157
|
-
|
|
158
|
-
Validates agent outputs before returning to user.
|
|
337
|
+
Output guardrail using chain-based authentication.
|
|
159
338
|
"""
|
|
160
339
|
|
|
161
340
|
name = "lockstock_output"
|
|
162
|
-
description = "LockStock output validation guardrail"
|
|
341
|
+
description = "LockStock chain-based output validation guardrail"
|
|
163
342
|
|
|
164
343
|
async def validate_output(
|
|
165
344
|
self,
|
|
@@ -167,27 +346,7 @@ class LockStockOutputGuardrail(LockStockGuardrail):
|
|
|
167
346
|
output: str,
|
|
168
347
|
context: Optional[Dict[str, Any]] = None
|
|
169
348
|
) -> GuardrailResult:
|
|
170
|
-
"""
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
Args:
|
|
174
|
-
agent: The agent instance
|
|
175
|
-
output: The agent's output string
|
|
176
|
-
context: Optional context dictionary
|
|
177
|
-
|
|
178
|
-
Returns:
|
|
179
|
-
GuardrailResult indicating if validation passed
|
|
180
|
-
"""
|
|
181
|
-
# Output validation can check for:
|
|
182
|
-
# - Sensitive data leakage
|
|
183
|
-
# - Compliance requirements
|
|
184
|
-
# - Output format requirements
|
|
185
|
-
|
|
186
|
-
# Log the output to audit trail
|
|
187
|
-
await self.client.log_audit(
|
|
188
|
-
action="output",
|
|
189
|
-
status="COMPLETED",
|
|
190
|
-
metadata={"output_length": len(output)}
|
|
191
|
-
)
|
|
192
|
-
|
|
349
|
+
"""Validate agent output."""
|
|
350
|
+
# Output validation - pass through for now
|
|
351
|
+
# Could log to audit trail here
|
|
193
352
|
return GuardrailResult(passed=True)
|
|
File without changes
|