dreadnode 1.0.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.
- dreadnode/__init__.py +51 -0
- dreadnode/api/__init__.py +0 -0
- dreadnode/api/client.py +244 -0
- dreadnode/api/models.py +210 -0
- dreadnode/artifact/__init__.py +0 -0
- dreadnode/artifact/merger.py +599 -0
- dreadnode/artifact/storage.py +126 -0
- dreadnode/artifact/tree_builder.py +455 -0
- dreadnode/constants.py +16 -0
- dreadnode/integrations/__init__.py +0 -0
- dreadnode/integrations/transformers.py +183 -0
- dreadnode/main.py +1048 -0
- dreadnode/metric.py +228 -0
- dreadnode/object.py +29 -0
- dreadnode/py.typed +0 -0
- dreadnode/serialization.py +731 -0
- dreadnode/task.py +447 -0
- dreadnode/tracing/__init__.py +0 -0
- dreadnode/tracing/constants.py +35 -0
- dreadnode/tracing/exporters.py +157 -0
- dreadnode/tracing/span.py +819 -0
- dreadnode/types.py +25 -0
- dreadnode/util.py +150 -0
- dreadnode/version.py +3 -0
- dreadnode-1.0.0.dist-info/METADATA +125 -0
- dreadnode-1.0.0.dist-info/RECORD +27 -0
- dreadnode-1.0.0.dist-info/WHEEL +4 -0
dreadnode/types.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
# Common types
|
|
4
|
+
|
|
5
|
+
JsonValue = t.Union[
|
|
6
|
+
int,
|
|
7
|
+
float,
|
|
8
|
+
str,
|
|
9
|
+
bool,
|
|
10
|
+
None,
|
|
11
|
+
list["JsonValue"],
|
|
12
|
+
tuple["JsonValue", ...],
|
|
13
|
+
"JsonDict",
|
|
14
|
+
]
|
|
15
|
+
JsonDict = dict[str, JsonValue]
|
|
16
|
+
|
|
17
|
+
AnyDict = dict[str, t.Any]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Unset:
|
|
21
|
+
def __bool__(self) -> t.Literal[False]:
|
|
22
|
+
return False
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
UNSET: Unset = Unset()
|
dreadnode/util.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# Lots of utilities shamelessly copied from the `logfire` package.
|
|
2
|
+
# https://github.com/pydantic/logfire
|
|
3
|
+
|
|
4
|
+
import inspect
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import typing as t
|
|
9
|
+
from contextlib import contextmanager
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from types import TracebackType
|
|
12
|
+
|
|
13
|
+
from logfire import suppress_instrumentation
|
|
14
|
+
from logfire._internal.stack_info import add_non_user_code_prefix, is_user_code
|
|
15
|
+
|
|
16
|
+
import dreadnode
|
|
17
|
+
|
|
18
|
+
SysExcInfo = (
|
|
19
|
+
tuple[type[BaseException], BaseException, TracebackType | None] | tuple[None, None, None]
|
|
20
|
+
)
|
|
21
|
+
"""
|
|
22
|
+
The return type of sys.exc_info(): exc_type, exc_val, exc_tb.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger("dreadnode")
|
|
26
|
+
|
|
27
|
+
add_non_user_code_prefix(Path(dreadnode.__file__).parent)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def safe_repr(obj: t.Any) -> str:
|
|
31
|
+
"""
|
|
32
|
+
Return some kind of non-empty string representation of an object, catching exceptions.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
result = repr(obj)
|
|
37
|
+
except Exception: # noqa: BLE001
|
|
38
|
+
result = ""
|
|
39
|
+
|
|
40
|
+
if result:
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
return f"<{type(obj).__name__} object>"
|
|
45
|
+
except Exception: # noqa: BLE001
|
|
46
|
+
return "<unknown (repr failed)>"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def log_internal_error() -> None:
|
|
50
|
+
try:
|
|
51
|
+
current_test = os.environ.get("PYTEST_CURRENT_TEST", "")
|
|
52
|
+
reraise = bool(current_test and "test_internal_exception" not in current_test)
|
|
53
|
+
except Exception: # noqa: BLE001
|
|
54
|
+
reraise = False
|
|
55
|
+
|
|
56
|
+
if reraise:
|
|
57
|
+
raise # noqa: PLE0704
|
|
58
|
+
|
|
59
|
+
with suppress_instrumentation(): # prevent infinite recursion from the logging integration
|
|
60
|
+
logger.exception(
|
|
61
|
+
"Caught an error in Dreadnode. This will not prevent code from running, but you may lose data.",
|
|
62
|
+
exc_info=_internal_error_exc_info(),
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _internal_error_exc_info() -> SysExcInfo:
|
|
67
|
+
"""Returns an exc_info tuple with a nicely tweaked traceback."""
|
|
68
|
+
original_exc_info: SysExcInfo = sys.exc_info()
|
|
69
|
+
exc_type, exc_val, original_tb = original_exc_info
|
|
70
|
+
try:
|
|
71
|
+
# First remove redundant frames already in the traceback about where the error was raised.
|
|
72
|
+
tb = original_tb
|
|
73
|
+
if tb and tb.tb_frame and tb.tb_frame.f_code is _HANDLE_INTERNAL_ERRORS_CODE:
|
|
74
|
+
# Skip the 'yield' line in _handle_internal_errors
|
|
75
|
+
tb = tb.tb_next
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
tb
|
|
79
|
+
and tb.tb_frame
|
|
80
|
+
and tb.tb_frame.f_code.co_filename == contextmanager.__code__.co_filename
|
|
81
|
+
and tb.tb_frame.f_code.co_name == "inner"
|
|
82
|
+
):
|
|
83
|
+
# Skip the 'inner' function frame when handle_internal_errors is used as a decorator.
|
|
84
|
+
# It looks like `return func(*args, **kwds)`
|
|
85
|
+
tb = tb.tb_next
|
|
86
|
+
|
|
87
|
+
# Now add useful outer frames that give context, but skipping frames that are just about handling the error.
|
|
88
|
+
frame = inspect.currentframe()
|
|
89
|
+
# Skip this frame right here.
|
|
90
|
+
assert frame # noqa: S101
|
|
91
|
+
frame = frame.f_back
|
|
92
|
+
|
|
93
|
+
if frame and frame.f_code is log_internal_error.__code__: # pragma: no branch
|
|
94
|
+
# This function is always called from log_internal_error, so skip that frame.
|
|
95
|
+
frame = frame.f_back
|
|
96
|
+
assert frame # noqa: S101
|
|
97
|
+
|
|
98
|
+
if frame.f_code is _HANDLE_INTERNAL_ERRORS_CODE:
|
|
99
|
+
# Skip the line in _handle_internal_errors that calls log_internal_error
|
|
100
|
+
frame = frame.f_back
|
|
101
|
+
# Skip the frame defining the _handle_internal_errors context manager
|
|
102
|
+
assert frame # noqa: S101
|
|
103
|
+
assert frame.f_code.co_name == "__exit__" # noqa: S101
|
|
104
|
+
frame = frame.f_back
|
|
105
|
+
assert frame # noqa: S101
|
|
106
|
+
# Skip the frame calling the context manager, on the `with` line.
|
|
107
|
+
frame = frame.f_back
|
|
108
|
+
else:
|
|
109
|
+
# `log_internal_error()` was called directly, so just skip that frame. No context manager stuff.
|
|
110
|
+
frame = frame.f_back
|
|
111
|
+
|
|
112
|
+
# Now add all remaining frames from internal logfire code.
|
|
113
|
+
while frame and not is_user_code(frame.f_code):
|
|
114
|
+
tb = TracebackType(
|
|
115
|
+
tb_next=tb,
|
|
116
|
+
tb_frame=frame,
|
|
117
|
+
tb_lasti=frame.f_lasti,
|
|
118
|
+
tb_lineno=frame.f_lineno,
|
|
119
|
+
)
|
|
120
|
+
frame = frame.f_back
|
|
121
|
+
|
|
122
|
+
# Add up to 3 frames from user code.
|
|
123
|
+
for _ in range(3):
|
|
124
|
+
if not frame: # pragma: no cover
|
|
125
|
+
break
|
|
126
|
+
tb = TracebackType(
|
|
127
|
+
tb_next=tb,
|
|
128
|
+
tb_frame=frame,
|
|
129
|
+
tb_lasti=frame.f_lasti,
|
|
130
|
+
tb_lineno=frame.f_lineno,
|
|
131
|
+
)
|
|
132
|
+
frame = frame.f_back
|
|
133
|
+
|
|
134
|
+
assert exc_type # noqa: S101
|
|
135
|
+
assert exc_val # noqa: S101
|
|
136
|
+
exc_val = exc_val.with_traceback(tb)
|
|
137
|
+
return exc_type, exc_val, tb # noqa: TRY300
|
|
138
|
+
except Exception: # noqa: BLE001
|
|
139
|
+
return original_exc_info
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
@contextmanager
|
|
143
|
+
def handle_internal_errors() -> t.Iterator[None]:
|
|
144
|
+
try:
|
|
145
|
+
yield
|
|
146
|
+
except Exception: # noqa: BLE001
|
|
147
|
+
log_internal_error()
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
_HANDLE_INTERNAL_ERRORS_CODE = inspect.unwrap(handle_internal_errors).__code__
|
dreadnode/version.py
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dreadnode
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Dreadnode SDK
|
|
5
|
+
Author: Nick Landers
|
|
6
|
+
Author-email: monoxgas@gmail.com
|
|
7
|
+
Requires-Python: >=3.10,<3.14
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
13
|
+
Provides-Extra: training
|
|
14
|
+
Requires-Dist: coolname (>=2.2.0,<3.0.0)
|
|
15
|
+
Requires-Dist: fast-depends (>=2.4.12,<3.0.0)
|
|
16
|
+
Requires-Dist: fsspec[s3] (>=2023.1.0,<=2025.3.0)
|
|
17
|
+
Requires-Dist: httpx (>=0.28.0,<0.29.0)
|
|
18
|
+
Requires-Dist: logfire (>=3.5.3,<4.0.0)
|
|
19
|
+
Requires-Dist: pandas (>=2.2.3,<3.0.0)
|
|
20
|
+
Requires-Dist: pydantic (>=2.9.2,<3.0.0)
|
|
21
|
+
Requires-Dist: python-ulid (>=3.0.0,<4.0.0)
|
|
22
|
+
Requires-Dist: transformers (>=4.41.0,<5.0.0) ; extra == "training"
|
|
23
|
+
Project-URL: Repository, https://github.com/dreadnode/sdk
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
|
|
26
|
+
<p align="center">
|
|
27
|
+
<img
|
|
28
|
+
src="https://d1lppblt9t2x15.cloudfront.net/logos/5714928f3cdc09503751580cffbe8d02.png"
|
|
29
|
+
alt="Logo"
|
|
30
|
+
align="center"
|
|
31
|
+
width="144px"
|
|
32
|
+
height="144px"
|
|
33
|
+
/>
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<h3 align="center">
|
|
37
|
+
Dreadnode Strikes SDK
|
|
38
|
+
</h3>
|
|
39
|
+
|
|
40
|
+
<h4 align="center">
|
|
41
|
+
<img alt="PyPI - Python Version" src="https://img.shields.io/pypi/pyversions/dreadnode">
|
|
42
|
+
<img alt="PyPI - Version" src="https://img.shields.io/pypi/v/dreadnode">
|
|
43
|
+
<img alt="GitHub License" src="https://img.shields.io/github/license/dreadnode/sdk">
|
|
44
|
+
<img alt="Tests" src="https://img.shields.io/github/actions/workflow/status/dreadnode/sdk/tests.yaml">
|
|
45
|
+
<img alt="Pre-Commit" src="https://img.shields.io/github/actions/workflow/status/dreadnode/sdk/pre-commit.yaml">
|
|
46
|
+
<img alt="Renovate" src="https://img.shields.io/github/actions/workflow/status/dreadnode/sdk/renovate.yaml">
|
|
47
|
+
</h4>
|
|
48
|
+
|
|
49
|
+
</br>
|
|
50
|
+
|
|
51
|
+
Strikes is a platform for building, experimenting with, and evaluating AI security agent code.
|
|
52
|
+
|
|
53
|
+
- **Experiment + Tasking + Observability** in a single place that's lightweight and scales.
|
|
54
|
+
- **Track your data** with parameters, inputs, and outputs all connected to your tasks.
|
|
55
|
+
- **Log your artifacts** — data, models, files, and folders — to track data of your Dreadnode runs, enabling easy reuse and reproducibility.
|
|
56
|
+
- **Measure everything** with metrics throughout your code and anywhere you need them.
|
|
57
|
+
- **Scale your code** from a single run to thousands.
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
import dreadnode as dn
|
|
61
|
+
import rigging as rg
|
|
62
|
+
|
|
63
|
+
from .tools import reversing_tools
|
|
64
|
+
|
|
65
|
+
dn.configure()
|
|
66
|
+
|
|
67
|
+
@dataclass
|
|
68
|
+
class Finding:
|
|
69
|
+
name: str
|
|
70
|
+
severity: str
|
|
71
|
+
description: str
|
|
72
|
+
exploit_code: str
|
|
73
|
+
|
|
74
|
+
@dn.scorer(name="Score Finding")
|
|
75
|
+
async def score_finding(finding: Finding) -> float:
|
|
76
|
+
if finding.severity == "critical":
|
|
77
|
+
return 1.0
|
|
78
|
+
elif finding.severity == "high":
|
|
79
|
+
return 0.8
|
|
80
|
+
else:
|
|
81
|
+
return 0.2
|
|
82
|
+
|
|
83
|
+
@dn.task(scorers=[score_finding])
|
|
84
|
+
@rg.prompt(tools=[reversing_tools])
|
|
85
|
+
async def analyze_binary(binary: str) -> list[Finding]:
|
|
86
|
+
"""
|
|
87
|
+
Analyze the binary for vulnerabilities.
|
|
88
|
+
"""
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
with dn.run(tags=["reverse-engineering"]):
|
|
92
|
+
binary = "c2/downloads/service.exe"
|
|
93
|
+
|
|
94
|
+
dn.log_params(
|
|
95
|
+
model="gpt-4",
|
|
96
|
+
temperature=0.5,
|
|
97
|
+
binary=binary
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
findings = await analyze_binary(binary)
|
|
101
|
+
|
|
102
|
+
dn.log_metric("findings", len(findings))
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Installation
|
|
106
|
+
|
|
107
|
+
We publish every version to PyPi:
|
|
108
|
+
```bash
|
|
109
|
+
pip install -U dreadnode
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
If you want to build from source:
|
|
113
|
+
```bash
|
|
114
|
+
poetry install
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
See our **[installation guide](https://docs.dreadnode.io/strikes/install)** for more options.
|
|
118
|
+
|
|
119
|
+
## Getting Started
|
|
120
|
+
|
|
121
|
+
Read through our **[introduction guide](https://docs.dreadnode.io/strikes/intro)** in the docs.
|
|
122
|
+
|
|
123
|
+
## Examples
|
|
124
|
+
|
|
125
|
+
Check out **[dreadnode/example-agents](https://github.com/dreadnode/example-agents)** to find your favorite use case.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
dreadnode/__init__.py,sha256=MCe5IZTposO-rigZPBGHkyLqHkIZe-ItA2WxNWMQUQU,1269
|
|
2
|
+
dreadnode/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
dreadnode/api/client.py,sha256=bdP57EiCn9ZRU0uCzvosrlKbaDB42m9nvWFa6dDjwnk,8213
|
|
4
|
+
dreadnode/api/models.py,sha256=qdCU0gJg2ZCl3Abe1zsf5rqiaKaLOE_Fxj5jxIAZXWo,3922
|
|
5
|
+
dreadnode/artifact/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
+
dreadnode/artifact/merger.py,sha256=DQXvia8Q3ukM7CkLSCjqJcoVN7gpam77Uw_XioXZgD4,21524
|
|
7
|
+
dreadnode/artifact/storage.py,sha256=0OPSZdZTfLZaaq7BkRbf7uj4hN2DV9UHF4pNqsN0tX8,4012
|
|
8
|
+
dreadnode/artifact/tree_builder.py,sha256=GrCxu8H2OXeyx7vCU6Fw-Qs1lC0uV8IAOYq912ZXF_E,16071
|
|
9
|
+
dreadnode/constants.py,sha256=UY-zRo1H_VpWW0cYEXyROtgIRoEGdo2QJNUDjCac0fY,518
|
|
10
|
+
dreadnode/integrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
dreadnode/integrations/transformers.py,sha256=rzkUBf232NFWiJ2wTtXMSdpdItKWdxSF7IXMm5bHDEo,5253
|
|
12
|
+
dreadnode/main.py,sha256=Ql4dc-uI5KcqaBjxL_qPx7Au8f1zKLUAsXiKlkXH0nw,35166
|
|
13
|
+
dreadnode/metric.py,sha256=8AUGoXyL119N52gq8WbUc9muK7rHvcEUHgej_07toYw,7231
|
|
14
|
+
dreadnode/object.py,sha256=b6B8cWI2nN-DXH_FbC_DtlNwGJ_HIzvO6DiYed-0Ru0,403
|
|
15
|
+
dreadnode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
dreadnode/serialization.py,sha256=CuZGikN02mBVc83XYXV03wYjiYEtMScQhearcSgYJ6Q,20992
|
|
17
|
+
dreadnode/task.py,sha256=lrspiTA4ZzTRTV2PxRjQI8-AAAt2iQW-ILKzYVgH584,15016
|
|
18
|
+
dreadnode/tracing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
|
+
dreadnode/tracing/constants.py,sha256=ObmPeM_VLZzSEfkcUwinq4B4C3713PjzS6REawZWphA,1587
|
|
20
|
+
dreadnode/tracing/exporters.py,sha256=1OTumIb4k1JDjJfAmTxr7u7xhoB1rh9hiS8-5RN1NcA,4960
|
|
21
|
+
dreadnode/tracing/span.py,sha256=ZBZCO1yFv6jQEgvlaA5_tLKExnCJnstzyRrvZyJvfyc,25457
|
|
22
|
+
dreadnode/types.py,sha256=DD3r5ZtufouIMwWUsMQwDgVhMpjPwsR40O6wsQKIsxA,341
|
|
23
|
+
dreadnode/util.py,sha256=Nwh-Idyr_I-bRYmO3WQmEbNmjNCjfJZuWX1m2NiCTD4,5088
|
|
24
|
+
dreadnode/version.py,sha256=kytZz5e7pAGE4YzT0F9mDC6nSNIwwGGrNXovH0S6LkQ,77
|
|
25
|
+
dreadnode-1.0.0.dist-info/METADATA,sha256=Tzdq_td8YpVjzpZLaJ5Y_tMQ_NHcva6z95D8S8tOemo,3793
|
|
26
|
+
dreadnode-1.0.0.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
|
27
|
+
dreadnode-1.0.0.dist-info/RECORD,,
|