fleet-python 0.2.66b4__py3-none-any.whl → 0.2.68__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.
Potentially problematic release.
This version of fleet-python might be problematic. Click here for more details.
- examples/export_tasks.py +11 -1
- examples/import_tasks.py +15 -0
- fleet/_async/client.py +36 -3
- fleet/_async/env/client.py +29 -3
- fleet/_async/models.py +3 -0
- fleet/_async/tasks.py +44 -26
- fleet/client.py +36 -3
- fleet/env/__init__.py +8 -0
- fleet/env/client.py +29 -3
- fleet/models.py +5 -0
- fleet/tasks.py +39 -25
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/METADATA +1 -1
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/RECORD +16 -18
- fleet/verifiers/parsing.py +0 -106
- tests/test_verifier_security.py +0 -427
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/top_level.txt +0 -0
tests/test_verifier_security.py
DELETED
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
"""Security tests for verifier_from_string function.
|
|
2
|
-
|
|
3
|
-
Tests that the verifier parsing and validation properly blocks
|
|
4
|
-
arbitrary code execution during import.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import pytest
|
|
8
|
-
from fleet.tasks import verifier_from_string as sync_verifier_from_string
|
|
9
|
-
from fleet._async.tasks import verifier_from_string as async_verifier_from_string
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class TestSyncVerifierSecurity:
|
|
13
|
-
"""Security tests for sync version of verifier_from_string."""
|
|
14
|
-
|
|
15
|
-
def test_blocks_module_level_subprocess_run(self):
|
|
16
|
-
"""Test that module-level subprocess.run() is blocked."""
|
|
17
|
-
code = """
|
|
18
|
-
import subprocess
|
|
19
|
-
subprocess.run(['echo', 'malicious'])
|
|
20
|
-
|
|
21
|
-
def my_verifier(env):
|
|
22
|
-
return 1.0
|
|
23
|
-
"""
|
|
24
|
-
with pytest.raises(ValueError, match="Expression statements that are not constants"):
|
|
25
|
-
sync_verifier_from_string(
|
|
26
|
-
verifier_func=code,
|
|
27
|
-
verifier_id="test-verifier",
|
|
28
|
-
verifier_key="test-key",
|
|
29
|
-
sha256="test-sha",
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
def test_blocks_module_level_open(self):
|
|
33
|
-
"""Test that module-level open() is blocked."""
|
|
34
|
-
code = """
|
|
35
|
-
open('/etc/passwd', 'r')
|
|
36
|
-
|
|
37
|
-
def my_verifier(env):
|
|
38
|
-
return 1.0
|
|
39
|
-
"""
|
|
40
|
-
with pytest.raises(ValueError, match="Expression statements that are not constants"):
|
|
41
|
-
sync_verifier_from_string(
|
|
42
|
-
verifier_func=code,
|
|
43
|
-
verifier_id="test-verifier",
|
|
44
|
-
verifier_key="test-key",
|
|
45
|
-
sha256="test-sha",
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
def test_blocks_assignment_with_subprocess_call(self):
|
|
49
|
-
"""Test that variable assignment with subprocess call is blocked."""
|
|
50
|
-
code = """
|
|
51
|
-
import subprocess
|
|
52
|
-
result = subprocess.run(['echo', 'malicious'])
|
|
53
|
-
|
|
54
|
-
def my_verifier(env):
|
|
55
|
-
return 1.0
|
|
56
|
-
"""
|
|
57
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
58
|
-
sync_verifier_from_string(
|
|
59
|
-
verifier_func=code,
|
|
60
|
-
verifier_id="test-verifier",
|
|
61
|
-
verifier_key="test-key",
|
|
62
|
-
sha256="test-sha",
|
|
63
|
-
)
|
|
64
|
-
|
|
65
|
-
def test_blocks_assignment_with_open_call(self):
|
|
66
|
-
"""Test that variable assignment with open() is blocked."""
|
|
67
|
-
code = """
|
|
68
|
-
file_handle = open('/etc/passwd', 'r')
|
|
69
|
-
|
|
70
|
-
def my_verifier(env):
|
|
71
|
-
return 1.0
|
|
72
|
-
"""
|
|
73
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
74
|
-
sync_verifier_from_string(
|
|
75
|
-
verifier_func=code,
|
|
76
|
-
verifier_id="test-verifier",
|
|
77
|
-
verifier_key="test-key",
|
|
78
|
-
sha256="test-sha",
|
|
79
|
-
)
|
|
80
|
-
|
|
81
|
-
def test_blocks_assignment_with_any_function_call(self):
|
|
82
|
-
"""Test that variable assignment with any function call is blocked."""
|
|
83
|
-
code = """
|
|
84
|
-
import os
|
|
85
|
-
path = os.getcwd()
|
|
86
|
-
|
|
87
|
-
def my_verifier(env):
|
|
88
|
-
return 1.0
|
|
89
|
-
"""
|
|
90
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
91
|
-
sync_verifier_from_string(
|
|
92
|
-
verifier_func=code,
|
|
93
|
-
verifier_id="test-verifier",
|
|
94
|
-
verifier_key="test-key",
|
|
95
|
-
sha256="test-sha",
|
|
96
|
-
)
|
|
97
|
-
|
|
98
|
-
def test_allows_constant_assignment(self):
|
|
99
|
-
"""Test that constant variable assignments are allowed."""
|
|
100
|
-
code = """
|
|
101
|
-
CONSTANT_VALUE = 42
|
|
102
|
-
ANOTHER_CONSTANT = "test"
|
|
103
|
-
PI = 3.14159
|
|
104
|
-
|
|
105
|
-
def my_verifier(env):
|
|
106
|
-
return CONSTANT_VALUE
|
|
107
|
-
"""
|
|
108
|
-
# Should not raise
|
|
109
|
-
verifier = sync_verifier_from_string(
|
|
110
|
-
verifier_func=code,
|
|
111
|
-
verifier_id="test-verifier",
|
|
112
|
-
verifier_key="test-key",
|
|
113
|
-
sha256="test-sha",
|
|
114
|
-
)
|
|
115
|
-
assert verifier is not None
|
|
116
|
-
|
|
117
|
-
def test_allows_list_dict_constant_assignment(self):
|
|
118
|
-
"""Test that list/dict constant assignments are allowed."""
|
|
119
|
-
code = """
|
|
120
|
-
MY_LIST = [1, 2, 3]
|
|
121
|
-
MY_DICT = {"key": "value"}
|
|
122
|
-
MY_TUPLE = (1, 2, 3)
|
|
123
|
-
|
|
124
|
-
def my_verifier(env):
|
|
125
|
-
return 1.0
|
|
126
|
-
"""
|
|
127
|
-
# Should not raise
|
|
128
|
-
verifier = sync_verifier_from_string(
|
|
129
|
-
verifier_func=code,
|
|
130
|
-
verifier_id="test-verifier",
|
|
131
|
-
verifier_key="test-key",
|
|
132
|
-
sha256="test-sha",
|
|
133
|
-
)
|
|
134
|
-
assert verifier is not None
|
|
135
|
-
|
|
136
|
-
def test_allows_valid_imports(self):
|
|
137
|
-
"""Test that imports are allowed."""
|
|
138
|
-
code = """
|
|
139
|
-
import json
|
|
140
|
-
import os
|
|
141
|
-
from typing import Dict
|
|
142
|
-
|
|
143
|
-
def my_verifier(env):
|
|
144
|
-
return 1.0
|
|
145
|
-
"""
|
|
146
|
-
# Should not raise
|
|
147
|
-
verifier = sync_verifier_from_string(
|
|
148
|
-
verifier_func=code,
|
|
149
|
-
verifier_id="test-verifier",
|
|
150
|
-
verifier_key="test-key",
|
|
151
|
-
sha256="test-sha",
|
|
152
|
-
)
|
|
153
|
-
assert verifier is not None
|
|
154
|
-
|
|
155
|
-
def test_allows_class_definitions(self):
|
|
156
|
-
"""Test that class definitions are allowed."""
|
|
157
|
-
code = """
|
|
158
|
-
class MyHelper:
|
|
159
|
-
def __init__(self):
|
|
160
|
-
self.value = 42
|
|
161
|
-
|
|
162
|
-
def get_value(self):
|
|
163
|
-
return self.value
|
|
164
|
-
|
|
165
|
-
def my_verifier(env):
|
|
166
|
-
helper = MyHelper()
|
|
167
|
-
return helper.get_value()
|
|
168
|
-
"""
|
|
169
|
-
# Should not raise
|
|
170
|
-
verifier = sync_verifier_from_string(
|
|
171
|
-
verifier_func=code,
|
|
172
|
-
verifier_id="test-verifier",
|
|
173
|
-
verifier_key="test-key",
|
|
174
|
-
sha256="test-sha",
|
|
175
|
-
)
|
|
176
|
-
assert verifier is not None
|
|
177
|
-
|
|
178
|
-
def test_allows_multiple_functions(self):
|
|
179
|
-
"""Test that multiple function definitions are allowed."""
|
|
180
|
-
code = """
|
|
181
|
-
def helper_function(x):
|
|
182
|
-
return x * 2
|
|
183
|
-
|
|
184
|
-
def my_verifier(env):
|
|
185
|
-
return helper_function(0.5)
|
|
186
|
-
"""
|
|
187
|
-
# Should not raise
|
|
188
|
-
verifier = sync_verifier_from_string(
|
|
189
|
-
verifier_func=code,
|
|
190
|
-
verifier_id="test-verifier",
|
|
191
|
-
verifier_key="test-key",
|
|
192
|
-
sha256="test-sha",
|
|
193
|
-
)
|
|
194
|
-
assert verifier is not None
|
|
195
|
-
|
|
196
|
-
def test_extracts_first_function_name(self):
|
|
197
|
-
"""Test that the first function name is correctly extracted."""
|
|
198
|
-
code = """
|
|
199
|
-
def first_function(env):
|
|
200
|
-
return 1.0
|
|
201
|
-
|
|
202
|
-
def second_function(env):
|
|
203
|
-
return 0.5
|
|
204
|
-
"""
|
|
205
|
-
verifier = sync_verifier_from_string(
|
|
206
|
-
verifier_func=code,
|
|
207
|
-
verifier_id="test-verifier",
|
|
208
|
-
verifier_key="test-key",
|
|
209
|
-
sha256="test-sha",
|
|
210
|
-
)
|
|
211
|
-
# The first function should be used
|
|
212
|
-
assert verifier.func.__name__ == "first_function"
|
|
213
|
-
|
|
214
|
-
def test_error_message_includes_line_number(self):
|
|
215
|
-
"""Test that error messages include helpful line numbers."""
|
|
216
|
-
code = """
|
|
217
|
-
import subprocess
|
|
218
|
-
|
|
219
|
-
subprocess.run(['echo', 'test'])
|
|
220
|
-
|
|
221
|
-
def my_verifier(env):
|
|
222
|
-
return 1.0
|
|
223
|
-
"""
|
|
224
|
-
with pytest.raises(ValueError, match=r"Line \d+"):
|
|
225
|
-
sync_verifier_from_string(
|
|
226
|
-
verifier_func=code,
|
|
227
|
-
verifier_id="test-verifier",
|
|
228
|
-
verifier_key="test-key",
|
|
229
|
-
sha256="test-sha",
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
def test_blocks_nested_function_call_in_list(self):
|
|
233
|
-
"""Test that function calls nested in list assignments are blocked."""
|
|
234
|
-
code = """
|
|
235
|
-
import os
|
|
236
|
-
MY_LIST = [1, 2, os.getcwd()]
|
|
237
|
-
|
|
238
|
-
def my_verifier(env):
|
|
239
|
-
return 1.0
|
|
240
|
-
"""
|
|
241
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
242
|
-
sync_verifier_from_string(
|
|
243
|
-
verifier_func=code,
|
|
244
|
-
verifier_id="test-verifier",
|
|
245
|
-
verifier_key="test-key",
|
|
246
|
-
sha256="test-sha",
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
def test_blocks_nested_function_call_in_dict(self):
|
|
250
|
-
"""Test that function calls nested in dict assignments are blocked."""
|
|
251
|
-
code = """
|
|
252
|
-
import os
|
|
253
|
-
MY_DICT = {"cwd": os.getcwd()}
|
|
254
|
-
|
|
255
|
-
def my_verifier(env):
|
|
256
|
-
return 1.0
|
|
257
|
-
"""
|
|
258
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
259
|
-
sync_verifier_from_string(
|
|
260
|
-
verifier_func=code,
|
|
261
|
-
verifier_id="test-verifier",
|
|
262
|
-
verifier_key="test-key",
|
|
263
|
-
sha256="test-sha",
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
def test_allows_docstrings(self):
|
|
267
|
-
"""Test that module-level docstrings are allowed."""
|
|
268
|
-
code = '''
|
|
269
|
-
"""This is a module docstring."""
|
|
270
|
-
|
|
271
|
-
def my_verifier(env):
|
|
272
|
-
"""This is a function docstring."""
|
|
273
|
-
return 1.0
|
|
274
|
-
'''
|
|
275
|
-
# Should not raise
|
|
276
|
-
verifier = sync_verifier_from_string(
|
|
277
|
-
verifier_func=code,
|
|
278
|
-
verifier_id="test-verifier",
|
|
279
|
-
verifier_key="test-key",
|
|
280
|
-
sha256="test-sha",
|
|
281
|
-
)
|
|
282
|
-
assert verifier is not None
|
|
283
|
-
|
|
284
|
-
def test_function_with_decorator_extracts_correct_name(self):
|
|
285
|
-
"""Test that decorators don't affect function name extraction."""
|
|
286
|
-
code = """
|
|
287
|
-
def some_decorator(func):
|
|
288
|
-
return func
|
|
289
|
-
|
|
290
|
-
@some_decorator
|
|
291
|
-
def my_actual_function(env):
|
|
292
|
-
return 1.0
|
|
293
|
-
"""
|
|
294
|
-
verifier = sync_verifier_from_string(
|
|
295
|
-
verifier_func=code,
|
|
296
|
-
verifier_id="test-verifier",
|
|
297
|
-
verifier_key="test-key",
|
|
298
|
-
sha256="test-sha",
|
|
299
|
-
)
|
|
300
|
-
# Should extract 'some_decorator' (first function) or 'my_actual_function'
|
|
301
|
-
# depending on order, but NOT the decorator name itself
|
|
302
|
-
assert verifier.func.__name__ in ["some_decorator", "my_actual_function"]
|
|
303
|
-
|
|
304
|
-
def test_blocks_decorator_with_function_call(self):
|
|
305
|
-
"""Test that decorators with function calls are blocked."""
|
|
306
|
-
code = """
|
|
307
|
-
import subprocess
|
|
308
|
-
|
|
309
|
-
@subprocess.run(['echo', 'bad'])
|
|
310
|
-
def my_verifier(env):
|
|
311
|
-
return 1.0
|
|
312
|
-
"""
|
|
313
|
-
# Decorators execute during import, so calls in decorators are dangerous
|
|
314
|
-
with pytest.raises(ValueError, match="Function decorators with function calls"):
|
|
315
|
-
sync_verifier_from_string(
|
|
316
|
-
verifier_func=code,
|
|
317
|
-
verifier_id="test-verifier",
|
|
318
|
-
verifier_key="test-key",
|
|
319
|
-
sha256="test-sha",
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
def test_allows_simple_decorator_reference(self):
|
|
323
|
-
"""Test that simple decorator references (no calls) are allowed."""
|
|
324
|
-
code = """
|
|
325
|
-
def my_decorator(func):
|
|
326
|
-
return func
|
|
327
|
-
|
|
328
|
-
@my_decorator
|
|
329
|
-
def my_verifier(env):
|
|
330
|
-
return 1.0
|
|
331
|
-
"""
|
|
332
|
-
# Simple decorator reference (no call) should be allowed
|
|
333
|
-
verifier = sync_verifier_from_string(
|
|
334
|
-
verifier_func=code,
|
|
335
|
-
verifier_id="test-verifier",
|
|
336
|
-
verifier_key="test-key",
|
|
337
|
-
sha256="test-sha",
|
|
338
|
-
)
|
|
339
|
-
assert verifier is not None
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
class TestAsyncVerifierSecurity:
|
|
343
|
-
"""Security tests for async version of verifier_from_string."""
|
|
344
|
-
|
|
345
|
-
def test_blocks_module_level_subprocess_run(self):
|
|
346
|
-
"""Test that module-level subprocess.run() is blocked."""
|
|
347
|
-
code = """
|
|
348
|
-
import subprocess
|
|
349
|
-
subprocess.run(['echo', 'malicious'])
|
|
350
|
-
|
|
351
|
-
async def my_async_verifier(env):
|
|
352
|
-
return 1.0
|
|
353
|
-
"""
|
|
354
|
-
with pytest.raises(ValueError, match="Expression statements that are not constants"):
|
|
355
|
-
async_verifier_from_string(
|
|
356
|
-
verifier_func=code,
|
|
357
|
-
verifier_id="test-verifier",
|
|
358
|
-
verifier_key="test-key",
|
|
359
|
-
sha256="test-sha",
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
def test_blocks_assignment_with_function_call(self):
|
|
363
|
-
"""Test that variable assignment with function call is blocked."""
|
|
364
|
-
code = """
|
|
365
|
-
import subprocess
|
|
366
|
-
result = subprocess.run(['echo', 'malicious'])
|
|
367
|
-
|
|
368
|
-
async def my_async_verifier(env):
|
|
369
|
-
return 1.0
|
|
370
|
-
"""
|
|
371
|
-
with pytest.raises(ValueError, match="Variable assignments with function calls"):
|
|
372
|
-
async_verifier_from_string(
|
|
373
|
-
verifier_func=code,
|
|
374
|
-
verifier_id="test-verifier",
|
|
375
|
-
verifier_key="test-key",
|
|
376
|
-
sha256="test-sha",
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
def test_allows_constant_assignment(self):
|
|
380
|
-
"""Test that constant variable assignments are allowed."""
|
|
381
|
-
code = """
|
|
382
|
-
CONSTANT_VALUE = 42
|
|
383
|
-
|
|
384
|
-
async def my_async_verifier(env):
|
|
385
|
-
return CONSTANT_VALUE
|
|
386
|
-
"""
|
|
387
|
-
# Should not raise
|
|
388
|
-
verifier = async_verifier_from_string(
|
|
389
|
-
verifier_func=code,
|
|
390
|
-
verifier_id="test-verifier",
|
|
391
|
-
verifier_key="test-key",
|
|
392
|
-
sha256="test-sha",
|
|
393
|
-
)
|
|
394
|
-
assert verifier is not None
|
|
395
|
-
|
|
396
|
-
def test_allows_async_function_definitions(self):
|
|
397
|
-
"""Test that async function definitions are recognized."""
|
|
398
|
-
code = """
|
|
399
|
-
async def my_async_verifier(env):
|
|
400
|
-
return 1.0
|
|
401
|
-
"""
|
|
402
|
-
# Should not raise
|
|
403
|
-
verifier = async_verifier_from_string(
|
|
404
|
-
verifier_func=code,
|
|
405
|
-
verifier_id="test-verifier",
|
|
406
|
-
verifier_key="test-key",
|
|
407
|
-
sha256="test-sha",
|
|
408
|
-
)
|
|
409
|
-
assert verifier is not None
|
|
410
|
-
|
|
411
|
-
def test_extracts_first_async_function_name(self):
|
|
412
|
-
"""Test that the first async function name is correctly extracted."""
|
|
413
|
-
code = """
|
|
414
|
-
async def first_async_function(env):
|
|
415
|
-
return 1.0
|
|
416
|
-
|
|
417
|
-
async def second_async_function(env):
|
|
418
|
-
return 0.5
|
|
419
|
-
"""
|
|
420
|
-
verifier = async_verifier_from_string(
|
|
421
|
-
verifier_func=code,
|
|
422
|
-
verifier_id="test-verifier",
|
|
423
|
-
verifier_key="test-key",
|
|
424
|
-
sha256="test-sha",
|
|
425
|
-
)
|
|
426
|
-
# The first function should be used
|
|
427
|
-
assert verifier.func.__name__ == "first_async_function"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|