@vk.amogh/trace 2.1.0
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.
- package/README.md +208 -0
- package/bin/trace.js +112 -0
- package/package.json +46 -0
- package/pyproject.toml +39 -0
- package/src/trace_engine/__init__.py +8 -0
- package/src/trace_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/doctor.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/verify.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__init__.py +7 -0
- package/src/trace_engine/ai/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/ollama.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/__pycache__/planner.cpython-311.pyc +0 -0
- package/src/trace_engine/ai/base.py +23 -0
- package/src/trace_engine/ai/ollama.py +50 -0
- package/src/trace_engine/ai/planner.py +40 -0
- package/src/trace_engine/apm/__init__.py +18 -0
- package/src/trace_engine/apm/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/builder.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/edges.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/nodes.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/__pycache__/serialization.cpython-311.pyc +0 -0
- package/src/trace_engine/apm/builder.py +208 -0
- package/src/trace_engine/apm/edges.py +25 -0
- package/src/trace_engine/apm/model.py +107 -0
- package/src/trace_engine/apm/nodes.py +27 -0
- package/src/trace_engine/apm/serialization.py +105 -0
- package/src/trace_engine/benchmark/__init__.py +5 -0
- package/src/trace_engine/benchmark/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/__pycache__/owasp.cpython-311.pyc +0 -0
- package/src/trace_engine/benchmark/owasp.py +183 -0
- package/src/trace_engine/cli.py +1184 -0
- package/src/trace_engine/config/__init__.py +35 -0
- package/src/trace_engine/config/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/defaults.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/loader.cpython-311.pyc +0 -0
- package/src/trace_engine/config/__pycache__/settings.cpython-311.pyc +0 -0
- package/src/trace_engine/config/defaults.py +48 -0
- package/src/trace_engine/config/loader.py +64 -0
- package/src/trace_engine/config/settings.py +72 -0
- package/src/trace_engine/doctor.py +250 -0
- package/src/trace_engine/findings/__init__.py +15 -0
- package/src/trace_engine/findings/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/correlate.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/model.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/recommendations.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/__pycache__/store.cpython-311.pyc +0 -0
- package/src/trace_engine/findings/correlate.py +103 -0
- package/src/trace_engine/findings/model.py +40 -0
- package/src/trace_engine/findings/recommendations.py +35 -0
- package/src/trace_engine/findings/store.py +39 -0
- package/src/trace_engine/framework/__init__.py +58 -0
- package/src/trace_engine/framework/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/csharp.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/dart.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/django.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/express.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/fastapi.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/flask.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/go.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/nextjs.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/php.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/react_router.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/ruby.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/rust.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/__pycache__/springboot.cpython-311.pyc +0 -0
- package/src/trace_engine/framework/base.py +49 -0
- package/src/trace_engine/framework/csharp.py +111 -0
- package/src/trace_engine/framework/dart.py +152 -0
- package/src/trace_engine/framework/django.py +188 -0
- package/src/trace_engine/framework/express.py +82 -0
- package/src/trace_engine/framework/fastapi.py +135 -0
- package/src/trace_engine/framework/flask.py +108 -0
- package/src/trace_engine/framework/go.py +94 -0
- package/src/trace_engine/framework/nextjs.py +200 -0
- package/src/trace_engine/framework/php.py +98 -0
- package/src/trace_engine/framework/react_router.py +331 -0
- package/src/trace_engine/framework/ruby.py +69 -0
- package/src/trace_engine/framework/rust.py +101 -0
- package/src/trace_engine/framework/springboot.py +145 -0
- package/src/trace_engine/harness/__init__.py +19 -0
- package/src/trace_engine/harness/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/benchmark.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/context.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/engine.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/patcher.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/__pycache__/remediators.cpython-311.pyc +0 -0
- package/src/trace_engine/harness/benchmark.py +55 -0
- package/src/trace_engine/harness/context.py +49 -0
- package/src/trace_engine/harness/engine.py +187 -0
- package/src/trace_engine/harness/patcher.py +143 -0
- package/src/trace_engine/harness/remediators.py +307 -0
- package/src/trace_engine/ingest/__init__.py +15 -0
- package/src/trace_engine/ingest/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/files.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/hashing.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/ignore.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/__pycache__/repository.cpython-311.pyc +0 -0
- package/src/trace_engine/ingest/files.py +60 -0
- package/src/trace_engine/ingest/hashing.py +21 -0
- package/src/trace_engine/ingest/ignore.py +108 -0
- package/src/trace_engine/ingest/repository.py +50 -0
- package/src/trace_engine/intelligence/__init__.py +15 -0
- package/src/trace_engine/intelligence/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/evaluation.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/orchestrator.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/__pycache__/tracebench.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/evaluation.py +828 -0
- package/src/trace_engine/intelligence/laya/__init__.py +19 -0
- package/src/trace_engine/intelligence/laya/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/prompts.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/schemas.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/telemetry.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/__pycache__/thresholds.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/prompts.py +67 -0
- package/src/trace_engine/intelligence/laya/router.py +352 -0
- package/src/trace_engine/intelligence/laya/schemas.py +56 -0
- package/src/trace_engine/intelligence/laya/telemetry.py +48 -0
- package/src/trace_engine/intelligence/laya/thresholds.py +12 -0
- package/src/trace_engine/intelligence/orchestrator.py +130 -0
- package/src/trace_engine/intelligence/securebert/__init__.py +6 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/cache.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/cache.py +37 -0
- package/src/trace_engine/intelligence/securebert/classifier.py +240 -0
- package/src/trace_engine/intelligence/tracebench.py +61 -0
- package/src/trace_engine/intelligence/training/__init__.py +21 -0
- package/src/trace_engine/intelligence/training/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/dataset_importers.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/laya_trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/lora_system2.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/morefixes_pipeline.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/slicer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/train_all.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/__pycache__/trainer.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/training/dataset.py +320 -0
- package/src/trace_engine/intelligence/training/dataset_importers.py +349 -0
- package/src/trace_engine/intelligence/training/laya_trainer.py +678 -0
- package/src/trace_engine/intelligence/training/lora_system2.py +162 -0
- package/src/trace_engine/intelligence/training/morefixes_pipeline.py +463 -0
- package/src/trace_engine/intelligence/training/slicer.py +129 -0
- package/src/trace_engine/intelligence/training/train_all.py +1009 -0
- package/src/trace_engine/intelligence/training/trainer.py +321 -0
- package/src/trace_engine/interactive.py +623 -0
- package/src/trace_engine/mcp/__init__.py +6 -0
- package/src/trace_engine/mcp/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/config.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/config.py +42 -0
- package/src/trace_engine/mcp/server.py +398 -0
- package/src/trace_engine/output/__init__.py +28 -0
- package/src/trace_engine/output/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/html.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/markdown.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/sarif.cpython-311.pyc +0 -0
- package/src/trace_engine/output/__pycache__/terminal.cpython-311.pyc +0 -0
- package/src/trace_engine/output/html.py +142 -0
- package/src/trace_engine/output/markdown.py +65 -0
- package/src/trace_engine/output/sarif.py +180 -0
- package/src/trace_engine/output/terminal.py +537 -0
- package/src/trace_engine/parsing/__init__.py +26 -0
- package/src/trace_engine/parsing/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/calls.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/language.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/locations.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/parser.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/__pycache__/symbols.cpython-311.pyc +0 -0
- package/src/trace_engine/parsing/calls.py +14 -0
- package/src/trace_engine/parsing/language.py +24 -0
- package/src/trace_engine/parsing/locations.py +17 -0
- package/src/trace_engine/parsing/parser.py +465 -0
- package/src/trace_engine/parsing/symbols.py +45 -0
- package/src/trace_engine/plugin/__init__.py +157 -0
- package/src/trace_engine/plugin/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/evaluator.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/swebench_adapter.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/__pycache__/task.cpython-311.pyc +0 -0
- package/src/trace_engine/plugin/bundle/hooks.json +24 -0
- package/src/trace_engine/plugin/bundle/mcp_config.json +11 -0
- package/src/trace_engine/plugin/bundle/plugin.json +20 -0
- package/src/trace_engine/plugin/bundle/rules/security_remediation.md +40 -0
- package/src/trace_engine/plugin/bundle/skills/trace-security-harness/SKILL.md +118 -0
- package/src/trace_engine/plugin/evaluator.py +105 -0
- package/src/trace_engine/plugin/swebench_adapter.py +96 -0
- package/src/trace_engine/plugin/task.py +48 -0
- package/src/trace_engine/policy/__init__.py +5 -0
- package/src/trace_engine/policy/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/__pycache__/scope.cpython-311.pyc +0 -0
- package/src/trace_engine/policy/scope.py +80 -0
- package/src/trace_engine/runtime/__init__.py +7 -0
- package/src/trace_engine/runtime/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/client.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/observations.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/__pycache__/target.cpython-311.pyc +0 -0
- package/src/trace_engine/runtime/client.py +81 -0
- package/src/trace_engine/runtime/observations.py +18 -0
- package/src/trace_engine/runtime/target.py +23 -0
- package/src/trace_engine/security/__init__.py +16 -0
- package/src/trace_engine/security/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/fusion.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/hypotheses.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/signals.cpython-311.pyc +0 -0
- package/src/trace_engine/security/__pycache__/timing.cpython-311.pyc +0 -0
- package/src/trace_engine/security/fusion.py +126 -0
- package/src/trace_engine/security/hypotheses.py +251 -0
- package/src/trace_engine/security/signals.py +28 -0
- package/src/trace_engine/security/timing.py +132 -0
- package/src/trace_engine/testpacks/__init__.py +18 -0
- package/src/trace_engine/testpacks/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/authentication.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bfla.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/bola.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/cors.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/deserialization.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/injection.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/mass_assignment.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/path_traversal.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssrf.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/__pycache__/ssti.cpython-311.pyc +0 -0
- package/src/trace_engine/testpacks/authentication.py +60 -0
- package/src/trace_engine/testpacks/base.py +63 -0
- package/src/trace_engine/testpacks/bfla.py +70 -0
- package/src/trace_engine/testpacks/bola.py +94 -0
- package/src/trace_engine/testpacks/cors.py +85 -0
- package/src/trace_engine/testpacks/deserialization.py +86 -0
- package/src/trace_engine/testpacks/injection.py +179 -0
- package/src/trace_engine/testpacks/mass_assignment.py +70 -0
- package/src/trace_engine/testpacks/path_traversal.py +117 -0
- package/src/trace_engine/testpacks/registry.py +44 -0
- package/src/trace_engine/testpacks/ssrf.py +85 -0
- package/src/trace_engine/testpacks/ssti.py +96 -0
- package/src/trace_engine/tools/__init__.py +6 -0
- package/src/trace_engine/tools/__pycache__/__init__.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/adapters.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/base.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/__pycache__/registry.cpython-311.pyc +0 -0
- package/src/trace_engine/tools/adapters.py +111 -0
- package/src/trace_engine/tools/base.py +68 -0
- package/src/trace_engine/tools/registry.py +36 -0
- package/src/trace_engine/verify.py +156 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""Mass assignment test pack."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
|
|
5
|
+
from trace_engine.security.hypotheses import SecurityHypothesis
|
|
6
|
+
from trace_engine.runtime.client import ScopedHttpClient
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MassAssignmentTestPack(TestPack):
|
|
10
|
+
@property
|
|
11
|
+
def name(self) -> str:
|
|
12
|
+
return "mass_assignment"
|
|
13
|
+
|
|
14
|
+
@property
|
|
15
|
+
def description(self) -> str:
|
|
16
|
+
return "Validates whether object property updates accept privileged fields (e.g. is_admin, role)."
|
|
17
|
+
|
|
18
|
+
def execute(
|
|
19
|
+
self,
|
|
20
|
+
hypothesis: SecurityHypothesis,
|
|
21
|
+
client: ScopedHttpClient,
|
|
22
|
+
context: TestContext,
|
|
23
|
+
) -> TestExecutionResult:
|
|
24
|
+
base_url = context.target_base_url.rstrip("/")
|
|
25
|
+
parts = hypothesis.endpoint_display.split(" ")
|
|
26
|
+
method = parts[0] if len(parts) > 1 else "PATCH"
|
|
27
|
+
raw_path = parts[1] if len(parts) > 1 else parts[0]
|
|
28
|
+
|
|
29
|
+
probe_path = re.sub(r"\{[a-zA-Z0-9_]+\}", "user-a", raw_path)
|
|
30
|
+
target_url = f"{base_url}{probe_path}"
|
|
31
|
+
|
|
32
|
+
token = context.active_tokens.get("user-a", "")
|
|
33
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
34
|
+
|
|
35
|
+
# Inject privileged fields
|
|
36
|
+
payload = {
|
|
37
|
+
"name": "Updated User",
|
|
38
|
+
"is_admin": True,
|
|
39
|
+
"role": "admin",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
obs = client.execute(method=method, url=target_url, headers=headers, json_body=payload)
|
|
43
|
+
observations = [obs]
|
|
44
|
+
|
|
45
|
+
confirmed = False
|
|
46
|
+
summary = "No mass assignment vulnerability detected."
|
|
47
|
+
steps = []
|
|
48
|
+
|
|
49
|
+
if obs.response_status in (200, 201):
|
|
50
|
+
body_lower = obs.response_body.lower()
|
|
51
|
+
if "is_admin" in body_lower or "role" in body_lower or "admin" in body_lower:
|
|
52
|
+
confirmed = True
|
|
53
|
+
summary = (
|
|
54
|
+
f"Mass Assignment confirmed: Endpoint {raw_path} allowed binding of privileged "
|
|
55
|
+
f"attribute 'is_admin'/'role' into persisted entity."
|
|
56
|
+
)
|
|
57
|
+
steps = [
|
|
58
|
+
f"Send {method} {target_url} with payload: {{'is_admin': True, 'role': 'admin'}}",
|
|
59
|
+
f"Observed HTTP {obs.response_status} with updated administrative privilege in body",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
return TestExecutionResult(
|
|
63
|
+
testpack_name=self.name,
|
|
64
|
+
hypothesis_id=hypothesis.id,
|
|
65
|
+
confirmed=confirmed,
|
|
66
|
+
confidence=0.91 if confirmed else 0.20,
|
|
67
|
+
summary=summary,
|
|
68
|
+
observations=observations,
|
|
69
|
+
reproduction_steps=steps,
|
|
70
|
+
)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
"""Path Traversal and Arbitrary File Access test pack (CWE-22 / CWE-73)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
|
|
6
|
+
from trace_engine.security.hypotheses import SecurityHypothesis
|
|
7
|
+
from trace_engine.runtime.client import ScopedHttpClient
|
|
8
|
+
from trace_engine.runtime.observations import RuntimeObservation
|
|
9
|
+
|
|
10
|
+
TRAVERSAL_PAYLOADS = [
|
|
11
|
+
"../../../../etc/passwd",
|
|
12
|
+
"../../../../../../../../etc/passwd",
|
|
13
|
+
"..\\..\\..\\..\\Windows\\win.ini",
|
|
14
|
+
"%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd",
|
|
15
|
+
"../../../../etc/passwd%00.png",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
SENSITIVE_SIGNATURES = [
|
|
19
|
+
"root:x:",
|
|
20
|
+
"daemon:x:",
|
|
21
|
+
"[extensions]",
|
|
22
|
+
"[fonts]",
|
|
23
|
+
"[files]",
|
|
24
|
+
"for 16-bit app support",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
FS_ERROR_MARKERS = [
|
|
28
|
+
"filenotfoundexception",
|
|
29
|
+
"no such file or directory",
|
|
30
|
+
"java.io.filenotfound",
|
|
31
|
+
"enoent",
|
|
32
|
+
"open_basedir restriction",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PathTraversalTestPack(TestPack):
|
|
37
|
+
@property
|
|
38
|
+
def name(self) -> str:
|
|
39
|
+
return "path_traversal"
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def description(self) -> str:
|
|
43
|
+
return "Validates whether file/path parameters allow escaping directory boundaries to access arbitrary files."
|
|
44
|
+
|
|
45
|
+
def execute(
|
|
46
|
+
self,
|
|
47
|
+
hypothesis: SecurityHypothesis,
|
|
48
|
+
client: ScopedHttpClient,
|
|
49
|
+
context: TestContext,
|
|
50
|
+
) -> TestExecutionResult:
|
|
51
|
+
base_url = context.target_base_url.rstrip("/")
|
|
52
|
+
parts = hypothesis.endpoint_display.split(" ")
|
|
53
|
+
method = parts[0] if len(parts) > 1 else "GET"
|
|
54
|
+
raw_path = parts[1] if len(parts) > 1 else parts[0]
|
|
55
|
+
|
|
56
|
+
token = context.active_tokens.get("user-a", "")
|
|
57
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
58
|
+
|
|
59
|
+
observations: List[RuntimeObservation] = []
|
|
60
|
+
confirmed = False
|
|
61
|
+
summary = "No path traversal vulnerability detected."
|
|
62
|
+
steps: List[str] = []
|
|
63
|
+
|
|
64
|
+
for payload in TRAVERSAL_PAYLOADS:
|
|
65
|
+
# 1. Path parameter substitution if endpoint contains {id} or {path} or {file}
|
|
66
|
+
if "{" in raw_path:
|
|
67
|
+
probed_path = re.sub(r"\{[a-zA-Z0-9_]+\}", payload, raw_path)
|
|
68
|
+
target_url = f"{base_url}{probed_path}"
|
|
69
|
+
obs = client.execute(method=method, url=target_url, headers=headers)
|
|
70
|
+
observations.append(obs)
|
|
71
|
+
else:
|
|
72
|
+
# 2. Query parameter probe
|
|
73
|
+
target_url = f"{base_url}{raw_path}?file={payload}&path={payload}"
|
|
74
|
+
obs = client.execute(method=method, url=target_url, headers=headers)
|
|
75
|
+
observations.append(obs)
|
|
76
|
+
|
|
77
|
+
if obs.response_status in (200, 500):
|
|
78
|
+
body_lower = obs.response_body.lower()
|
|
79
|
+
for sig in SENSITIVE_SIGNATURES:
|
|
80
|
+
if sig.lower() in body_lower:
|
|
81
|
+
confirmed = True
|
|
82
|
+
summary = (
|
|
83
|
+
f"Path Traversal (CWE-22) CONFIRMED: Endpoint {raw_path} returned contents of "
|
|
84
|
+
f"system file when supplied with traversal payload '{payload}'."
|
|
85
|
+
)
|
|
86
|
+
steps = [
|
|
87
|
+
f"Send {method} request to {target_url} with traversal sequence '{payload}'",
|
|
88
|
+
f"Observe leaked file system contents containing '{sig}'",
|
|
89
|
+
]
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
if not confirmed and obs.response_status == 500:
|
|
93
|
+
for marker in FS_ERROR_MARKERS:
|
|
94
|
+
if marker in body_lower:
|
|
95
|
+
confirmed = True
|
|
96
|
+
summary = (
|
|
97
|
+
f"Path Traversal Indicator: File system path error disclosure '{marker}' "
|
|
98
|
+
f"triggered on {raw_path} using traversal payload '{payload}'."
|
|
99
|
+
)
|
|
100
|
+
steps = [
|
|
101
|
+
f"Send {method} {target_url} with traversal probe '{payload}'",
|
|
102
|
+
f"Observe underlying file system path disclosure error '{marker}'",
|
|
103
|
+
]
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
if confirmed:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
return TestExecutionResult(
|
|
110
|
+
testpack_name=self.name,
|
|
111
|
+
hypothesis_id=hypothesis.id,
|
|
112
|
+
confirmed=confirmed,
|
|
113
|
+
confidence=0.95 if confirmed else 0.1,
|
|
114
|
+
summary=summary,
|
|
115
|
+
observations=observations,
|
|
116
|
+
reproduction_steps=steps,
|
|
117
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"""Test pack registry for looking up available security packs."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, List, Optional
|
|
4
|
+
from trace_engine.testpacks.base import TestPack
|
|
5
|
+
from trace_engine.testpacks.bola import BolaTestPack
|
|
6
|
+
from trace_engine.testpacks.bfla import BflaTestPack
|
|
7
|
+
from trace_engine.testpacks.authentication import AuthenticationTestPack
|
|
8
|
+
from trace_engine.testpacks.ssrf import SsrfTestPack
|
|
9
|
+
from trace_engine.testpacks.mass_assignment import MassAssignmentTestPack
|
|
10
|
+
from trace_engine.testpacks.injection import InjectionTestPack
|
|
11
|
+
from trace_engine.testpacks.path_traversal import PathTraversalTestPack
|
|
12
|
+
from trace_engine.testpacks.ssti import SstiTestPack
|
|
13
|
+
from trace_engine.testpacks.cors import CorsTestPack
|
|
14
|
+
from trace_engine.testpacks.deserialization import DeserializationTestPack
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestPackRegistry:
|
|
18
|
+
"""Central registry of registered test packs."""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self._packs: Dict[str, TestPack] = {
|
|
22
|
+
"bola": BolaTestPack(),
|
|
23
|
+
"bfla": BflaTestPack(),
|
|
24
|
+
"authentication": AuthenticationTestPack(),
|
|
25
|
+
"ssrf": SsrfTestPack(),
|
|
26
|
+
"mass_assignment": MassAssignmentTestPack(),
|
|
27
|
+
"injection": InjectionTestPack(),
|
|
28
|
+
"path_traversal": PathTraversalTestPack(),
|
|
29
|
+
"ssti": SstiTestPack(),
|
|
30
|
+
"cors": CorsTestPack(),
|
|
31
|
+
"deserialization": DeserializationTestPack(),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def get(self, name: str) -> Optional[TestPack]:
|
|
35
|
+
return self._packs.get(name.lower())
|
|
36
|
+
|
|
37
|
+
def list_all(self) -> List[TestPack]:
|
|
38
|
+
return list(self._packs.values())
|
|
39
|
+
|
|
40
|
+
def register(self, pack: TestPack) -> None:
|
|
41
|
+
self._packs[pack.name.lower()] = pack
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
default_registry = TestPackRegistry()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""SSRF (Server-Side Request Forgery) test pack with polymorphic payload mutations."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List
|
|
5
|
+
from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
|
|
6
|
+
from trace_engine.security.hypotheses import SecurityHypothesis
|
|
7
|
+
from trace_engine.runtime.client import ScopedHttpClient
|
|
8
|
+
from trace_engine.runtime.observations import RuntimeObservation
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SsrfTestPack(TestPack):
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
return "ssrf"
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def description(self) -> str:
|
|
18
|
+
return "Validates whether server endpoints can be coerced into arbitrary internal network fetches across mutated IP representations."
|
|
19
|
+
|
|
20
|
+
def execute(
|
|
21
|
+
self,
|
|
22
|
+
hypothesis: SecurityHypothesis,
|
|
23
|
+
client: ScopedHttpClient,
|
|
24
|
+
context: TestContext,
|
|
25
|
+
) -> TestExecutionResult:
|
|
26
|
+
base_url = context.target_base_url.rstrip("/")
|
|
27
|
+
parts = hypothesis.endpoint_display.split(" ")
|
|
28
|
+
method = parts[0] if len(parts) > 1 else "POST"
|
|
29
|
+
raw_path = parts[1] if len(parts) > 1 else parts[0]
|
|
30
|
+
|
|
31
|
+
clean_path = re.sub(r"\{[a-zA-Z0-9_]+\}", "1", raw_path)
|
|
32
|
+
target_url = f"{base_url}{clean_path}"
|
|
33
|
+
token = context.active_tokens.get("user-a", "")
|
|
34
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
35
|
+
|
|
36
|
+
# Extract port from base_url for loopback mutations
|
|
37
|
+
port_match = re.search(r":(\d+)", base_url)
|
|
38
|
+
port = port_match.group(1) if port_match else "80"
|
|
39
|
+
|
|
40
|
+
# Polymorphic internal probes
|
|
41
|
+
candidate_probes = [
|
|
42
|
+
# Standard internal endpoint
|
|
43
|
+
f"{base_url}/api/products",
|
|
44
|
+
# Loopback aliases & mutations
|
|
45
|
+
f"http://127.0.0.1:{port}/api/products",
|
|
46
|
+
f"http://localhost:{port}/api/products",
|
|
47
|
+
f"http://127.0.0.1:{port}/health",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
observations: List[RuntimeObservation] = []
|
|
51
|
+
confirmed = False
|
|
52
|
+
summary = "No SSRF behavior observed."
|
|
53
|
+
steps: List[str] = []
|
|
54
|
+
|
|
55
|
+
for probe_url in candidate_probes:
|
|
56
|
+
payload = {"url": probe_url, "target": probe_url, "callbackUrl": probe_url, "webhookUrl": probe_url}
|
|
57
|
+
try:
|
|
58
|
+
obs = client.execute(method=method, url=target_url, headers=headers, json_body=payload)
|
|
59
|
+
observations.append(obs)
|
|
60
|
+
|
|
61
|
+
if obs.response_status == 200:
|
|
62
|
+
body_lower = obs.response_body.lower()
|
|
63
|
+
if any(k in body_lower for k in ("status", "data", "product", "content", "health", "ok", "items")):
|
|
64
|
+
confirmed = True
|
|
65
|
+
summary = (
|
|
66
|
+
f"SSRF (CWE-918) CONFIRMED: Endpoint {raw_path} fetched internal address "
|
|
67
|
+
f"{probe_url} and returned fetched data."
|
|
68
|
+
)
|
|
69
|
+
steps = [
|
|
70
|
+
f"Send {method} {target_url} with JSON payload containing URL: {probe_url}",
|
|
71
|
+
f"Observe internal service response echoed back in response body",
|
|
72
|
+
]
|
|
73
|
+
break
|
|
74
|
+
except Exception:
|
|
75
|
+
continue
|
|
76
|
+
|
|
77
|
+
return TestExecutionResult(
|
|
78
|
+
testpack_name=self.name,
|
|
79
|
+
hypothesis_id=hypothesis.id,
|
|
80
|
+
confirmed=confirmed,
|
|
81
|
+
confidence=0.96 if confirmed else 0.25,
|
|
82
|
+
summary=summary,
|
|
83
|
+
observations=observations,
|
|
84
|
+
reproduction_steps=steps,
|
|
85
|
+
)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Server-Side Template Injection (SSTI) test pack (CWE-1336 / CWE-94)."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import List, Dict, Any
|
|
5
|
+
from trace_engine.testpacks.base import TestPack, TestContext, TestExecutionResult
|
|
6
|
+
from trace_engine.security.hypotheses import SecurityHypothesis
|
|
7
|
+
from trace_engine.runtime.client import ScopedHttpClient
|
|
8
|
+
from trace_engine.runtime.observations import RuntimeObservation
|
|
9
|
+
|
|
10
|
+
SSTI_PROBES = [
|
|
11
|
+
("{{7*7}}", "49"),
|
|
12
|
+
("{{7*'7'}}", "7777777"),
|
|
13
|
+
("${7*7}", "49"),
|
|
14
|
+
("#{7*7}", "49"),
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class SstiTestPack(TestPack):
|
|
19
|
+
@property
|
|
20
|
+
def name(self) -> str:
|
|
21
|
+
return "ssti"
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
def description(self) -> str:
|
|
25
|
+
return "Validates whether template engines dynamically evaluate user-supplied mathematical and script expressions."
|
|
26
|
+
|
|
27
|
+
def execute(
|
|
28
|
+
self,
|
|
29
|
+
hypothesis: SecurityHypothesis,
|
|
30
|
+
client: ScopedHttpClient,
|
|
31
|
+
context: TestContext,
|
|
32
|
+
) -> TestExecutionResult:
|
|
33
|
+
base_url = context.target_base_url.rstrip("/")
|
|
34
|
+
parts = hypothesis.endpoint_display.split(" ")
|
|
35
|
+
method = parts[0] if len(parts) > 1 else "POST"
|
|
36
|
+
raw_path = parts[1] if len(parts) > 1 else parts[0]
|
|
37
|
+
|
|
38
|
+
token = context.active_tokens.get("user-a", "")
|
|
39
|
+
headers = {"Authorization": f"Bearer {token}"} if token else {}
|
|
40
|
+
|
|
41
|
+
observations: List[RuntimeObservation] = []
|
|
42
|
+
confirmed = False
|
|
43
|
+
summary = "No Server-Side Template Injection behavior observed."
|
|
44
|
+
steps: List[str] = []
|
|
45
|
+
|
|
46
|
+
for payload, expected in SSTI_PROBES:
|
|
47
|
+
# 1. Query parameter probe
|
|
48
|
+
target_url = f"{base_url}{raw_path}?template={payload}&name={payload}&msg={payload}"
|
|
49
|
+
obs = client.execute(method="GET", url=target_url, headers=headers)
|
|
50
|
+
observations.append(obs)
|
|
51
|
+
|
|
52
|
+
if obs.response_status == 200:
|
|
53
|
+
if expected in obs.response_body and payload not in obs.response_body:
|
|
54
|
+
confirmed = True
|
|
55
|
+
summary = (
|
|
56
|
+
f"SSTI (CWE-1336) CONFIRMED: Template payload '{payload}' dynamically executed "
|
|
57
|
+
f"and evaluated to '{expected}' in response on {raw_path}."
|
|
58
|
+
)
|
|
59
|
+
steps = [
|
|
60
|
+
f"Send GET {target_url} with template expression '{payload}'",
|
|
61
|
+
f"Observe mathematical execution result '{expected}' evaluated in response body",
|
|
62
|
+
]
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
# 2. JSON Body probe for mutating methods
|
|
66
|
+
if method in ("POST", "PUT", "PATCH"):
|
|
67
|
+
post_url = f"{base_url}{raw_path}"
|
|
68
|
+
body_payload = {"template": payload, "name": payload, "format": payload}
|
|
69
|
+
post_obs = client.execute(method=method, url=post_url, headers=headers, json_body=body_payload)
|
|
70
|
+
observations.append(post_obs)
|
|
71
|
+
|
|
72
|
+
if post_obs.response_status in (200, 201):
|
|
73
|
+
if expected in post_obs.response_body and payload not in post_obs.response_body:
|
|
74
|
+
confirmed = True
|
|
75
|
+
summary = (
|
|
76
|
+
f"SSTI (CWE-1336) CONFIRMED: JSON payload '{payload}' executed in template engine "
|
|
77
|
+
f"yielding '{expected}' on {raw_path}."
|
|
78
|
+
)
|
|
79
|
+
steps = [
|
|
80
|
+
f"Send {method} {post_url} with JSON payload containing '{payload}'",
|
|
81
|
+
f"Observe template evaluation output '{expected}'",
|
|
82
|
+
]
|
|
83
|
+
break
|
|
84
|
+
|
|
85
|
+
if confirmed:
|
|
86
|
+
break
|
|
87
|
+
|
|
88
|
+
return TestExecutionResult(
|
|
89
|
+
testpack_name=self.name,
|
|
90
|
+
hypothesis_id=hypothesis.id,
|
|
91
|
+
confirmed=confirmed,
|
|
92
|
+
confidence=0.95 if confirmed else 0.1,
|
|
93
|
+
summary=summary,
|
|
94
|
+
observations=observations,
|
|
95
|
+
reproduction_steps=steps,
|
|
96
|
+
)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""Specific adapters for open-source security tools."""
|
|
2
|
+
|
|
3
|
+
from trace_engine.tools.base import ToolAdapter
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SchemathesisAdapter(ToolAdapter):
|
|
7
|
+
@property
|
|
8
|
+
def name(self) -> str:
|
|
9
|
+
return "Schemathesis"
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
def command(self) -> str:
|
|
13
|
+
return "schemathesis"
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def description(self) -> str:
|
|
17
|
+
return "Specification-based API property fuzzing and contract validation."
|
|
18
|
+
|
|
19
|
+
@property
|
|
20
|
+
def category(self) -> str:
|
|
21
|
+
return "API Fuzzing"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class NucleiAdapter(ToolAdapter):
|
|
25
|
+
@property
|
|
26
|
+
def name(self) -> str:
|
|
27
|
+
return "Nuclei"
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def command(self) -> str:
|
|
31
|
+
return "nuclei"
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def description(self) -> str:
|
|
35
|
+
return "Fast and customizable vulnerability scanner based on simple YAML DSL."
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def category(self) -> str:
|
|
39
|
+
return "DAST / Templates"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class ZapAdapter(ToolAdapter):
|
|
43
|
+
@property
|
|
44
|
+
def name(self) -> str:
|
|
45
|
+
return "OWASP ZAP"
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def command(self) -> str:
|
|
49
|
+
return "zap.sh"
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def description(self) -> str:
|
|
53
|
+
return "Dynamic application security scanner for web applications."
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def category(self) -> str:
|
|
57
|
+
return "DAST Scanner"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TruffleHogAdapter(ToolAdapter):
|
|
61
|
+
@property
|
|
62
|
+
def name(self) -> str:
|
|
63
|
+
return "TruffleHog"
|
|
64
|
+
|
|
65
|
+
@property
|
|
66
|
+
def command(self) -> str:
|
|
67
|
+
return "trufflehog"
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def description(self) -> str:
|
|
71
|
+
return "High-accuracy credentials and secrets scanner in git repositories."
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def category(self) -> str:
|
|
75
|
+
return "Secret Detection"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class SqlmapAdapter(ToolAdapter):
|
|
79
|
+
@property
|
|
80
|
+
def name(self) -> str:
|
|
81
|
+
return "sqlmap"
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def command(self) -> str:
|
|
85
|
+
return "sqlmap"
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def description(self) -> str:
|
|
89
|
+
return "Automatic SQL injection and database takeover tool."
|
|
90
|
+
|
|
91
|
+
@property
|
|
92
|
+
def category(self) -> str:
|
|
93
|
+
return "Injection Testing"
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class FfufAdapter(ToolAdapter):
|
|
97
|
+
@property
|
|
98
|
+
def name(self) -> str:
|
|
99
|
+
return "ffuf"
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def command(self) -> str:
|
|
103
|
+
return "ffuf"
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def description(self) -> str:
|
|
107
|
+
return "Fast web fuzzer for parameter and endpoint discovery."
|
|
108
|
+
|
|
109
|
+
@property
|
|
110
|
+
def category(self) -> str:
|
|
111
|
+
return "Web Fuzzing"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Base interfaces for external tool adapters."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
from typing import Optional, Dict, Any, List
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ToolInfo(BaseModel):
|
|
11
|
+
name: str
|
|
12
|
+
command: str
|
|
13
|
+
description: str
|
|
14
|
+
installed: bool
|
|
15
|
+
version: Optional[str] = None
|
|
16
|
+
category: str
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ToolAdapter(ABC):
|
|
20
|
+
"""Abstract adapter for external security tools."""
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def name(self) -> str:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def command(self) -> str:
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def description(self) -> str:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def category(self) -> str:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
def is_installed(self) -> bool:
|
|
43
|
+
return shutil.which(self.command) is not None
|
|
44
|
+
|
|
45
|
+
def get_version(self) -> Optional[str]:
|
|
46
|
+
if not self.is_installed():
|
|
47
|
+
return None
|
|
48
|
+
try:
|
|
49
|
+
res = subprocess.run(
|
|
50
|
+
[self.command, "--version"],
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
timeout=3,
|
|
54
|
+
)
|
|
55
|
+
out = (res.stdout or res.stderr).strip().splitlines()
|
|
56
|
+
return out[0] if out else "installed"
|
|
57
|
+
except Exception:
|
|
58
|
+
return "installed"
|
|
59
|
+
|
|
60
|
+
def get_info(self) -> ToolInfo:
|
|
61
|
+
return ToolInfo(
|
|
62
|
+
name=self.name,
|
|
63
|
+
command=self.command,
|
|
64
|
+
description=self.description,
|
|
65
|
+
installed=self.is_installed(),
|
|
66
|
+
version=self.get_version(),
|
|
67
|
+
category=self.category,
|
|
68
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Registry of external security tool adapters."""
|
|
2
|
+
|
|
3
|
+
from typing import List, Dict, Optional
|
|
4
|
+
from trace_engine.tools.base import ToolAdapter, ToolInfo
|
|
5
|
+
from trace_engine.tools.adapters import (
|
|
6
|
+
SchemathesisAdapter,
|
|
7
|
+
NucleiAdapter,
|
|
8
|
+
ZapAdapter,
|
|
9
|
+
TruffleHogAdapter,
|
|
10
|
+
SqlmapAdapter,
|
|
11
|
+
FfufAdapter,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ToolRegistry:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self._tools: Dict[str, ToolAdapter] = {
|
|
18
|
+
"schemathesis": SchemathesisAdapter(),
|
|
19
|
+
"nuclei": NucleiAdapter(),
|
|
20
|
+
"zap": ZapAdapter(),
|
|
21
|
+
"trufflehog": TruffleHogAdapter(),
|
|
22
|
+
"sqlmap": SqlmapAdapter(),
|
|
23
|
+
"ffuf": FfufAdapter(),
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def list_all(self) -> List[ToolAdapter]:
|
|
27
|
+
return list(self._tools.values())
|
|
28
|
+
|
|
29
|
+
def get(self, name: str) -> Optional[ToolAdapter]:
|
|
30
|
+
return self._tools.get(name.lower())
|
|
31
|
+
|
|
32
|
+
def get_all_info(self) -> List[ToolInfo]:
|
|
33
|
+
return [t.get_info() for t in self._tools.values()]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
default_tool_registry = ToolRegistry()
|