strix-agent 0.4.0__py3-none-any.whl → 0.6.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.
- strix/agents/StrixAgent/strix_agent.py +3 -3
- strix/agents/StrixAgent/system_prompt.jinja +30 -26
- strix/agents/base_agent.py +159 -75
- strix/agents/state.py +5 -2
- strix/config/__init__.py +12 -0
- strix/config/config.py +172 -0
- strix/interface/assets/tui_styles.tcss +195 -230
- strix/interface/cli.py +16 -41
- strix/interface/main.py +151 -74
- strix/interface/streaming_parser.py +119 -0
- strix/interface/tool_components/__init__.py +4 -0
- strix/interface/tool_components/agent_message_renderer.py +190 -0
- strix/interface/tool_components/agents_graph_renderer.py +54 -38
- strix/interface/tool_components/base_renderer.py +68 -36
- strix/interface/tool_components/browser_renderer.py +106 -91
- strix/interface/tool_components/file_edit_renderer.py +117 -36
- strix/interface/tool_components/finish_renderer.py +43 -10
- strix/interface/tool_components/notes_renderer.py +63 -38
- strix/interface/tool_components/proxy_renderer.py +133 -92
- strix/interface/tool_components/python_renderer.py +121 -8
- strix/interface/tool_components/registry.py +19 -12
- strix/interface/tool_components/reporting_renderer.py +196 -28
- strix/interface/tool_components/scan_info_renderer.py +22 -19
- strix/interface/tool_components/terminal_renderer.py +270 -90
- strix/interface/tool_components/thinking_renderer.py +8 -6
- strix/interface/tool_components/todo_renderer.py +225 -0
- strix/interface/tool_components/user_message_renderer.py +26 -19
- strix/interface/tool_components/web_search_renderer.py +7 -6
- strix/interface/tui.py +907 -262
- strix/interface/utils.py +236 -4
- strix/llm/__init__.py +6 -2
- strix/llm/config.py +8 -5
- strix/llm/dedupe.py +217 -0
- strix/llm/llm.py +209 -356
- strix/llm/memory_compressor.py +6 -5
- strix/llm/utils.py +17 -8
- strix/runtime/__init__.py +12 -3
- strix/runtime/docker_runtime.py +121 -202
- strix/runtime/tool_server.py +55 -95
- strix/skills/README.md +64 -0
- strix/skills/__init__.py +110 -0
- strix/{prompts → skills}/frameworks/nextjs.jinja +26 -0
- strix/skills/scan_modes/deep.jinja +145 -0
- strix/skills/scan_modes/quick.jinja +63 -0
- strix/skills/scan_modes/standard.jinja +91 -0
- strix/telemetry/README.md +38 -0
- strix/telemetry/__init__.py +7 -1
- strix/telemetry/posthog.py +137 -0
- strix/telemetry/tracer.py +194 -54
- strix/tools/__init__.py +11 -4
- strix/tools/agents_graph/agents_graph_actions.py +20 -21
- strix/tools/agents_graph/agents_graph_actions_schema.xml +8 -8
- strix/tools/browser/browser_actions.py +10 -6
- strix/tools/browser/browser_actions_schema.xml +6 -1
- strix/tools/browser/browser_instance.py +96 -48
- strix/tools/browser/tab_manager.py +121 -102
- strix/tools/context.py +12 -0
- strix/tools/executor.py +63 -4
- strix/tools/file_edit/file_edit_actions.py +6 -3
- strix/tools/file_edit/file_edit_actions_schema.xml +45 -3
- strix/tools/finish/finish_actions.py +80 -105
- strix/tools/finish/finish_actions_schema.xml +121 -14
- strix/tools/notes/notes_actions.py +6 -33
- strix/tools/notes/notes_actions_schema.xml +50 -46
- strix/tools/proxy/proxy_actions.py +14 -2
- strix/tools/proxy/proxy_actions_schema.xml +0 -1
- strix/tools/proxy/proxy_manager.py +28 -16
- strix/tools/python/python_actions.py +2 -2
- strix/tools/python/python_actions_schema.xml +9 -1
- strix/tools/python/python_instance.py +39 -37
- strix/tools/python/python_manager.py +43 -31
- strix/tools/registry.py +73 -12
- strix/tools/reporting/reporting_actions.py +218 -31
- strix/tools/reporting/reporting_actions_schema.xml +256 -8
- strix/tools/terminal/terminal_actions.py +2 -2
- strix/tools/terminal/terminal_actions_schema.xml +6 -0
- strix/tools/terminal/terminal_manager.py +41 -30
- strix/tools/thinking/thinking_actions_schema.xml +27 -25
- strix/tools/todo/__init__.py +18 -0
- strix/tools/todo/todo_actions.py +568 -0
- strix/tools/todo/todo_actions_schema.xml +225 -0
- strix/utils/__init__.py +0 -0
- strix/utils/resource_paths.py +13 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/METADATA +90 -65
- strix_agent-0.6.2.dist-info/RECORD +134 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/WHEEL +1 -1
- strix/llm/request_queue.py +0 -87
- strix/prompts/README.md +0 -64
- strix/prompts/__init__.py +0 -109
- strix_agent-0.4.0.dist-info/RECORD +0 -118
- /strix/{prompts → skills}/cloud/.gitkeep +0 -0
- /strix/{prompts → skills}/coordination/root_agent.jinja +0 -0
- /strix/{prompts → skills}/custom/.gitkeep +0 -0
- /strix/{prompts → skills}/frameworks/fastapi.jinja +0 -0
- /strix/{prompts → skills}/protocols/graphql.jinja +0 -0
- /strix/{prompts → skills}/reconnaissance/.gitkeep +0 -0
- /strix/{prompts → skills}/technologies/firebase_firestore.jinja +0 -0
- /strix/{prompts → skills}/technologies/supabase.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/authentication_jwt.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/broken_function_level_authorization.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/business_logic.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/csrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/idor.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/information_disclosure.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/insecure_file_uploads.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/mass_assignment.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/open_redirect.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/race_conditions.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/rce.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/sql_injection.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/ssrf.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/subdomain_takeover.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xss.jinja +0 -0
- /strix/{prompts → skills}/vulnerabilities/xxe.jinja +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info}/entry_points.txt +0 -0
- {strix_agent-0.4.0.dist-info → strix_agent-0.6.2.dist-info/licenses}/LICENSE +0 -0
strix/tools/registry.py
CHANGED
|
@@ -7,9 +7,14 @@ from inspect import signature
|
|
|
7
7
|
from pathlib import Path
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
+
import defusedxml.ElementTree as DefusedET
|
|
11
|
+
|
|
12
|
+
from strix.utils.resource_paths import get_strix_resource_path
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
tools: list[dict[str, Any]] = []
|
|
12
16
|
_tools_by_name: dict[str, Callable[..., Any]] = {}
|
|
17
|
+
_tool_param_schemas: dict[str, dict[str, Any]] = {}
|
|
13
18
|
logger = logging.getLogger(__name__)
|
|
14
19
|
|
|
15
20
|
|
|
@@ -23,17 +28,17 @@ class ImplementedInClientSideOnlyError(Exception):
|
|
|
23
28
|
|
|
24
29
|
|
|
25
30
|
def _process_dynamic_content(content: str) -> str:
|
|
26
|
-
if "{{
|
|
31
|
+
if "{{DYNAMIC_SKILLS_DESCRIPTION}}" in content:
|
|
27
32
|
try:
|
|
28
|
-
from strix.
|
|
33
|
+
from strix.skills import generate_skills_description
|
|
29
34
|
|
|
30
|
-
|
|
31
|
-
content = content.replace("{{
|
|
35
|
+
skills_description = generate_skills_description()
|
|
36
|
+
content = content.replace("{{DYNAMIC_SKILLS_DESCRIPTION}}", skills_description)
|
|
32
37
|
except ImportError:
|
|
33
|
-
logger.warning("Could not import
|
|
38
|
+
logger.warning("Could not import skills utilities for dynamic schema generation")
|
|
34
39
|
content = content.replace(
|
|
35
|
-
"{{
|
|
36
|
-
"List of
|
|
40
|
+
"{{DYNAMIC_SKILLS_DESCRIPTION}}",
|
|
41
|
+
"List of skills to load for this agent (max 5). Skill discovery failed.",
|
|
37
42
|
)
|
|
38
43
|
|
|
39
44
|
return content
|
|
@@ -82,6 +87,34 @@ def _load_xml_schema(path: Path) -> Any:
|
|
|
82
87
|
return tools_dict
|
|
83
88
|
|
|
84
89
|
|
|
90
|
+
def _parse_param_schema(tool_xml: str) -> dict[str, Any]:
|
|
91
|
+
params: set[str] = set()
|
|
92
|
+
required: set[str] = set()
|
|
93
|
+
|
|
94
|
+
params_start = tool_xml.find("<parameters>")
|
|
95
|
+
params_end = tool_xml.find("</parameters>")
|
|
96
|
+
|
|
97
|
+
if params_start == -1 or params_end == -1:
|
|
98
|
+
return {"params": set(), "required": set(), "has_params": False}
|
|
99
|
+
|
|
100
|
+
params_section = tool_xml[params_start : params_end + len("</parameters>")]
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
root = DefusedET.fromstring(params_section)
|
|
104
|
+
except DefusedET.ParseError:
|
|
105
|
+
return {"params": set(), "required": set(), "has_params": False}
|
|
106
|
+
|
|
107
|
+
for param in root.findall(".//parameter"):
|
|
108
|
+
name = param.attrib.get("name")
|
|
109
|
+
if not name:
|
|
110
|
+
continue
|
|
111
|
+
params.add(name)
|
|
112
|
+
if param.attrib.get("required", "false").lower() == "true":
|
|
113
|
+
required.add(name)
|
|
114
|
+
|
|
115
|
+
return {"params": params, "required": required, "has_params": bool(params or required)}
|
|
116
|
+
|
|
117
|
+
|
|
85
118
|
def _get_module_name(func: Callable[..., Any]) -> str:
|
|
86
119
|
module = inspect.getmodule(func)
|
|
87
120
|
if not module:
|
|
@@ -95,6 +128,27 @@ def _get_module_name(func: Callable[..., Any]) -> str:
|
|
|
95
128
|
return "unknown"
|
|
96
129
|
|
|
97
130
|
|
|
131
|
+
def _get_schema_path(func: Callable[..., Any]) -> Path | None:
|
|
132
|
+
module = inspect.getmodule(func)
|
|
133
|
+
if not module or not module.__name__:
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
module_name = module.__name__
|
|
137
|
+
|
|
138
|
+
if ".tools." not in module_name:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
parts = module_name.split(".tools.")[-1].split(".")
|
|
142
|
+
if len(parts) < 2:
|
|
143
|
+
return None
|
|
144
|
+
|
|
145
|
+
folder = parts[0]
|
|
146
|
+
file_stem = parts[1]
|
|
147
|
+
schema_file = f"{file_stem}_schema.xml"
|
|
148
|
+
|
|
149
|
+
return get_strix_resource_path("tools", folder, schema_file)
|
|
150
|
+
|
|
151
|
+
|
|
98
152
|
def register_tool(
|
|
99
153
|
func: Callable[..., Any] | None = None, *, sandbox_execution: bool = True
|
|
100
154
|
) -> Callable[..., Any]:
|
|
@@ -109,11 +163,8 @@ def register_tool(
|
|
|
109
163
|
sandbox_mode = os.getenv("STRIX_SANDBOX_MODE", "false").lower() == "true"
|
|
110
164
|
if not sandbox_mode:
|
|
111
165
|
try:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
schema_path = module_path.parent / schema_file_name
|
|
115
|
-
|
|
116
|
-
xml_tools = _load_xml_schema(schema_path)
|
|
166
|
+
schema_path = _get_schema_path(f)
|
|
167
|
+
xml_tools = _load_xml_schema(schema_path) if schema_path else None
|
|
117
168
|
|
|
118
169
|
if xml_tools is not None and f.__name__ in xml_tools:
|
|
119
170
|
func_dict["xml_schema"] = xml_tools[f.__name__]
|
|
@@ -131,6 +182,11 @@ def register_tool(
|
|
|
131
182
|
"</tool>"
|
|
132
183
|
)
|
|
133
184
|
|
|
185
|
+
if not sandbox_mode:
|
|
186
|
+
xml_schema = func_dict.get("xml_schema")
|
|
187
|
+
param_schema = _parse_param_schema(xml_schema if isinstance(xml_schema, str) else "")
|
|
188
|
+
_tool_param_schemas[str(func_dict["name"])] = param_schema
|
|
189
|
+
|
|
134
190
|
tools.append(func_dict)
|
|
135
191
|
_tools_by_name[str(func_dict["name"])] = f
|
|
136
192
|
|
|
@@ -153,6 +209,10 @@ def get_tool_names() -> list[str]:
|
|
|
153
209
|
return list(_tools_by_name.keys())
|
|
154
210
|
|
|
155
211
|
|
|
212
|
+
def get_tool_param_schema(name: str) -> dict[str, Any] | None:
|
|
213
|
+
return _tool_param_schemas.get(name)
|
|
214
|
+
|
|
215
|
+
|
|
156
216
|
def needs_agent_state(tool_name: str) -> bool:
|
|
157
217
|
tool_func = get_tool_by_name(tool_name)
|
|
158
218
|
if not tool_func:
|
|
@@ -194,3 +254,4 @@ def get_tools_prompt() -> str:
|
|
|
194
254
|
def clear_registry() -> None:
|
|
195
255
|
tools.clear()
|
|
196
256
|
_tools_by_name.clear()
|
|
257
|
+
_tool_param_schemas.clear()
|
|
@@ -3,61 +3,248 @@ from typing import Any
|
|
|
3
3
|
from strix.tools.registry import register_tool
|
|
4
4
|
|
|
5
5
|
|
|
6
|
+
def calculate_cvss_and_severity(
|
|
7
|
+
attack_vector: str,
|
|
8
|
+
attack_complexity: str,
|
|
9
|
+
privileges_required: str,
|
|
10
|
+
user_interaction: str,
|
|
11
|
+
scope: str,
|
|
12
|
+
confidentiality: str,
|
|
13
|
+
integrity: str,
|
|
14
|
+
availability: str,
|
|
15
|
+
) -> tuple[float, str, str]:
|
|
16
|
+
try:
|
|
17
|
+
from cvss import CVSS3
|
|
18
|
+
|
|
19
|
+
vector = (
|
|
20
|
+
f"CVSS:3.1/AV:{attack_vector}/AC:{attack_complexity}/"
|
|
21
|
+
f"PR:{privileges_required}/UI:{user_interaction}/S:{scope}/"
|
|
22
|
+
f"C:{confidentiality}/I:{integrity}/A:{availability}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
c = CVSS3(vector)
|
|
26
|
+
scores = c.scores()
|
|
27
|
+
severities = c.severities()
|
|
28
|
+
|
|
29
|
+
base_score = scores[0]
|
|
30
|
+
base_severity = severities[0]
|
|
31
|
+
|
|
32
|
+
severity = base_severity.lower()
|
|
33
|
+
|
|
34
|
+
except Exception:
|
|
35
|
+
import logging
|
|
36
|
+
|
|
37
|
+
logging.exception("Failed to calculate CVSS")
|
|
38
|
+
return 7.5, "high", ""
|
|
39
|
+
else:
|
|
40
|
+
return base_score, severity, vector
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _validate_required_fields(**kwargs: str | None) -> list[str]:
|
|
44
|
+
validation_errors: list[str] = []
|
|
45
|
+
|
|
46
|
+
required_fields = {
|
|
47
|
+
"title": "Title cannot be empty",
|
|
48
|
+
"description": "Description cannot be empty",
|
|
49
|
+
"impact": "Impact cannot be empty",
|
|
50
|
+
"target": "Target cannot be empty",
|
|
51
|
+
"technical_analysis": "Technical analysis cannot be empty",
|
|
52
|
+
"poc_description": "PoC description cannot be empty",
|
|
53
|
+
"poc_script_code": "PoC script/code is REQUIRED - provide the actual exploit/payload",
|
|
54
|
+
"remediation_steps": "Remediation steps cannot be empty",
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
for field_name, error_msg in required_fields.items():
|
|
58
|
+
value = kwargs.get(field_name)
|
|
59
|
+
if not value or not str(value).strip():
|
|
60
|
+
validation_errors.append(error_msg)
|
|
61
|
+
|
|
62
|
+
return validation_errors
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _validate_cvss_parameters(**kwargs: str) -> list[str]:
|
|
66
|
+
validation_errors: list[str] = []
|
|
67
|
+
|
|
68
|
+
cvss_validations = {
|
|
69
|
+
"attack_vector": ["N", "A", "L", "P"],
|
|
70
|
+
"attack_complexity": ["L", "H"],
|
|
71
|
+
"privileges_required": ["N", "L", "H"],
|
|
72
|
+
"user_interaction": ["N", "R"],
|
|
73
|
+
"scope": ["U", "C"],
|
|
74
|
+
"confidentiality": ["N", "L", "H"],
|
|
75
|
+
"integrity": ["N", "L", "H"],
|
|
76
|
+
"availability": ["N", "L", "H"],
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for param_name, valid_values in cvss_validations.items():
|
|
80
|
+
value = kwargs.get(param_name)
|
|
81
|
+
if value not in valid_values:
|
|
82
|
+
validation_errors.append(
|
|
83
|
+
f"Invalid {param_name}: {value}. Must be one of: {valid_values}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return validation_errors
|
|
87
|
+
|
|
88
|
+
|
|
6
89
|
@register_tool(sandbox_execution=False)
|
|
7
90
|
def create_vulnerability_report(
|
|
8
91
|
title: str,
|
|
9
|
-
|
|
10
|
-
|
|
92
|
+
description: str,
|
|
93
|
+
impact: str,
|
|
94
|
+
target: str,
|
|
95
|
+
technical_analysis: str,
|
|
96
|
+
poc_description: str,
|
|
97
|
+
poc_script_code: str,
|
|
98
|
+
remediation_steps: str,
|
|
99
|
+
# CVSS Breakdown Components
|
|
100
|
+
attack_vector: str,
|
|
101
|
+
attack_complexity: str,
|
|
102
|
+
privileges_required: str,
|
|
103
|
+
user_interaction: str,
|
|
104
|
+
scope: str,
|
|
105
|
+
confidentiality: str,
|
|
106
|
+
integrity: str,
|
|
107
|
+
availability: str,
|
|
108
|
+
# Optional fields
|
|
109
|
+
endpoint: str | None = None,
|
|
110
|
+
method: str | None = None,
|
|
111
|
+
cve: str | None = None,
|
|
112
|
+
code_file: str | None = None,
|
|
113
|
+
code_before: str | None = None,
|
|
114
|
+
code_after: str | None = None,
|
|
115
|
+
code_diff: str | None = None,
|
|
11
116
|
) -> dict[str, Any]:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
117
|
+
validation_errors = _validate_required_fields(
|
|
118
|
+
title=title,
|
|
119
|
+
description=description,
|
|
120
|
+
impact=impact,
|
|
121
|
+
target=target,
|
|
122
|
+
technical_analysis=technical_analysis,
|
|
123
|
+
poc_description=poc_description,
|
|
124
|
+
poc_script_code=poc_script_code,
|
|
125
|
+
remediation_steps=remediation_steps,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
validation_errors.extend(
|
|
129
|
+
_validate_cvss_parameters(
|
|
130
|
+
attack_vector=attack_vector,
|
|
131
|
+
attack_complexity=attack_complexity,
|
|
132
|
+
privileges_required=privileges_required,
|
|
133
|
+
user_interaction=user_interaction,
|
|
134
|
+
scope=scope,
|
|
135
|
+
confidentiality=confidentiality,
|
|
136
|
+
integrity=integrity,
|
|
137
|
+
availability=availability,
|
|
138
|
+
)
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if validation_errors:
|
|
142
|
+
return {"success": False, "message": "Validation failed", "errors": validation_errors}
|
|
25
143
|
|
|
26
|
-
|
|
27
|
-
|
|
144
|
+
cvss_score, severity, cvss_vector = calculate_cvss_and_severity(
|
|
145
|
+
attack_vector,
|
|
146
|
+
attack_complexity,
|
|
147
|
+
privileges_required,
|
|
148
|
+
user_interaction,
|
|
149
|
+
scope,
|
|
150
|
+
confidentiality,
|
|
151
|
+
integrity,
|
|
152
|
+
availability,
|
|
153
|
+
)
|
|
28
154
|
|
|
29
155
|
try:
|
|
30
156
|
from strix.telemetry.tracer import get_global_tracer
|
|
31
157
|
|
|
32
158
|
tracer = get_global_tracer()
|
|
33
159
|
if tracer:
|
|
160
|
+
from strix.llm.dedupe import check_duplicate
|
|
161
|
+
|
|
162
|
+
existing_reports = tracer.get_existing_vulnerabilities()
|
|
163
|
+
|
|
164
|
+
candidate = {
|
|
165
|
+
"title": title,
|
|
166
|
+
"description": description,
|
|
167
|
+
"impact": impact,
|
|
168
|
+
"target": target,
|
|
169
|
+
"technical_analysis": technical_analysis,
|
|
170
|
+
"poc_description": poc_description,
|
|
171
|
+
"poc_script_code": poc_script_code,
|
|
172
|
+
"endpoint": endpoint,
|
|
173
|
+
"method": method,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
dedupe_result = check_duplicate(candidate, existing_reports)
|
|
177
|
+
|
|
178
|
+
if dedupe_result.get("is_duplicate"):
|
|
179
|
+
duplicate_id = dedupe_result.get("duplicate_id", "")
|
|
180
|
+
|
|
181
|
+
duplicate_title = ""
|
|
182
|
+
for report in existing_reports:
|
|
183
|
+
if report.get("id") == duplicate_id:
|
|
184
|
+
duplicate_title = report.get("title", "Unknown")
|
|
185
|
+
break
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"success": False,
|
|
189
|
+
"message": (
|
|
190
|
+
f"Potential duplicate of '{duplicate_title}' "
|
|
191
|
+
f"(id={duplicate_id[:8]}...). Do not re-report the same vulnerability."
|
|
192
|
+
),
|
|
193
|
+
"duplicate_of": duplicate_id,
|
|
194
|
+
"duplicate_title": duplicate_title,
|
|
195
|
+
"confidence": dedupe_result.get("confidence", 0.0),
|
|
196
|
+
"reason": dedupe_result.get("reason", ""),
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
cvss_breakdown = {
|
|
200
|
+
"attack_vector": attack_vector,
|
|
201
|
+
"attack_complexity": attack_complexity,
|
|
202
|
+
"privileges_required": privileges_required,
|
|
203
|
+
"user_interaction": user_interaction,
|
|
204
|
+
"scope": scope,
|
|
205
|
+
"confidentiality": confidentiality,
|
|
206
|
+
"integrity": integrity,
|
|
207
|
+
"availability": availability,
|
|
208
|
+
}
|
|
209
|
+
|
|
34
210
|
report_id = tracer.add_vulnerability_report(
|
|
35
211
|
title=title,
|
|
36
|
-
|
|
212
|
+
description=description,
|
|
37
213
|
severity=severity,
|
|
214
|
+
impact=impact,
|
|
215
|
+
target=target,
|
|
216
|
+
technical_analysis=technical_analysis,
|
|
217
|
+
poc_description=poc_description,
|
|
218
|
+
poc_script_code=poc_script_code,
|
|
219
|
+
remediation_steps=remediation_steps,
|
|
220
|
+
cvss=cvss_score,
|
|
221
|
+
cvss_breakdown=cvss_breakdown,
|
|
222
|
+
endpoint=endpoint,
|
|
223
|
+
method=method,
|
|
224
|
+
cve=cve,
|
|
225
|
+
code_file=code_file,
|
|
226
|
+
code_before=code_before,
|
|
227
|
+
code_after=code_after,
|
|
228
|
+
code_diff=code_diff,
|
|
38
229
|
)
|
|
39
230
|
|
|
40
231
|
return {
|
|
41
232
|
"success": True,
|
|
42
233
|
"message": f"Vulnerability report '{title}' created successfully",
|
|
43
234
|
"report_id": report_id,
|
|
44
|
-
"severity": severity
|
|
235
|
+
"severity": severity,
|
|
236
|
+
"cvss_score": cvss_score,
|
|
45
237
|
}
|
|
238
|
+
|
|
46
239
|
import logging
|
|
47
240
|
|
|
48
|
-
logging.warning("
|
|
241
|
+
logging.warning("Current tracer not available - vulnerability report not stored")
|
|
49
242
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
"warning": "Report could not be persisted - tracer unavailable",
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
except ImportError:
|
|
243
|
+
except (ImportError, AttributeError) as e:
|
|
244
|
+
return {"success": False, "message": f"Failed to create vulnerability report: {e!s}"}
|
|
245
|
+
else:
|
|
57
246
|
return {
|
|
58
247
|
"success": True,
|
|
59
|
-
"message": f"Vulnerability report '{title}' created
|
|
60
|
-
"warning": "Report could not be persisted - tracer
|
|
248
|
+
"message": f"Vulnerability report '{title}' created (not persisted)",
|
|
249
|
+
"warning": "Report could not be persisted - tracer unavailable",
|
|
61
250
|
}
|
|
62
|
-
except (ValueError, TypeError) as e:
|
|
63
|
-
return {"success": False, "message": f"Failed to create vulnerability report: {e!s}"}
|