@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,42 @@
|
|
|
1
|
+
"""Project-local MCP configuration generator (Section 64)."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def generate_mcp_json(project_dir: Path, trace_cmd_path: Optional[str] = None) -> Path:
|
|
10
|
+
"""Generate .mcp.json in the repository directory."""
|
|
11
|
+
project_dir = project_dir.resolve()
|
|
12
|
+
mcp_file = project_dir / ".mcp.json"
|
|
13
|
+
|
|
14
|
+
import shutil
|
|
15
|
+
|
|
16
|
+
candidate_cmd = (Path(__file__).parent.parent.parent.parent / "trace.cmd").resolve()
|
|
17
|
+
if trace_cmd_path:
|
|
18
|
+
cmd = trace_cmd_path
|
|
19
|
+
args = ["mcp"]
|
|
20
|
+
elif sys.platform == "win32" and candidate_cmd.exists():
|
|
21
|
+
cmd = str(candidate_cmd)
|
|
22
|
+
args = ["mcp"]
|
|
23
|
+
elif shutil.which("trace"):
|
|
24
|
+
cmd = "trace"
|
|
25
|
+
args = ["mcp"]
|
|
26
|
+
else:
|
|
27
|
+
cmd = sys.executable
|
|
28
|
+
args = ["-m", "trace_engine.cli", "mcp"]
|
|
29
|
+
|
|
30
|
+
config_data = {
|
|
31
|
+
"mcpServers": {
|
|
32
|
+
"trace": {
|
|
33
|
+
"command": cmd,
|
|
34
|
+
"args": args,
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
with open(mcp_file, "w", encoding="utf-8") as f:
|
|
40
|
+
json.dump(config_data, f, indent=2)
|
|
41
|
+
|
|
42
|
+
return mcp_file
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
"""TRACE Model Context Protocol (MCP) stdio server implementation."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import logging
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Dict, Any, Optional, List
|
|
8
|
+
|
|
9
|
+
from trace_engine.findings.store import FindingStore
|
|
10
|
+
from trace_engine.verify import VerificationEngine
|
|
11
|
+
from trace_engine.ingest.repository import RepositoryScanner
|
|
12
|
+
from trace_engine.parsing.parser import CodeParser
|
|
13
|
+
from trace_engine.framework import get_adapters
|
|
14
|
+
from trace_engine.apm.builder import APMBuilder
|
|
15
|
+
from trace_engine.apm.model import AttackPathModel
|
|
16
|
+
from trace_engine.security.hypotheses import HypothesisEngine
|
|
17
|
+
from trace_engine.policy.scope import ScopeGuard
|
|
18
|
+
from trace_engine.runtime.client import ScopedHttpClient
|
|
19
|
+
from trace_engine.testpacks.registry import default_registry
|
|
20
|
+
from trace_engine.testpacks.base import TestContext
|
|
21
|
+
from trace_engine.findings.correlate import EvidenceCorrelator
|
|
22
|
+
from trace_engine.config.loader import load_config, get_trace_dir
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger("trace_mcp")
|
|
25
|
+
|
|
26
|
+
# MCP Specification Tool Definitions
|
|
27
|
+
TRACE_TOOLS = [
|
|
28
|
+
{
|
|
29
|
+
"name": "trace_scan",
|
|
30
|
+
"description": "Run complete TRACE static + runtime security audit on repository.",
|
|
31
|
+
"inputSchema": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"repository": {"type": "string", "default": ".", "description": "Path to target repository"},
|
|
35
|
+
"target": {"type": "string", "default": "http://127.0.0.1:18080", "description": "Target HTTP runtime URL"},
|
|
36
|
+
"profile": {"type": "string", "default": "standard", "enum": ["quick", "standard", "deep"]},
|
|
37
|
+
},
|
|
38
|
+
"required": ["repository"],
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"name": "trace_status",
|
|
43
|
+
"description": "Get current status and statistics of TRACE engine in repository.",
|
|
44
|
+
"inputSchema": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"properties": {
|
|
47
|
+
"repository": {"type": "string", "default": "."},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"name": "trace_findings",
|
|
53
|
+
"description": "Retrieve all correlated vulnerability findings for repository.",
|
|
54
|
+
"inputSchema": {
|
|
55
|
+
"type": "object",
|
|
56
|
+
"properties": {
|
|
57
|
+
"repository": {"type": "string", "default": "."},
|
|
58
|
+
"severity": {"type": "string", "description": "Filter by minimum severity"},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"name": "trace_explain",
|
|
64
|
+
"description": "Retrieve root cause explanation, attack path, and evidence for a finding.",
|
|
65
|
+
"inputSchema": {
|
|
66
|
+
"type": "object",
|
|
67
|
+
"properties": {
|
|
68
|
+
"finding_id": {"type": "string", "description": "Finding identifier (e.g. TR-BOLA-001)"},
|
|
69
|
+
"repository": {"type": "string", "default": "."},
|
|
70
|
+
},
|
|
71
|
+
"required": ["finding_id"],
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"name": "trace_attack_path",
|
|
76
|
+
"description": "Inspect Attack-Path Model hops connecting an endpoint to dangerous sinks.",
|
|
77
|
+
"inputSchema": {
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"endpoint": {"type": "string", "description": "Endpoint path (e.g. /api/orders/{id})"},
|
|
81
|
+
"repository": {"type": "string", "default": "."},
|
|
82
|
+
},
|
|
83
|
+
"required": ["endpoint"],
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"name": "trace_verify",
|
|
88
|
+
"description": "Verify whether a code modification by a coding agent resolved a finding.",
|
|
89
|
+
"inputSchema": {
|
|
90
|
+
"type": "object",
|
|
91
|
+
"properties": {
|
|
92
|
+
"finding_id": {"type": "string", "description": "Finding identifier to verify"},
|
|
93
|
+
"repository": {"type": "string", "default": "."},
|
|
94
|
+
"target": {"type": "string", "default": "http://127.0.0.1:18080"},
|
|
95
|
+
},
|
|
96
|
+
"required": ["finding_id"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "trace_harness_task",
|
|
101
|
+
"description": "Retrieve benchmark task specification (SWE-bench format) for a security finding to guide an autonomous agent harness.",
|
|
102
|
+
"inputSchema": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"finding_id": {"type": "string", "description": "Finding identifier (e.g. TR-BOLA-001)"},
|
|
106
|
+
"repository": {"type": "string", "default": "."},
|
|
107
|
+
},
|
|
108
|
+
"required": ["finding_id"],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"name": "trace_eval_patch",
|
|
113
|
+
"description": "Evaluate an agent's proposed patch or diff against the TRACE verification oracle and exploit tests.",
|
|
114
|
+
"inputSchema": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {
|
|
117
|
+
"finding_id": {"type": "string", "description": "Finding identifier"},
|
|
118
|
+
"patch_content": {"type": "string", "description": "Full new content for the target file"},
|
|
119
|
+
"target_file": {"type": "string", "description": "Relative path to target file in repository"},
|
|
120
|
+
"repository": {"type": "string", "default": "."},
|
|
121
|
+
"target": {"type": "string", "default": "http://127.0.0.1:18080"},
|
|
122
|
+
"rollback_after": {"type": "boolean", "default": False, "description": "Whether to rollback the patch after evaluation"},
|
|
123
|
+
},
|
|
124
|
+
"required": ["finding_id"],
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class TraceMCPServer:
|
|
131
|
+
"""Stdio JSON-RPC 2.0 MCP server for coding agent handoff."""
|
|
132
|
+
|
|
133
|
+
def __init__(self):
|
|
134
|
+
self.running = True
|
|
135
|
+
|
|
136
|
+
def handle_request(self, request: Dict[str, Any]) -> Optional[Dict[str, Any]]:
|
|
137
|
+
method = request.get("method")
|
|
138
|
+
msg_id = request.get("id")
|
|
139
|
+
params = request.get("params", {})
|
|
140
|
+
|
|
141
|
+
if method == "initialize":
|
|
142
|
+
return {
|
|
143
|
+
"jsonrpc": "2.0",
|
|
144
|
+
"id": msg_id,
|
|
145
|
+
"result": {
|
|
146
|
+
"protocolVersion": "2024-11-05",
|
|
147
|
+
"serverInfo": {
|
|
148
|
+
"name": "trace-security-mcp",
|
|
149
|
+
"version": "2.0.0",
|
|
150
|
+
},
|
|
151
|
+
"capabilities": {"tools": {}},
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
elif method == "notifications/initialized":
|
|
156
|
+
return None
|
|
157
|
+
|
|
158
|
+
elif method == "tools/list":
|
|
159
|
+
return {
|
|
160
|
+
"jsonrpc": "2.0",
|
|
161
|
+
"id": msg_id,
|
|
162
|
+
"result": {"tools": TRACE_TOOLS},
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
elif method == "tools/call":
|
|
166
|
+
tool_name = params.get("name")
|
|
167
|
+
args = params.get("arguments", {})
|
|
168
|
+
try:
|
|
169
|
+
res_content = self.execute_tool(tool_name, args)
|
|
170
|
+
return {
|
|
171
|
+
"jsonrpc": "2.0",
|
|
172
|
+
"id": msg_id,
|
|
173
|
+
"result": {
|
|
174
|
+
"content": [
|
|
175
|
+
{"type": "text", "text": json.dumps(res_content, indent=2)}
|
|
176
|
+
]
|
|
177
|
+
},
|
|
178
|
+
}
|
|
179
|
+
except Exception as e:
|
|
180
|
+
return {
|
|
181
|
+
"jsonrpc": "2.0",
|
|
182
|
+
"id": msg_id,
|
|
183
|
+
"error": {"code": -32000, "message": str(e)},
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
elif method == "ping":
|
|
187
|
+
return {"jsonrpc": "2.0", "id": msg_id, "result": {}}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
"jsonrpc": "2.0",
|
|
191
|
+
"id": msg_id,
|
|
192
|
+
"error": {"code": -32601, "message": f"Method {method} not found"},
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
def execute_tool(self, name: str, args: Dict[str, Any]) -> Any:
|
|
196
|
+
repo_path = Path(args.get("repository", ".")).resolve()
|
|
197
|
+
target_url = args.get("target", "http://127.0.0.1:18080")
|
|
198
|
+
|
|
199
|
+
if name == "trace_status":
|
|
200
|
+
trace_dir = repo_path / ".trace"
|
|
201
|
+
store = FindingStore(trace_dir)
|
|
202
|
+
findings = store.load_findings()
|
|
203
|
+
return {
|
|
204
|
+
"repository": str(repo_path),
|
|
205
|
+
"initialized": trace_dir.exists(),
|
|
206
|
+
"total_findings": len(findings),
|
|
207
|
+
"confirmed": sum(1 for f in findings if f.confidence.value == "CONFIRMED"),
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
elif name == "trace_findings":
|
|
211
|
+
store = FindingStore(repo_path / ".trace")
|
|
212
|
+
findings = store.load_findings()
|
|
213
|
+
return [
|
|
214
|
+
{
|
|
215
|
+
"id": f.id,
|
|
216
|
+
"title": f.title,
|
|
217
|
+
"category": f.category,
|
|
218
|
+
"severity": f.severity.value,
|
|
219
|
+
"confidence": f.confidence.value,
|
|
220
|
+
"endpoint": f.endpoint,
|
|
221
|
+
"remediation": f.remediation,
|
|
222
|
+
"source": str(f.source_location) if f.source_location else None,
|
|
223
|
+
}
|
|
224
|
+
for f in findings
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
elif name == "trace_explain":
|
|
228
|
+
finding_id = args.get("finding_id", "")
|
|
229
|
+
store = FindingStore(repo_path / ".trace")
|
|
230
|
+
finding = store.get_finding_by_id(finding_id)
|
|
231
|
+
if not finding:
|
|
232
|
+
return {"error": f"Finding {finding_id} not found."}
|
|
233
|
+
return {
|
|
234
|
+
"id": finding.id,
|
|
235
|
+
"title": finding.title,
|
|
236
|
+
"category": finding.category,
|
|
237
|
+
"severity": finding.severity.value,
|
|
238
|
+
"confidence": finding.confidence.value,
|
|
239
|
+
"endpoint": finding.endpoint,
|
|
240
|
+
"source": str(finding.source_location),
|
|
241
|
+
"attack_path": finding.attack_path,
|
|
242
|
+
"static_evidence": finding.static_evidence,
|
|
243
|
+
"runtime_evidence": finding.runtime_evidence,
|
|
244
|
+
"correlation_notes": finding.correlation_notes,
|
|
245
|
+
"reproduction_steps": finding.reproduction_steps,
|
|
246
|
+
"remediation": finding.remediation,
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
elif name == "trace_verify":
|
|
250
|
+
finding_id = args.get("finding_id", "")
|
|
251
|
+
engine = VerificationEngine(repo_path=repo_path, target_url=target_url)
|
|
252
|
+
res = engine.verify(finding_id)
|
|
253
|
+
return res.model_dump()
|
|
254
|
+
|
|
255
|
+
elif name == "trace_scan":
|
|
256
|
+
# Execute scan programmatic flow
|
|
257
|
+
scanner = RepositoryScanner(repo_path)
|
|
258
|
+
source_files = scanner.scan()
|
|
259
|
+
parser = CodeParser()
|
|
260
|
+
adapters = get_adapters()
|
|
261
|
+
|
|
262
|
+
parsed_files = []
|
|
263
|
+
discovered_endpoints = []
|
|
264
|
+
for sf in source_files:
|
|
265
|
+
content = Path(sf.absolute_path).read_text(encoding="utf-8", errors="replace")
|
|
266
|
+
pf = parser.parse(sf.path, content, sf.language, absolute_path=sf.absolute_path)
|
|
267
|
+
parsed_files.append(pf)
|
|
268
|
+
for adapter in adapters:
|
|
269
|
+
if adapter.can_handle(pf):
|
|
270
|
+
discovered_endpoints.extend(adapter.extract_endpoints(pf, content))
|
|
271
|
+
|
|
272
|
+
builder = APMBuilder(project_name=repo_path.name)
|
|
273
|
+
apm = builder.build(repo_path, source_files, parsed_files, discovered_endpoints)
|
|
274
|
+
|
|
275
|
+
engine = HypothesisEngine()
|
|
276
|
+
hypotheses = engine.derive_hypotheses(apm, discovered_endpoints)
|
|
277
|
+
|
|
278
|
+
scope_guard = ScopeGuard()
|
|
279
|
+
scope_guard.allow_target(target_url)
|
|
280
|
+
client = ScopedHttpClient(scope_guard=scope_guard)
|
|
281
|
+
context = TestContext(
|
|
282
|
+
target_base_url=target_url,
|
|
283
|
+
active_tokens={"user-a": "user-a", "user-b": "user-b", "admin": "admin"},
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
correlator = EvidenceCorrelator()
|
|
287
|
+
findings = []
|
|
288
|
+
for hyp in hypotheses:
|
|
289
|
+
pack = default_registry.get(hyp.recommended_test_pack)
|
|
290
|
+
test_res = None
|
|
291
|
+
if pack:
|
|
292
|
+
try:
|
|
293
|
+
test_res = pack.execute(hyp, client, context)
|
|
294
|
+
except Exception:
|
|
295
|
+
test_res = None
|
|
296
|
+
f = correlator.correlate(hyp, test_res, apm)
|
|
297
|
+
if f:
|
|
298
|
+
findings.append(f)
|
|
299
|
+
|
|
300
|
+
store = FindingStore(repo_path / ".trace")
|
|
301
|
+
store.save_findings(findings)
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
"status": "completed",
|
|
305
|
+
"total_findings": len(findings),
|
|
306
|
+
"confirmed": sum(1 for f in findings if f.confidence.value == "CONFIRMED"),
|
|
307
|
+
"endpoints_audited": len(discovered_endpoints),
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
elif name == "trace_attack_path":
|
|
311
|
+
endpoint_str = args.get("endpoint", "")
|
|
312
|
+
store = FindingStore(repo_path / ".trace")
|
|
313
|
+
for f in store.load_findings():
|
|
314
|
+
if endpoint_str.lower() in f.endpoint.lower():
|
|
315
|
+
return {"endpoint": f.endpoint, "attack_path": f.attack_path}
|
|
316
|
+
return {"endpoint": endpoint_str, "attack_path": []}
|
|
317
|
+
|
|
318
|
+
elif name == "trace_harness_task":
|
|
319
|
+
finding_id = args.get("finding_id", "")
|
|
320
|
+
from trace_engine.plugin import TraceHarnessPlugin
|
|
321
|
+
plugin = TraceHarnessPlugin(repo_path=repo_path, target_url=target_url)
|
|
322
|
+
task = plugin.get_task(finding_id)
|
|
323
|
+
if not task:
|
|
324
|
+
return {"error": f"Harness task for finding '{finding_id}' not found."}
|
|
325
|
+
return task.model_dump()
|
|
326
|
+
|
|
327
|
+
elif name == "trace_eval_patch":
|
|
328
|
+
finding_id = args.get("finding_id", "")
|
|
329
|
+
patch_content = args.get("patch_content")
|
|
330
|
+
target_file = args.get("target_file")
|
|
331
|
+
rollback = args.get("rollback_after", False)
|
|
332
|
+
from trace_engine.plugin import TraceHarnessPlugin
|
|
333
|
+
plugin = TraceHarnessPlugin(repo_path=repo_path, target_url=target_url)
|
|
334
|
+
eval_res = plugin.evaluate_patch(
|
|
335
|
+
finding_id=finding_id,
|
|
336
|
+
patch_content=patch_content,
|
|
337
|
+
target_file=target_file,
|
|
338
|
+
rollback_after=rollback,
|
|
339
|
+
)
|
|
340
|
+
return eval_res.model_dump()
|
|
341
|
+
|
|
342
|
+
return {"error": f"Unknown tool: {name}"}
|
|
343
|
+
|
|
344
|
+
def run_stdio(self):
|
|
345
|
+
"""Run the stdio JSON-RPC loop supporting both line-delimited JSON and Content-Length headers."""
|
|
346
|
+
while self.running:
|
|
347
|
+
line = sys.stdin.readline()
|
|
348
|
+
if not line:
|
|
349
|
+
break
|
|
350
|
+
line_str = line.strip()
|
|
351
|
+
if not line_str:
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
# Support Content-Length framing if sent by client
|
|
355
|
+
if line_str.lower().startswith("content-length:"):
|
|
356
|
+
try:
|
|
357
|
+
content_length = int(line_str.split(":", 1)[1].strip())
|
|
358
|
+
while True:
|
|
359
|
+
hdr = sys.stdin.readline()
|
|
360
|
+
if not hdr or hdr.strip() == "":
|
|
361
|
+
break
|
|
362
|
+
body = sys.stdin.read(content_length)
|
|
363
|
+
req = json.loads(body)
|
|
364
|
+
except Exception as e:
|
|
365
|
+
err_resp = {
|
|
366
|
+
"jsonrpc": "2.0",
|
|
367
|
+
"id": None,
|
|
368
|
+
"error": {"code": -32700, "message": f"Parse error: {e}"},
|
|
369
|
+
}
|
|
370
|
+
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
371
|
+
sys.stdout.flush()
|
|
372
|
+
continue
|
|
373
|
+
else:
|
|
374
|
+
try:
|
|
375
|
+
req = json.loads(line_str)
|
|
376
|
+
except Exception as e:
|
|
377
|
+
err_resp = {
|
|
378
|
+
"jsonrpc": "2.0",
|
|
379
|
+
"id": None,
|
|
380
|
+
"error": {"code": -32700, "message": f"Parse error: {e}"},
|
|
381
|
+
}
|
|
382
|
+
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
383
|
+
sys.stdout.flush()
|
|
384
|
+
continue
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
resp = self.handle_request(req)
|
|
388
|
+
if resp is not None:
|
|
389
|
+
sys.stdout.write(json.dumps(resp) + "\n")
|
|
390
|
+
sys.stdout.flush()
|
|
391
|
+
except Exception as e:
|
|
392
|
+
err_resp = {
|
|
393
|
+
"jsonrpc": "2.0",
|
|
394
|
+
"id": req.get("id") if isinstance(req, dict) else None,
|
|
395
|
+
"error": {"code": -32603, "message": f"Internal error: {e}"},
|
|
396
|
+
}
|
|
397
|
+
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
398
|
+
sys.stdout.flush()
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Output and rendering module."""
|
|
2
|
+
|
|
3
|
+
from trace_engine.output.terminal import (
|
|
4
|
+
print_banner,
|
|
5
|
+
print_security_notes,
|
|
6
|
+
print_endpoints_table,
|
|
7
|
+
print_hypotheses_table,
|
|
8
|
+
print_findings_table,
|
|
9
|
+
print_finding_detail,
|
|
10
|
+
print_doctor_report,
|
|
11
|
+
)
|
|
12
|
+
from trace_engine.output.markdown import generate_markdown_report
|
|
13
|
+
from trace_engine.output.html import generate_html_report
|
|
14
|
+
from trace_engine.output.sarif import generate_sarif_report, export_sarif_json
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"print_banner",
|
|
18
|
+
"print_security_notes",
|
|
19
|
+
"print_endpoints_table",
|
|
20
|
+
"print_hypotheses_table",
|
|
21
|
+
"print_findings_table",
|
|
22
|
+
"print_finding_detail",
|
|
23
|
+
"print_doctor_report",
|
|
24
|
+
"generate_markdown_report",
|
|
25
|
+
"generate_html_report",
|
|
26
|
+
"generate_sarif_report",
|
|
27
|
+
"export_sarif_json",
|
|
28
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""Standalone dark-themed HTML report generator with green accents."""
|
|
2
|
+
|
|
3
|
+
import html
|
|
4
|
+
from typing import List
|
|
5
|
+
from trace_engine.findings.model import Finding
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_html_report(findings: List[Finding], project_name: str = "Application") -> str:
|
|
9
|
+
cards = []
|
|
10
|
+
for f in findings:
|
|
11
|
+
sev_color = {
|
|
12
|
+
"CRITICAL": "#ff4d4d",
|
|
13
|
+
"HIGH": "#ff7733",
|
|
14
|
+
"MEDIUM": "#ffcc00",
|
|
15
|
+
"LOW": "#33ccff",
|
|
16
|
+
}.get(f.severity.value, "#888888")
|
|
17
|
+
|
|
18
|
+
cards.append(f"""
|
|
19
|
+
<div class="card">
|
|
20
|
+
<div class="card-header">
|
|
21
|
+
<span class="finding-id">{html.escape(f.id)}</span>
|
|
22
|
+
<span class="badge" style="background: {sev_color}; color: #000;">{f.severity.value}</span>
|
|
23
|
+
<span class="badge badge-conf">{f.confidence.value}</span>
|
|
24
|
+
<span class="category">{html.escape(f.category)}</span>
|
|
25
|
+
</div>
|
|
26
|
+
<h3 class="card-title">{html.escape(f.title)}</h3>
|
|
27
|
+
<p class="endpoint"><code>{html.escape(f.endpoint)}</code></p>
|
|
28
|
+
<div class="section-title">Static Code Evidence</div>
|
|
29
|
+
<ul>{"".join(f"<li>{html.escape(e)}</li>" for e in f.static_evidence)}</ul>
|
|
30
|
+
<div class="section-title">Runtime Behavioral Evidence</div>
|
|
31
|
+
<ul>{"".join(f"<li>{html.escape(e)}</li>" for e in f.runtime_evidence)}</ul>
|
|
32
|
+
<div class="section-title">Remediation</div>
|
|
33
|
+
<p class="remediation">{html.escape(f.remediation)}</p>
|
|
34
|
+
</div>
|
|
35
|
+
""")
|
|
36
|
+
|
|
37
|
+
return f"""<!DOCTYPE html>
|
|
38
|
+
<html lang="en">
|
|
39
|
+
<head>
|
|
40
|
+
<meta charset="UTF-8">
|
|
41
|
+
<title>TRACE Report — {html.escape(project_name)}</title>
|
|
42
|
+
<style>
|
|
43
|
+
body {{
|
|
44
|
+
background-color: #0c0d0e;
|
|
45
|
+
color: #e6edf3;
|
|
46
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
|
|
47
|
+
margin: 0;
|
|
48
|
+
padding: 30px;
|
|
49
|
+
}}
|
|
50
|
+
.header {{
|
|
51
|
+
border-bottom: 1px solid #21262d;
|
|
52
|
+
padding-bottom: 20px;
|
|
53
|
+
margin-bottom: 30px;
|
|
54
|
+
}}
|
|
55
|
+
h1 {{
|
|
56
|
+
color: #00FF66;
|
|
57
|
+
margin: 0 0 8px 0;
|
|
58
|
+
font-size: 28px;
|
|
59
|
+
letter-spacing: -0.5px;
|
|
60
|
+
}}
|
|
61
|
+
.subtitle {{
|
|
62
|
+
color: #8b949e;
|
|
63
|
+
font-size: 14px;
|
|
64
|
+
}}
|
|
65
|
+
.card {{
|
|
66
|
+
background: #161b22;
|
|
67
|
+
border: 1px solid #30363d;
|
|
68
|
+
border-radius: 8px;
|
|
69
|
+
padding: 20px;
|
|
70
|
+
margin-bottom: 20px;
|
|
71
|
+
}}
|
|
72
|
+
.card-header {{
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 12px;
|
|
76
|
+
margin-bottom: 12px;
|
|
77
|
+
}}
|
|
78
|
+
.finding-id {{
|
|
79
|
+
font-family: monospace;
|
|
80
|
+
font-weight: bold;
|
|
81
|
+
color: #00FF66;
|
|
82
|
+
}}
|
|
83
|
+
.badge {{
|
|
84
|
+
font-size: 11px;
|
|
85
|
+
font-weight: bold;
|
|
86
|
+
padding: 2px 8px;
|
|
87
|
+
border-radius: 4px;
|
|
88
|
+
text-transform: uppercase;
|
|
89
|
+
}}
|
|
90
|
+
.badge-conf {{
|
|
91
|
+
background: #00FF66;
|
|
92
|
+
color: #000;
|
|
93
|
+
}}
|
|
94
|
+
.category {{
|
|
95
|
+
color: #8b949e;
|
|
96
|
+
font-size: 12px;
|
|
97
|
+
}}
|
|
98
|
+
.card-title {{
|
|
99
|
+
margin: 0 0 10px 0;
|
|
100
|
+
font-size: 18px;
|
|
101
|
+
}}
|
|
102
|
+
code {{
|
|
103
|
+
background: #0d1117;
|
|
104
|
+
padding: 4px 8px;
|
|
105
|
+
border-radius: 4px;
|
|
106
|
+
color: #00FF66;
|
|
107
|
+
}}
|
|
108
|
+
.section-title {{
|
|
109
|
+
font-size: 12px;
|
|
110
|
+
font-weight: bold;
|
|
111
|
+
color: #8b949e;
|
|
112
|
+
text-transform: uppercase;
|
|
113
|
+
margin-top: 15px;
|
|
114
|
+
margin-bottom: 5px;
|
|
115
|
+
}}
|
|
116
|
+
ul {{
|
|
117
|
+
margin: 0 0 10px 0;
|
|
118
|
+
padding-left: 20px;
|
|
119
|
+
color: #c9d1d9;
|
|
120
|
+
font-size: 14px;
|
|
121
|
+
}}
|
|
122
|
+
.remediation {{
|
|
123
|
+
background: #0d1117;
|
|
124
|
+
border-left: 3px solid #00FF66;
|
|
125
|
+
padding: 10px 14px;
|
|
126
|
+
font-size: 13px;
|
|
127
|
+
color: #8b949e;
|
|
128
|
+
margin: 5px 0 0 0;
|
|
129
|
+
}}
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<div class="header">
|
|
134
|
+
<h1>TRACE Security Assessment</h1>
|
|
135
|
+
<div class="subtitle">Threat Reconnaissance & Attack-path Correlation Engine • {html.escape(project_name)}</div>
|
|
136
|
+
</div>
|
|
137
|
+
<div class="findings-container">
|
|
138
|
+
{"".join(cards) if cards else "<p>No correlated vulnerabilities detected.</p>"}
|
|
139
|
+
</div>
|
|
140
|
+
</body>
|
|
141
|
+
</html>
|
|
142
|
+
"""
|