agentic-radar 0.1.2__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.
- agentic_radar/__init__.py +3 -0
- agentic_radar/__main__.py +3 -0
- agentic_radar/analysis/__init__.py +3 -0
- agentic_radar/analysis/analyze.py +8 -0
- agentic_radar/analysis/langgraph/__init__.py +3 -0
- agentic_radar/analysis/langgraph/analyze.py +148 -0
- agentic_radar/analysis/langgraph/custom_tools.py +44 -0
- agentic_radar/analysis/langgraph/graph.py +792 -0
- agentic_radar/analysis/langgraph/models.py +36 -0
- agentic_radar/analysis/langgraph/nodes.py +115 -0
- agentic_radar/analysis/langgraph/predefined_tools.json +1 -0
- agentic_radar/analysis/langgraph/predefined_tools.py +61 -0
- agentic_radar/analysis/langgraph/utils.py +98 -0
- agentic_radar/cli.py +93 -0
- agentic_radar/graph.py +57 -0
- agentic_radar/mapper/__init__.py +3 -0
- agentic_radar/mapper/mapper.py +52 -0
- agentic_radar/mapper/vulnerabilities.json +1 -0
- agentic_radar/report/__init__.py +13 -0
- agentic_radar/report/graph/__init__.py +19 -0
- agentic_radar/report/graph/assets/agent.svg +12 -0
- agentic_radar/report/graph/assets/basic.svg +11 -0
- agentic_radar/report/graph/assets/custom_tool.svg +4 -0
- agentic_radar/report/graph/assets/tools/code_interpreter.svg +6 -0
- agentic_radar/report/graph/assets/tools/database.svg +10 -0
- agentic_radar/report/graph/assets/tools/default.svg +4 -0
- agentic_radar/report/graph/assets/tools/document_loader.svg +6 -0
- agentic_radar/report/graph/assets/tools/llm.svg +13 -0
- agentic_radar/report/graph/assets/tools/web_search.svg +9 -0
- agentic_radar/report/graph/edge.py +14 -0
- agentic_radar/report/graph/graph.py +33 -0
- agentic_radar/report/graph/node.py +86 -0
- agentic_radar/report/graph/parse.py +60 -0
- agentic_radar/report/report.py +81 -0
- agentic_radar/report/templates/assets/cover.svg +2719 -0
- agentic_radar/report/templates/template.html.jinja +292 -0
- agentic_radar-0.1.2.dist-info/LICENSE +661 -0
- agentic_radar-0.1.2.dist-info/METADATA +890 -0
- agentic_radar-0.1.2.dist-info/RECORD +41 -0
- agentic_radar-0.1.2.dist-info/WHEEL +4 -0
- agentic_radar-0.1.2.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from ...analysis.analyze import Analyzer
|
|
4
|
+
from ...graph import EdgeDefinition as Edge
|
|
5
|
+
from ...graph import GraphDefinition
|
|
6
|
+
from ...graph import NodeDefinition as Node
|
|
7
|
+
from ...graph import NodeType, ToolType
|
|
8
|
+
from .custom_tools import get_all_custom_tools_from_directory
|
|
9
|
+
from .graph import parse_all_graph_instances_in_directory
|
|
10
|
+
from .predefined_tools import get_all_predefined_tools_from_directory
|
|
11
|
+
from .utils import build_global_registry
|
|
12
|
+
|
|
13
|
+
GRAPH_CLASS = "langgraph.graph.StateGraph"
|
|
14
|
+
COMMAND_CLASS = "langgraph.types.Command"
|
|
15
|
+
ADD_CONDITIONAL_EDGES_METHOD_NAME = "add_conditional_edges"
|
|
16
|
+
ADD_NODE_METHOD_NAME = "add_node"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LangGraphAnalyzer(Analyzer):
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__()
|
|
22
|
+
|
|
23
|
+
def analyze(self, root_directory: str) -> GraphDefinition:
|
|
24
|
+
global_functions, global_variables = build_global_registry(root_directory)
|
|
25
|
+
|
|
26
|
+
predefined_tools = get_all_predefined_tools_from_directory(root_directory)
|
|
27
|
+
|
|
28
|
+
custom_tools = get_all_custom_tools_from_directory(root_directory)
|
|
29
|
+
|
|
30
|
+
all_graphs = parse_all_graph_instances_in_directory(
|
|
31
|
+
root_directory,
|
|
32
|
+
GRAPH_CLASS,
|
|
33
|
+
COMMAND_CLASS,
|
|
34
|
+
ADD_CONDITIONAL_EDGES_METHOD_NAME,
|
|
35
|
+
ADD_NODE_METHOD_NAME,
|
|
36
|
+
global_functions,
|
|
37
|
+
global_variables,
|
|
38
|
+
)
|
|
39
|
+
nodes = []
|
|
40
|
+
edges = []
|
|
41
|
+
|
|
42
|
+
for graph in all_graphs:
|
|
43
|
+
for i, node in enumerate(graph["graph_info"]["nodes"]):
|
|
44
|
+
|
|
45
|
+
nodes.append(
|
|
46
|
+
Node(type=NodeType.BASIC, name=node["name"], label=node["name"])
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
nodes.append(Node(type=NodeType.BASIC, name="START", label="START"))
|
|
50
|
+
|
|
51
|
+
nodes.append(Node(type=NodeType.BASIC, name="END", label="END"))
|
|
52
|
+
|
|
53
|
+
for basic_edge in graph["graph_info"]["basic_edges"]:
|
|
54
|
+
edges.append(
|
|
55
|
+
Edge(start=basic_edge["start_node"], end=basic_edge["end_node"])
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
for conditional_edge in graph["graph_info"]["conditional_edges"]:
|
|
59
|
+
edges.append(
|
|
60
|
+
Edge(
|
|
61
|
+
start=conditional_edge["start_node"],
|
|
62
|
+
end=conditional_edge["end_node"],
|
|
63
|
+
condition="if else",
|
|
64
|
+
)
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
tools = []
|
|
68
|
+
|
|
69
|
+
for predefined_tool in predefined_tools:
|
|
70
|
+
tools.append(
|
|
71
|
+
Node(
|
|
72
|
+
name=predefined_tool["name"],
|
|
73
|
+
type=NodeType.TOOL,
|
|
74
|
+
category=predefined_tool["category"],
|
|
75
|
+
vulnerabilities=[], # TODO Add vulnerability mapping
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
for custom_tool in custom_tools:
|
|
80
|
+
tools.append(
|
|
81
|
+
Node(
|
|
82
|
+
name=custom_tool["name"],
|
|
83
|
+
description=custom_tool["description"],
|
|
84
|
+
type=NodeType.CUSTOM_TOOL,
|
|
85
|
+
category=ToolType.DEFAULT,
|
|
86
|
+
vulnerabilities=[], # TODO Add vulnerability mapping
|
|
87
|
+
)
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
# This is for report generation testing
|
|
91
|
+
# tools = [
|
|
92
|
+
# Node(
|
|
93
|
+
# name="create_issue",
|
|
94
|
+
# type=NodeType.TOOL,
|
|
95
|
+
# category=ToolType.CODE_INTERPRETER,
|
|
96
|
+
# description="Tool description",
|
|
97
|
+
# vulnerabilities=[
|
|
98
|
+
# VulnerabilityDefinition(
|
|
99
|
+
# name="Improper Output Handling",
|
|
100
|
+
# description="Generated",
|
|
101
|
+
# security_framework_mapping={
|
|
102
|
+
# "CVE": "blabla",
|
|
103
|
+
# "OWASP LLM TOP 10": "blabla",
|
|
104
|
+
# },
|
|
105
|
+
# remediation="Remediate placholder",
|
|
106
|
+
# ),
|
|
107
|
+
# VulnerabilityDefinition(
|
|
108
|
+
# name="Improper Output Handling 2",
|
|
109
|
+
# description="Generated",
|
|
110
|
+
# security_framework_mapping={
|
|
111
|
+
# "CVE": "blabla",
|
|
112
|
+
# "OWASP LLM TOP 10": "blabla",
|
|
113
|
+
# },
|
|
114
|
+
# remediation="Placeholder",
|
|
115
|
+
# ),
|
|
116
|
+
# ],
|
|
117
|
+
# ),
|
|
118
|
+
# Node(
|
|
119
|
+
# name="create_issue 2",
|
|
120
|
+
# type=NodeType.TOOL,
|
|
121
|
+
# category=ToolType.CODE_INTERPRETER,
|
|
122
|
+
# description="Tool description",
|
|
123
|
+
# vulnerabilities=[
|
|
124
|
+
# VulnerabilityDefinition(
|
|
125
|
+
# name="Improper Output Handling",
|
|
126
|
+
# description="Generated",
|
|
127
|
+
# security_framework_mapping={
|
|
128
|
+
# "CVE": '<span style="color: black; font-size: 10px; font-family: Inter; font-weight: 400; text-decoration: underline; line-height: 15px; word-wrap: break-word">CVE-2023-44467</span>',
|
|
129
|
+
# "OWASP LLM TOP 10": "blabla",
|
|
130
|
+
# },
|
|
131
|
+
# remediation="Remediate placholder",
|
|
132
|
+
# ),
|
|
133
|
+
# VulnerabilityDefinition(
|
|
134
|
+
# name="Improper Output Handling 2",
|
|
135
|
+
# description="Generated",
|
|
136
|
+
# security_framework_mapping={
|
|
137
|
+
# "CVE": "blabla",
|
|
138
|
+
# "OWASP LLM TOP 10": "blabla",
|
|
139
|
+
# },
|
|
140
|
+
# remediation="Placeholder",
|
|
141
|
+
# ),
|
|
142
|
+
# ],
|
|
143
|
+
# ),
|
|
144
|
+
# ]
|
|
145
|
+
|
|
146
|
+
return GraphDefinition(
|
|
147
|
+
name=Path(root_directory).name, nodes=nodes, edges=edges, tools=tools
|
|
148
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import os
|
|
3
|
+
from typing import Dict, List
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def extract_custom_tools_with_ast(
|
|
7
|
+
file_content: str, file_path: str
|
|
8
|
+
) -> List[Dict[str, str]]:
|
|
9
|
+
custom_tools = []
|
|
10
|
+
tree = ast.parse(file_content)
|
|
11
|
+
|
|
12
|
+
for node in ast.walk(tree):
|
|
13
|
+
if isinstance(node, ast.FunctionDef):
|
|
14
|
+
decorator_names = []
|
|
15
|
+
for decorator in node.decorator_list:
|
|
16
|
+
if isinstance(decorator, ast.Name):
|
|
17
|
+
decorator_names.append(decorator.id)
|
|
18
|
+
elif isinstance(decorator, ast.Call):
|
|
19
|
+
if isinstance(decorator.func, ast.Name):
|
|
20
|
+
decorator_names.append(decorator.func.id)
|
|
21
|
+
elif isinstance(decorator.func, ast.Attribute):
|
|
22
|
+
decorator_names.append(decorator.func.attr)
|
|
23
|
+
|
|
24
|
+
if "tool" in decorator_names:
|
|
25
|
+
custom_tools.append({"name": node.name, "filepath": file_path, "description": ast.get_docstring(node)})
|
|
26
|
+
|
|
27
|
+
return custom_tools
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_all_custom_tools_from_directory(directory_path: str) -> List[Dict[str, str]]:
|
|
31
|
+
all_custom_tools = []
|
|
32
|
+
for root, _, files in os.walk(directory_path):
|
|
33
|
+
for file in files:
|
|
34
|
+
if file.endswith(".py"):
|
|
35
|
+
file_path = os.path.join(root, file)
|
|
36
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
37
|
+
file_content = f.read()
|
|
38
|
+
custom_tools = extract_custom_tools_with_ast(
|
|
39
|
+
file_content, file_path
|
|
40
|
+
)
|
|
41
|
+
for single_custom_tool in custom_tools:
|
|
42
|
+
all_custom_tools.append(single_custom_tool)
|
|
43
|
+
|
|
44
|
+
return all_custom_tools
|