kailash 0.3.0__py3-none-any.whl → 0.3.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.
- kailash/access_control.py +40 -39
- kailash/api/auth.py +26 -32
- kailash/api/custom_nodes.py +29 -29
- kailash/api/custom_nodes_secure.py +35 -35
- kailash/api/database.py +17 -17
- kailash/api/gateway.py +19 -19
- kailash/api/mcp_integration.py +24 -23
- kailash/api/studio.py +45 -45
- kailash/api/workflow_api.py +8 -8
- kailash/cli/commands.py +5 -8
- kailash/manifest.py +42 -42
- kailash/mcp/__init__.py +1 -1
- kailash/mcp/ai_registry_server.py +20 -20
- kailash/mcp/client.py +9 -11
- kailash/mcp/client_new.py +10 -10
- kailash/mcp/server.py +1 -2
- kailash/mcp/server_enhanced.py +449 -0
- kailash/mcp/servers/ai_registry.py +6 -6
- kailash/mcp/utils/__init__.py +31 -0
- kailash/mcp/utils/cache.py +267 -0
- kailash/mcp/utils/config.py +263 -0
- kailash/mcp/utils/formatters.py +293 -0
- kailash/mcp/utils/metrics.py +418 -0
- kailash/nodes/ai/agents.py +9 -9
- kailash/nodes/ai/ai_providers.py +33 -34
- kailash/nodes/ai/embedding_generator.py +31 -32
- kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
- kailash/nodes/ai/iterative_llm_agent.py +48 -48
- kailash/nodes/ai/llm_agent.py +32 -33
- kailash/nodes/ai/models.py +13 -13
- kailash/nodes/ai/self_organizing.py +44 -44
- kailash/nodes/api/auth.py +11 -11
- kailash/nodes/api/graphql.py +13 -13
- kailash/nodes/api/http.py +19 -19
- kailash/nodes/api/monitoring.py +20 -20
- kailash/nodes/api/rate_limiting.py +9 -13
- kailash/nodes/api/rest.py +29 -29
- kailash/nodes/api/security.py +44 -47
- kailash/nodes/base.py +21 -23
- kailash/nodes/base_async.py +7 -7
- kailash/nodes/base_cycle_aware.py +12 -12
- kailash/nodes/base_with_acl.py +5 -5
- kailash/nodes/code/python.py +66 -57
- kailash/nodes/data/directory.py +6 -6
- kailash/nodes/data/event_generation.py +10 -10
- kailash/nodes/data/file_discovery.py +28 -31
- kailash/nodes/data/readers.py +8 -8
- kailash/nodes/data/retrieval.py +10 -10
- kailash/nodes/data/sharepoint_graph.py +17 -17
- kailash/nodes/data/sources.py +5 -5
- kailash/nodes/data/sql.py +13 -13
- kailash/nodes/data/streaming.py +25 -25
- kailash/nodes/data/vector_db.py +22 -22
- kailash/nodes/data/writers.py +7 -7
- kailash/nodes/logic/async_operations.py +17 -17
- kailash/nodes/logic/convergence.py +11 -11
- kailash/nodes/logic/loop.py +4 -4
- kailash/nodes/logic/operations.py +11 -11
- kailash/nodes/logic/workflow.py +8 -9
- kailash/nodes/mixins/mcp.py +17 -17
- kailash/nodes/mixins.py +8 -10
- kailash/nodes/transform/chunkers.py +3 -3
- kailash/nodes/transform/formatters.py +7 -7
- kailash/nodes/transform/processors.py +10 -10
- kailash/runtime/access_controlled.py +18 -18
- kailash/runtime/async_local.py +17 -19
- kailash/runtime/docker.py +20 -22
- kailash/runtime/local.py +16 -16
- kailash/runtime/parallel.py +23 -23
- kailash/runtime/parallel_cyclic.py +27 -27
- kailash/runtime/runner.py +6 -6
- kailash/runtime/testing.py +20 -20
- kailash/sdk_exceptions.py +0 -58
- kailash/security.py +14 -26
- kailash/tracking/manager.py +38 -38
- kailash/tracking/metrics_collector.py +15 -14
- kailash/tracking/models.py +53 -53
- kailash/tracking/storage/base.py +7 -17
- kailash/tracking/storage/database.py +22 -23
- kailash/tracking/storage/filesystem.py +38 -40
- kailash/utils/export.py +21 -21
- kailash/utils/templates.py +2 -3
- kailash/visualization/api.py +30 -34
- kailash/visualization/dashboard.py +17 -17
- kailash/visualization/performance.py +16 -16
- kailash/visualization/reports.py +25 -27
- kailash/workflow/builder.py +8 -8
- kailash/workflow/convergence.py +13 -12
- kailash/workflow/cycle_analyzer.py +30 -32
- kailash/workflow/cycle_builder.py +12 -12
- kailash/workflow/cycle_config.py +16 -15
- kailash/workflow/cycle_debugger.py +40 -40
- kailash/workflow/cycle_exceptions.py +29 -29
- kailash/workflow/cycle_profiler.py +21 -21
- kailash/workflow/cycle_state.py +20 -22
- kailash/workflow/cyclic_runner.py +44 -44
- kailash/workflow/graph.py +40 -40
- kailash/workflow/mermaid_visualizer.py +9 -11
- kailash/workflow/migration.py +22 -22
- kailash/workflow/mock_registry.py +6 -6
- kailash/workflow/runner.py +9 -9
- kailash/workflow/safety.py +12 -13
- kailash/workflow/state.py +8 -11
- kailash/workflow/templates.py +19 -19
- kailash/workflow/validation.py +14 -14
- kailash/workflow/visualization.py +22 -22
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/METADATA +53 -5
- kailash-0.3.2.dist-info/RECORD +136 -0
- kailash-0.3.0.dist-info/RECORD +0 -130
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/WHEEL +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/entry_points.txt +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/top_level.txt +0 -0
kailash/nodes/api/rest.py
CHANGED
@@ -10,7 +10,7 @@ Key Components:
|
|
10
10
|
* Resource path builders and response handlers
|
11
11
|
"""
|
12
12
|
|
13
|
-
from typing import Any
|
13
|
+
from typing import Any
|
14
14
|
|
15
15
|
from kailash.nodes.api.http import AsyncHTTPRequestNode, HTTPRequestNode
|
16
16
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
@@ -157,7 +157,7 @@ class RESTClientNode(Node):
|
|
157
157
|
super().__init__(**kwargs)
|
158
158
|
self.http_node = HTTPRequestNode(url="")
|
159
159
|
|
160
|
-
def get_parameters(self) ->
|
160
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
161
161
|
"""Define the parameters this node accepts.
|
162
162
|
|
163
163
|
Returns:
|
@@ -297,7 +297,7 @@ class RESTClientNode(Node):
|
|
297
297
|
),
|
298
298
|
}
|
299
299
|
|
300
|
-
def get_output_schema(self) ->
|
300
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
301
301
|
"""Define the output schema for this node.
|
302
302
|
|
303
303
|
Returns:
|
@@ -334,8 +334,8 @@ class RESTClientNode(Node):
|
|
334
334
|
self,
|
335
335
|
base_url: str,
|
336
336
|
resource: str,
|
337
|
-
path_params:
|
338
|
-
version:
|
337
|
+
path_params: dict[str, Any],
|
338
|
+
version: str | None = None,
|
339
339
|
) -> str:
|
340
340
|
"""Build the full URL for a REST API request.
|
341
341
|
|
@@ -394,10 +394,10 @@ class RESTClientNode(Node):
|
|
394
394
|
|
395
395
|
def _handle_pagination(
|
396
396
|
self,
|
397
|
-
initial_response:
|
398
|
-
query_params:
|
399
|
-
pagination_params:
|
400
|
-
) ->
|
397
|
+
initial_response: dict[str, Any],
|
398
|
+
query_params: dict[str, Any],
|
399
|
+
pagination_params: dict[str, Any],
|
400
|
+
) -> list[Any]:
|
401
401
|
"""Handle pagination for REST API responses.
|
402
402
|
|
403
403
|
This method supports common pagination patterns:
|
@@ -469,7 +469,7 @@ class RESTClientNode(Node):
|
|
469
469
|
return all_items
|
470
470
|
|
471
471
|
def _get_nested_value(
|
472
|
-
self, obj:
|
472
|
+
self, obj: dict[str, Any], path: str, default: Any = None
|
473
473
|
) -> Any:
|
474
474
|
"""Get a nested value from a dictionary using a dot-separated path.
|
475
475
|
|
@@ -495,7 +495,7 @@ class RESTClientNode(Node):
|
|
495
495
|
|
496
496
|
return current
|
497
497
|
|
498
|
-
def run(self, **kwargs) ->
|
498
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
499
499
|
"""Execute a REST API request.
|
500
500
|
|
501
501
|
Args:
|
@@ -671,8 +671,8 @@ class RESTClientNode(Node):
|
|
671
671
|
|
672
672
|
# Convenience methods for CRUD operations
|
673
673
|
def get(
|
674
|
-
self, base_url: str, resource: str, resource_id:
|
675
|
-
) ->
|
674
|
+
self, base_url: str, resource: str, resource_id: str | None = None, **kwargs
|
675
|
+
) -> dict[str, Any]:
|
676
676
|
"""GET a resource or list of resources.
|
677
677
|
|
678
678
|
Args:
|
@@ -703,8 +703,8 @@ class RESTClientNode(Node):
|
|
703
703
|
)
|
704
704
|
|
705
705
|
def create(
|
706
|
-
self, base_url: str, resource: str, data:
|
707
|
-
) ->
|
706
|
+
self, base_url: str, resource: str, data: dict[str, Any], **kwargs
|
707
|
+
) -> dict[str, Any]:
|
708
708
|
"""CREATE (POST) a new resource.
|
709
709
|
|
710
710
|
Args:
|
@@ -725,10 +725,10 @@ class RESTClientNode(Node):
|
|
725
725
|
base_url: str,
|
726
726
|
resource: str,
|
727
727
|
resource_id: str,
|
728
|
-
data:
|
728
|
+
data: dict[str, Any],
|
729
729
|
partial: bool = False,
|
730
730
|
**kwargs,
|
731
|
-
) ->
|
731
|
+
) -> dict[str, Any]:
|
732
732
|
"""UPDATE (PUT/PATCH) an existing resource.
|
733
733
|
|
734
734
|
Args:
|
@@ -756,7 +756,7 @@ class RESTClientNode(Node):
|
|
756
756
|
|
757
757
|
def delete(
|
758
758
|
self, base_url: str, resource: str, resource_id: str, **kwargs
|
759
|
-
) ->
|
759
|
+
) -> dict[str, Any]:
|
760
760
|
"""DELETE a resource.
|
761
761
|
|
762
762
|
Args:
|
@@ -779,7 +779,7 @@ class RESTClientNode(Node):
|
|
779
779
|
**kwargs,
|
780
780
|
)
|
781
781
|
|
782
|
-
def _extract_metadata(self, response:
|
782
|
+
def _extract_metadata(self, response: dict[str, Any]) -> dict[str, Any]:
|
783
783
|
"""Extract additional metadata from response.
|
784
784
|
|
785
785
|
Args:
|
@@ -811,8 +811,8 @@ class RESTClientNode(Node):
|
|
811
811
|
return metadata
|
812
812
|
|
813
813
|
def _extract_rate_limit_metadata(
|
814
|
-
self, headers:
|
815
|
-
) ->
|
814
|
+
self, headers: dict[str, str]
|
815
|
+
) -> dict[str, Any] | None:
|
816
816
|
"""Extract rate limiting information from response headers.
|
817
817
|
|
818
818
|
Args:
|
@@ -847,8 +847,8 @@ class RESTClientNode(Node):
|
|
847
847
|
return rate_limit if rate_limit else None
|
848
848
|
|
849
849
|
def _extract_pagination_metadata(
|
850
|
-
self, headers:
|
851
|
-
) ->
|
850
|
+
self, headers: dict[str, str], content: Any
|
851
|
+
) -> dict[str, Any] | None:
|
852
852
|
"""Extract pagination information from headers and response body.
|
853
853
|
|
854
854
|
Args:
|
@@ -892,7 +892,7 @@ class RESTClientNode(Node):
|
|
892
892
|
|
893
893
|
return pagination if pagination else None
|
894
894
|
|
895
|
-
def _parse_link_header(self, link_header: str) ->
|
895
|
+
def _parse_link_header(self, link_header: str) -> dict[str, str]:
|
896
896
|
"""Parse Link header for pagination URLs.
|
897
897
|
|
898
898
|
Args:
|
@@ -916,7 +916,7 @@ class RESTClientNode(Node):
|
|
916
916
|
|
917
917
|
return links
|
918
918
|
|
919
|
-
def _extract_links(self, content: Any) ->
|
919
|
+
def _extract_links(self, content: Any) -> dict[str, Any] | None:
|
920
920
|
"""Extract HATEOAS links from response content.
|
921
921
|
|
922
922
|
Args:
|
@@ -986,7 +986,7 @@ class AsyncRESTClientNode(AsyncNode):
|
|
986
986
|
self.http_node = AsyncHTTPRequestNode(**kwargs)
|
987
987
|
self.rest_node = RESTClientNode(**kwargs)
|
988
988
|
|
989
|
-
def get_parameters(self) ->
|
989
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
990
990
|
"""Define the parameters this node accepts.
|
991
991
|
|
992
992
|
Returns:
|
@@ -995,7 +995,7 @@ class AsyncRESTClientNode(AsyncNode):
|
|
995
995
|
# Same parameters as the synchronous version
|
996
996
|
return self.rest_node.get_parameters()
|
997
997
|
|
998
|
-
def get_output_schema(self) ->
|
998
|
+
def get_output_schema(self) -> dict[str, NodeParameter]:
|
999
999
|
"""Define the output schema for this node.
|
1000
1000
|
|
1001
1001
|
Returns:
|
@@ -1004,7 +1004,7 @@ class AsyncRESTClientNode(AsyncNode):
|
|
1004
1004
|
# Same output schema as the synchronous version
|
1005
1005
|
return self.rest_node.get_output_schema()
|
1006
1006
|
|
1007
|
-
def run(self, **kwargs) ->
|
1007
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
1008
1008
|
"""Synchronous version of the REST request, for compatibility.
|
1009
1009
|
|
1010
1010
|
This is implemented for compatibility but users should use the
|
@@ -1022,7 +1022,7 @@ class AsyncRESTClientNode(AsyncNode):
|
|
1022
1022
|
# Forward to the synchronous REST node
|
1023
1023
|
return self.rest_node.run(**kwargs)
|
1024
1024
|
|
1025
|
-
async def async_run(self, **kwargs) ->
|
1025
|
+
async def async_run(self, **kwargs) -> dict[str, Any]:
|
1026
1026
|
"""Execute a REST API request asynchronously.
|
1027
1027
|
|
1028
1028
|
Args:
|
kailash/nodes/api/security.py
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
|
3
3
|
import socket
|
4
4
|
import time
|
5
|
-
from datetime import
|
6
|
-
from typing import Any
|
5
|
+
from datetime import UTC, datetime
|
6
|
+
from typing import Any
|
7
7
|
from urllib.parse import urlparse
|
8
8
|
|
9
9
|
import requests
|
@@ -87,7 +87,7 @@ class SecurityScannerNode(Node):
|
|
87
87
|
>>> assert 'security_findings' in result
|
88
88
|
"""
|
89
89
|
|
90
|
-
def get_parameters(self) ->
|
90
|
+
def get_parameters(self) -> dict[str, NodeParameter]:
|
91
91
|
return {
|
92
92
|
"scan_types": NodeParameter(
|
93
93
|
name="scan_types",
|
@@ -138,7 +138,7 @@ class SecurityScannerNode(Node):
|
|
138
138
|
),
|
139
139
|
}
|
140
140
|
|
141
|
-
def run(self, **kwargs) ->
|
141
|
+
def run(self, **kwargs) -> dict[str, Any]:
|
142
142
|
scan_types = kwargs["scan_types"]
|
143
143
|
targets = kwargs["targets"]
|
144
144
|
scan_depth = kwargs.get("scan_depth", "basic")
|
@@ -169,7 +169,7 @@ class SecurityScannerNode(Node):
|
|
169
169
|
"severity": "info",
|
170
170
|
"title": f"Scan Error: {scan_type}",
|
171
171
|
"description": f"Failed to complete {scan_type} scan: {str(e)}",
|
172
|
-
"timestamp": datetime.now(
|
172
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
173
173
|
}
|
174
174
|
target_findings.append(error_finding)
|
175
175
|
|
@@ -205,12 +205,12 @@ class SecurityScannerNode(Node):
|
|
205
205
|
[f for f in all_findings if 1 <= f.get("risk_score", 0) < 4]
|
206
206
|
),
|
207
207
|
"execution_time": execution_time,
|
208
|
-
"timestamp": datetime.now(
|
208
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
209
209
|
}
|
210
210
|
|
211
211
|
def _perform_scan(
|
212
212
|
self, scan_type: str, target: str, scan_depth: str, ports: str, timeout: int
|
213
|
-
) ->
|
213
|
+
) -> list[dict[str, Any]]:
|
214
214
|
"""Perform a specific type of security scan."""
|
215
215
|
|
216
216
|
if scan_type == "web_security":
|
@@ -233,13 +233,13 @@ class SecurityScannerNode(Node):
|
|
233
233
|
"severity": "info",
|
234
234
|
"title": f"Unsupported Scan Type: {scan_type}",
|
235
235
|
"description": f"Scan type '{scan_type}' is not supported",
|
236
|
-
"timestamp": datetime.now(
|
236
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
237
237
|
}
|
238
238
|
]
|
239
239
|
|
240
240
|
def _scan_web_security(
|
241
241
|
self, target: str, scan_depth: str, timeout: int
|
242
|
-
) ->
|
242
|
+
) -> list[dict[str, Any]]:
|
243
243
|
"""Perform web application security scan."""
|
244
244
|
findings = []
|
245
245
|
|
@@ -266,13 +266,13 @@ class SecurityScannerNode(Node):
|
|
266
266
|
"severity": "medium",
|
267
267
|
"title": "Connection Error",
|
268
268
|
"description": f"Failed to connect to target: {str(e)}",
|
269
|
-
"timestamp": datetime.now(
|
269
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
270
270
|
}
|
271
271
|
)
|
272
272
|
|
273
273
|
return findings
|
274
274
|
|
275
|
-
def _scan_ssl(self, target: str, timeout: int) ->
|
275
|
+
def _scan_ssl(self, target: str, timeout: int) -> list[dict[str, Any]]:
|
276
276
|
"""Perform SSL/TLS security scan."""
|
277
277
|
findings = []
|
278
278
|
|
@@ -290,7 +290,7 @@ class SecurityScannerNode(Node):
|
|
290
290
|
"title": "SSL/TLS Not Used",
|
291
291
|
"description": "Target does not use HTTPS encryption",
|
292
292
|
"recommendation": "Implement SSL/TLS encryption",
|
293
|
-
"timestamp": datetime.now(
|
293
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
294
294
|
}
|
295
295
|
)
|
296
296
|
return findings
|
@@ -323,8 +323,7 @@ class SecurityScannerNode(Node):
|
|
323
323
|
"title": "SSL Certificate Expiring",
|
324
324
|
"description": f"SSL certificate expires in {days_until_expiry} days",
|
325
325
|
"details": {"expiry_date": cert["notAfter"]},
|
326
|
-
"timestamp": datetime.now(
|
327
|
-
+ "Z",
|
326
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
328
327
|
}
|
329
328
|
)
|
330
329
|
|
@@ -336,7 +335,7 @@ class SecurityScannerNode(Node):
|
|
336
335
|
"severity": "low",
|
337
336
|
"title": "SSL Check Error",
|
338
337
|
"description": f"Failed to perform detailed SSL check: {str(e)}",
|
339
|
-
"timestamp": datetime.now(
|
338
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
340
339
|
}
|
341
340
|
)
|
342
341
|
|
@@ -348,7 +347,7 @@ class SecurityScannerNode(Node):
|
|
348
347
|
"severity": "low",
|
349
348
|
"title": "SSL Scan Error",
|
350
349
|
"description": f"SSL scan failed: {str(e)}",
|
351
|
-
"timestamp": datetime.now(
|
350
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
352
351
|
}
|
353
352
|
)
|
354
353
|
|
@@ -356,7 +355,7 @@ class SecurityScannerNode(Node):
|
|
356
355
|
|
357
356
|
def _scan_ports(
|
358
357
|
self, target: str, ports: str, timeout: int
|
359
|
-
) ->
|
358
|
+
) -> list[dict[str, Any]]:
|
360
359
|
"""Perform port scan."""
|
361
360
|
findings = []
|
362
361
|
|
@@ -432,8 +431,7 @@ class SecurityScannerNode(Node):
|
|
432
431
|
"description": f"Port {port} ({service}) is open and accessible",
|
433
432
|
"details": {"port": port, "service": service},
|
434
433
|
"recommendation": f"Ensure {service} service is properly secured",
|
435
|
-
"timestamp": datetime.now(
|
436
|
-
+ "Z",
|
434
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
437
435
|
}
|
438
436
|
)
|
439
437
|
|
@@ -453,7 +451,7 @@ class SecurityScannerNode(Node):
|
|
453
451
|
"open_ports": open_ports,
|
454
452
|
"total_scanned": len(port_list),
|
455
453
|
},
|
456
|
-
"timestamp": datetime.now(
|
454
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
457
455
|
}
|
458
456
|
)
|
459
457
|
|
@@ -461,7 +459,7 @@ class SecurityScannerNode(Node):
|
|
461
459
|
|
462
460
|
def _scan_services(
|
463
461
|
self, target: str, ports: str, timeout: int
|
464
|
-
) ->
|
462
|
+
) -> list[dict[str, Any]]:
|
465
463
|
"""Perform service detection scan."""
|
466
464
|
# For now, this is a simplified version
|
467
465
|
# In a full implementation, you would use nmap or similar tools
|
@@ -469,7 +467,7 @@ class SecurityScannerNode(Node):
|
|
469
467
|
|
470
468
|
def _scan_vulnerabilities(
|
471
469
|
self, target: str, scan_depth: str, timeout: int
|
472
|
-
) ->
|
470
|
+
) -> list[dict[str, Any]]:
|
473
471
|
"""Perform vulnerability scan using known CVE patterns."""
|
474
472
|
findings = []
|
475
473
|
|
@@ -500,7 +498,7 @@ class SecurityScannerNode(Node):
|
|
500
498
|
"potential_cve": vuln_info["cve"],
|
501
499
|
},
|
502
500
|
"recommendation": "Update server software to latest version",
|
503
|
-
"timestamp": datetime.now(
|
501
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
504
502
|
}
|
505
503
|
)
|
506
504
|
|
@@ -512,13 +510,13 @@ class SecurityScannerNode(Node):
|
|
512
510
|
"severity": "low",
|
513
511
|
"title": "Vulnerability Scan Error",
|
514
512
|
"description": f"Failed to perform vulnerability scan: {str(e)}",
|
515
|
-
"timestamp": datetime.now(
|
513
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
516
514
|
}
|
517
515
|
)
|
518
516
|
|
519
517
|
return findings
|
520
518
|
|
521
|
-
def _scan_headers(self, target: str, timeout: int) ->
|
519
|
+
def _scan_headers(self, target: str, timeout: int) -> list[dict[str, Any]]:
|
522
520
|
"""Perform HTTP security headers analysis."""
|
523
521
|
findings = []
|
524
522
|
|
@@ -546,7 +544,7 @@ class SecurityScannerNode(Node):
|
|
546
544
|
"title": f"Missing Security Header: {header}",
|
547
545
|
"description": f"Missing {header} header for {description}",
|
548
546
|
"recommendation": f"Implement {header} header",
|
549
|
-
"timestamp": datetime.now(
|
547
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
550
548
|
}
|
551
549
|
)
|
552
550
|
|
@@ -558,7 +556,7 @@ class SecurityScannerNode(Node):
|
|
558
556
|
"severity": "low",
|
559
557
|
"title": "Header Scan Error",
|
560
558
|
"description": f"Failed to analyze headers: {str(e)}",
|
561
|
-
"timestamp": datetime.now(
|
559
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
562
560
|
}
|
563
561
|
)
|
564
562
|
|
@@ -566,13 +564,13 @@ class SecurityScannerNode(Node):
|
|
566
564
|
|
567
565
|
def _check_security_headers(
|
568
566
|
self, target: str, response: requests.Response
|
569
|
-
) ->
|
567
|
+
) -> list[dict[str, Any]]:
|
570
568
|
"""Check for security headers in response."""
|
571
569
|
return self._scan_headers(target, 30) # Reuse header scan logic
|
572
570
|
|
573
571
|
def _check_ssl_redirect(
|
574
572
|
self, target: str, response: requests.Response
|
575
|
-
) ->
|
573
|
+
) -> list[dict[str, Any]]:
|
576
574
|
"""Check if HTTP redirects to HTTPS."""
|
577
575
|
findings = []
|
578
576
|
|
@@ -585,7 +583,7 @@ class SecurityScannerNode(Node):
|
|
585
583
|
"title": "No HTTPS Redirect",
|
586
584
|
"description": "HTTP requests are not redirected to HTTPS",
|
587
585
|
"recommendation": "Implement automatic HTTPS redirect",
|
588
|
-
"timestamp": datetime.now(
|
586
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
589
587
|
}
|
590
588
|
)
|
591
589
|
|
@@ -593,7 +591,7 @@ class SecurityScannerNode(Node):
|
|
593
591
|
|
594
592
|
def _check_directory_listing(
|
595
593
|
self, target: str, response: requests.Response
|
596
|
-
) ->
|
594
|
+
) -> list[dict[str, Any]]:
|
597
595
|
"""Check for directory listing vulnerabilities."""
|
598
596
|
findings = []
|
599
597
|
|
@@ -606,13 +604,13 @@ class SecurityScannerNode(Node):
|
|
606
604
|
"title": "Directory Listing Enabled",
|
607
605
|
"description": "Directory listing is enabled, potentially exposing sensitive files",
|
608
606
|
"recommendation": "Disable directory listing",
|
609
|
-
"timestamp": datetime.now(
|
607
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
610
608
|
}
|
611
609
|
)
|
612
610
|
|
613
611
|
return findings
|
614
612
|
|
615
|
-
def _check_common_files(self, target: str, timeout: int) ->
|
613
|
+
def _check_common_files(self, target: str, timeout: int) -> list[dict[str, Any]]:
|
616
614
|
"""Check for common sensitive files."""
|
617
615
|
findings = []
|
618
616
|
|
@@ -646,7 +644,7 @@ class SecurityScannerNode(Node):
|
|
646
644
|
"description": f"Sensitive file {file_path} is publicly accessible",
|
647
645
|
"details": {"file_path": file_path, "url": url},
|
648
646
|
"recommendation": "Restrict access to sensitive files",
|
649
|
-
"timestamp": datetime.now(
|
647
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
650
648
|
}
|
651
649
|
)
|
652
650
|
except:
|
@@ -656,7 +654,7 @@ class SecurityScannerNode(Node):
|
|
656
654
|
|
657
655
|
def _check_injection_points(
|
658
656
|
self, target: str, timeout: int
|
659
|
-
) ->
|
657
|
+
) -> list[dict[str, Any]]:
|
660
658
|
"""Check for basic injection vulnerabilities."""
|
661
659
|
# This is a simplified check - real implementations would be much more thorough
|
662
660
|
findings = []
|
@@ -683,7 +681,7 @@ class SecurityScannerNode(Node):
|
|
683
681
|
"title": "Potential Injection Vulnerability",
|
684
682
|
"description": f"Test payload reflected in response: {payload[:50]}...",
|
685
683
|
"recommendation": "Implement proper input validation and sanitization",
|
686
|
-
"timestamp": datetime.now(
|
684
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
687
685
|
}
|
688
686
|
)
|
689
687
|
break # Don't continue testing if one is found
|
@@ -692,7 +690,7 @@ class SecurityScannerNode(Node):
|
|
692
690
|
|
693
691
|
return findings
|
694
692
|
|
695
|
-
def _check_authentication(self, target: str, timeout: int) ->
|
693
|
+
def _check_authentication(self, target: str, timeout: int) -> list[dict[str, Any]]:
|
696
694
|
"""Check for authentication-related issues."""
|
697
695
|
findings = []
|
698
696
|
|
@@ -716,8 +714,7 @@ class SecurityScannerNode(Node):
|
|
716
714
|
"description": f"Login page at {path} is not using HTTPS",
|
717
715
|
"details": {"login_path": path},
|
718
716
|
"recommendation": "Use HTTPS for all authentication pages",
|
719
|
-
"timestamp": datetime.now(
|
720
|
-
+ "Z",
|
717
|
+
"timestamp": datetime.now(UTC).isoformat() + "Z",
|
721
718
|
}
|
722
719
|
)
|
723
720
|
except:
|
@@ -726,8 +723,8 @@ class SecurityScannerNode(Node):
|
|
726
723
|
return findings
|
727
724
|
|
728
725
|
def _calculate_risk_scores(
|
729
|
-
self, findings:
|
730
|
-
) ->
|
726
|
+
self, findings: list[dict[str, Any]]
|
727
|
+
) -> list[dict[str, Any]]:
|
731
728
|
"""Calculate risk scores for findings."""
|
732
729
|
severity_scores = {
|
733
730
|
"critical": 10,
|
@@ -757,8 +754,8 @@ class SecurityScannerNode(Node):
|
|
757
754
|
return findings
|
758
755
|
|
759
756
|
def _add_compliance_mapping(
|
760
|
-
self, findings:
|
761
|
-
) ->
|
757
|
+
self, findings: list[dict[str, Any]]
|
758
|
+
) -> list[dict[str, Any]]:
|
762
759
|
"""Add compliance framework mapping to findings."""
|
763
760
|
compliance_mapping = {
|
764
761
|
"missing_security_header": ["OWASP Top 10", "PCI DSS"],
|
@@ -777,11 +774,11 @@ class SecurityScannerNode(Node):
|
|
777
774
|
|
778
775
|
def _generate_scan_summary(
|
779
776
|
self,
|
780
|
-
findings:
|
781
|
-
targets:
|
782
|
-
scan_types:
|
777
|
+
findings: list[dict],
|
778
|
+
targets: list[str],
|
779
|
+
scan_types: list[str],
|
783
780
|
execution_time: float,
|
784
|
-
) ->
|
781
|
+
) -> dict[str, Any]:
|
785
782
|
"""Generate summary of security scan results."""
|
786
783
|
|
787
784
|
# Count findings by severity
|