trace-sec 2.1.4 → 2.1.6
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/bin/trace.js +43 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/src/trace_engine/__pycache__/cli.cpython-311.pyc +0 -0
- package/src/trace_engine/__pycache__/interactive.cpython-311.pyc +0 -0
- package/src/trace_engine/cli.py +46 -9
- package/src/trace_engine/intelligence/__pycache__/downloader.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/downloader.py +155 -44
- package/src/trace_engine/intelligence/laya/__pycache__/router.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/laya/router.py +3 -0
- package/src/trace_engine/intelligence/securebert/__pycache__/classifier.cpython-311.pyc +0 -0
- package/src/trace_engine/intelligence/securebert/classifier.py +3 -0
- package/src/trace_engine/interactive.py +17 -6
- package/src/trace_engine/mcp/__pycache__/server.cpython-311.pyc +0 -0
- package/src/trace_engine/mcp/server.py +171 -0
package/bin/trace.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Node.js & NPX launcher for TRACE Python engine.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { spawn } from 'child_process';
|
|
8
|
+
import { spawn, execSync } from 'child_process';
|
|
9
9
|
import { existsSync, mkdirSync, cpSync, writeFileSync } from 'fs';
|
|
10
10
|
import { resolve, join, dirname } from 'path';
|
|
11
11
|
import { fileURLToPath } from 'url';
|
|
@@ -102,6 +102,48 @@ for (const cand of candidatePythons) {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
+
// Verify that the resolved Python environment has required TRACE dependencies
|
|
106
|
+
let depsOk = false;
|
|
107
|
+
try {
|
|
108
|
+
execSync(`"${pythonBin}" -c "import typer, rich, pydantic"`, { stdio: 'ignore' });
|
|
109
|
+
depsOk = true;
|
|
110
|
+
} catch (e) {
|
|
111
|
+
depsOk = false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (!depsOk) {
|
|
115
|
+
const dedicatedVenv = join(userHome, '.trace', 'venv');
|
|
116
|
+
const venvPython = join(dedicatedVenv, isWin ? 'Scripts' : 'bin', isWin ? 'python.exe' : 'python');
|
|
117
|
+
|
|
118
|
+
if (existsSync(venvPython)) {
|
|
119
|
+
try {
|
|
120
|
+
execSync(`"${venvPython}" -c "import typer, rich, pydantic"`, { stdio: 'ignore' });
|
|
121
|
+
pythonBin = venvPython;
|
|
122
|
+
depsOk = true;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
depsOk = false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!depsOk) {
|
|
129
|
+
console.log('\n\x1b[32m\x1b[1m[TRACE]\x1b[0m Initializing TRACE runtime dependencies in dedicated environment (~/.trace/venv)...');
|
|
130
|
+
try {
|
|
131
|
+
mkdirSync(join(userHome, '.trace'), { recursive: true });
|
|
132
|
+
if (!existsSync(venvPython)) {
|
|
133
|
+
console.log(' \x1b[38;2;255;158;59m›\x1b[0m Creating Python virtual environment in ~/.trace/venv...');
|
|
134
|
+
execSync(`"${pythonBin}" -m venv "${dedicatedVenv}"`, { stdio: 'inherit' });
|
|
135
|
+
}
|
|
136
|
+
console.log(' \x1b[38;2;255;158;59m›\x1b[0m Installing core dependencies (typer, rich, pydantic, tree-sitter, torch, transformers)...');
|
|
137
|
+
execSync(`"${venvPython}" -m pip install typer rich pydantic pydantic-settings networkx httpx orjson tomli-w tree-sitter tree-sitter-language-pack torch transformers safetensors onnxruntime numpy`, { stdio: 'inherit' });
|
|
138
|
+
pythonBin = venvPython;
|
|
139
|
+
console.log('\x1b[32m✓ Runtime dependencies provisioned successfully.\x1b[0m\n');
|
|
140
|
+
} catch (e) {
|
|
141
|
+
console.warn(`[TRACE] Automatic environment setup warning: ${e.message}`);
|
|
142
|
+
console.warn(`If command fails, install directly via: pip install git+https://github.com/VK-Amogh/TRACE.git\n`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
105
147
|
const cliArgs = ['-m', 'trace_engine.cli', ...args];
|
|
106
148
|
|
|
107
149
|
const proc = spawn(pythonBin, cliArgs, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "trace-sec",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "Threat Reconnaissance & Attack-path Correlation Engine — Security Verification Layer for Coding Agents with Fine-Tuned SecureBERT 2.0 & Laya AI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "bin/trace.js",
|
package/pyproject.toml
CHANGED
|
Binary file
|
|
Binary file
|
package/src/trace_engine/cli.py
CHANGED
|
@@ -320,6 +320,10 @@ def scan(
|
|
|
320
320
|
):
|
|
321
321
|
"""Execute end-to-end scan: Ingestion -> APM -> Signals -> Runtime Tests -> Correlated Findings."""
|
|
322
322
|
project_dir = path.resolve()
|
|
323
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
324
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
325
|
+
raise typer.Exit(code=1)
|
|
326
|
+
|
|
323
327
|
config = load_config(project_dir)
|
|
324
328
|
|
|
325
329
|
console.print(f"\n[bold green]Starting TRACE Full Scan[/bold green] on [white]{project_dir.name}[/white]")
|
|
@@ -438,6 +442,11 @@ def test_all(
|
|
|
438
442
|
from trace_engine.intelligence.orchestrator import IntelligenceOrchestrator
|
|
439
443
|
|
|
440
444
|
project_dir = path.resolve()
|
|
445
|
+
if models:
|
|
446
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
447
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
448
|
+
raise typer.Exit(code=1)
|
|
449
|
+
|
|
441
450
|
config = load_config(project_dir)
|
|
442
451
|
|
|
443
452
|
resolved_target = target if target else (config.target.url or "http://127.0.0.1:18080")
|
|
@@ -824,29 +833,53 @@ def verify(
|
|
|
824
833
|
|
|
825
834
|
|
|
826
835
|
@app.command()
|
|
827
|
-
def mcp(
|
|
828
|
-
|
|
836
|
+
def mcp(
|
|
837
|
+
server: bool = typer.Option(False, "--server", "-s", help="Run local HTTP/SSE server instead of stdio"),
|
|
838
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host interface to bind HTTP/SSE server"),
|
|
839
|
+
port: int = typer.Option(8765, "--port", "-p", help="Port to bind HTTP/SSE server"),
|
|
840
|
+
):
|
|
841
|
+
"""Start TRACE Model Context Protocol (MCP) server for Claude Code and coding agents."""
|
|
842
|
+
from trace_engine.mcp.server import TraceMCPServer
|
|
843
|
+
|
|
844
|
+
mcp_obj = TraceMCPServer()
|
|
845
|
+
if server:
|
|
846
|
+
mcp_obj.run_sse_server(host=host, port=port)
|
|
847
|
+
else:
|
|
848
|
+
mcp_obj.run_stdio()
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
@app.command(name="mcp-server")
|
|
852
|
+
@app.command(name="serve-mcp")
|
|
853
|
+
def mcp_server_cmd(
|
|
854
|
+
host: str = typer.Option("127.0.0.1", "--host", "-h", help="Host interface to bind HTTP/SSE server"),
|
|
855
|
+
port: int = typer.Option(8765, "--port", "-p", help="Port to bind HTTP/SSE server"),
|
|
856
|
+
):
|
|
857
|
+
"""Start local HTTP/SSE TRACE MCP server and display connection link for Claude Code."""
|
|
829
858
|
from trace_engine.mcp.server import TraceMCPServer
|
|
830
859
|
|
|
831
|
-
|
|
832
|
-
|
|
860
|
+
mcp_obj = TraceMCPServer()
|
|
861
|
+
mcp_obj.run_sse_server(host=host, port=port)
|
|
833
862
|
|
|
834
863
|
|
|
835
864
|
@app.command("setup-mcp")
|
|
836
865
|
def setup_mcp(
|
|
837
866
|
path: Path = typer.Argument(Path("."), help="Path to project repository")
|
|
838
867
|
):
|
|
839
|
-
"""Generate .mcp.json and configure MCP coding agent integration
|
|
868
|
+
"""Generate .mcp.json and configure MCP coding agent integration."""
|
|
840
869
|
from trace_engine.mcp.config import generate_mcp_json
|
|
841
870
|
|
|
842
871
|
project_dir = path.resolve()
|
|
843
872
|
mcp_file = generate_mcp_json(project_dir)
|
|
844
873
|
|
|
845
874
|
console.print(f"\n[bold green]Configured TRACE MCP[/bold green] in [white]{mcp_file}[/white]")
|
|
846
|
-
console.print("\n[bold white]
|
|
847
|
-
console.print(" [green]claude mcp add trace --
|
|
848
|
-
console.print("\n[bold white]
|
|
849
|
-
console.print("
|
|
875
|
+
console.print("\n[bold white]Option A: Direct Stdio Integration in Claude Code:[/bold white]")
|
|
876
|
+
console.print(" [green]claude mcp add trace -- npx -y trace-sec mcp[/green]")
|
|
877
|
+
console.print("\n[bold white]Option B: Local HTTP/SSE Server Link (Real-Time Bridge):[/bold white]")
|
|
878
|
+
console.print(" 1. Launch server: [bold green]trace mcp-server[/bold green]")
|
|
879
|
+
console.print(" 2. Connect via CLI: [green]claude mcp add --transport sse trace http://127.0.0.1:8765/sse[/green]")
|
|
880
|
+
console.print(" 3. Or paste [cyan]http://127.0.0.1:8765/sse[/cyan] directly in your Claude Code chat!")
|
|
881
|
+
console.print("\n[bold white]Option C: Cursor IDE / Antigravity / Claude Code:[/bold white]")
|
|
882
|
+
console.print(" [dim]The created .mcp.json is already active in your workspace.[/dim]\n")
|
|
850
883
|
|
|
851
884
|
|
|
852
885
|
@app.command()
|
|
@@ -885,6 +918,10 @@ def remediate(
|
|
|
885
918
|
"""Execute autonomous Agent Harness self-healing and remediation loop: patch vulnerabilities and verify fixes."""
|
|
886
919
|
from trace_engine.harness.engine import AgentHarness
|
|
887
920
|
|
|
921
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
922
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
923
|
+
raise typer.Exit(code=1)
|
|
924
|
+
|
|
888
925
|
target_path = repo or path
|
|
889
926
|
_, resolved_dir = resolve_finding_store(target_path.resolve())
|
|
890
927
|
resolved_target = target or "http://127.0.0.1:18082"
|
|
Binary file
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Automated model downloader and provisioner for TRACE fine-tuned weights."""
|
|
1
|
+
"""Automated model downloader, verification gate, and provisioner for TRACE fine-tuned weights."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import sys
|
|
@@ -17,19 +17,31 @@ MODEL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
|
|
17
17
|
"securebert-finetuned": {
|
|
18
18
|
"files": {
|
|
19
19
|
"config.json": {
|
|
20
|
-
"
|
|
20
|
+
"urls": [
|
|
21
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/config.json",
|
|
22
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/config.json",
|
|
23
|
+
],
|
|
21
24
|
"min_size": 500,
|
|
22
25
|
},
|
|
23
26
|
"tokenizer.json": {
|
|
24
|
-
"
|
|
27
|
+
"urls": [
|
|
28
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/tokenizer.json",
|
|
29
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer.json",
|
|
30
|
+
],
|
|
25
31
|
"min_size": 1000000,
|
|
26
32
|
},
|
|
27
33
|
"tokenizer_config.json": {
|
|
28
|
-
"
|
|
34
|
+
"urls": [
|
|
35
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/tokenizer_config.json",
|
|
36
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer_config.json",
|
|
37
|
+
],
|
|
29
38
|
"min_size": 200,
|
|
30
39
|
},
|
|
31
40
|
"model.safetensors": {
|
|
32
|
-
"
|
|
41
|
+
"urls": [
|
|
42
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/securebert-finetuned/model.safetensors",
|
|
43
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/model.safetensors",
|
|
44
|
+
],
|
|
33
45
|
"min_size": 100000000, # ~498 MB
|
|
34
46
|
},
|
|
35
47
|
},
|
|
@@ -37,23 +49,38 @@ MODEL_REGISTRY: Dict[str, Dict[str, Any]] = {
|
|
|
37
49
|
"laya-finetuned": {
|
|
38
50
|
"files": {
|
|
39
51
|
"laya_metadata.json": {
|
|
40
|
-
"
|
|
52
|
+
"urls": [
|
|
53
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_metadata.json",
|
|
54
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_metadata.json",
|
|
55
|
+
],
|
|
41
56
|
"min_size": 300,
|
|
42
57
|
},
|
|
43
58
|
"tokenizer.json": {
|
|
44
|
-
"
|
|
59
|
+
"urls": [
|
|
60
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/tokenizer.json",
|
|
61
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer.json",
|
|
62
|
+
],
|
|
45
63
|
"min_size": 500000,
|
|
46
64
|
},
|
|
47
65
|
"tokenizer_config.json": {
|
|
48
|
-
"
|
|
66
|
+
"urls": [
|
|
67
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/tokenizer_config.json",
|
|
68
|
+
f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer_config.json",
|
|
69
|
+
],
|
|
49
70
|
"min_size": 200,
|
|
50
71
|
},
|
|
51
72
|
"laya_dual_head.onnx": {
|
|
52
|
-
"
|
|
73
|
+
"urls": [
|
|
74
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_dual_head.onnx",
|
|
75
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.onnx",
|
|
76
|
+
],
|
|
53
77
|
"min_size": 50000000, # ~267 MB
|
|
54
78
|
},
|
|
55
79
|
"laya_dual_head.pt": {
|
|
56
|
-
"
|
|
80
|
+
"urls": [
|
|
81
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/models/laya-finetuned/laya_dual_head.pt",
|
|
82
|
+
f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.pt",
|
|
83
|
+
],
|
|
57
84
|
"min_size": 50000000, # ~267 MB
|
|
58
85
|
},
|
|
59
86
|
},
|
|
@@ -88,6 +115,33 @@ def is_model_installed(model_name: str, candidate_dir: Optional[Path] = None) ->
|
|
|
88
115
|
return True
|
|
89
116
|
|
|
90
117
|
|
|
118
|
+
def is_environment_ready() -> bool:
|
|
119
|
+
"""Checks if both fine-tuned SecureBERT 2.0 and Laya System 1 models are installed and valid."""
|
|
120
|
+
repo_root = Path(__file__).resolve().parents[4]
|
|
121
|
+
candidate_roots = [
|
|
122
|
+
Path.home() / ".trace" / "models",
|
|
123
|
+
Path("models"),
|
|
124
|
+
Path(".trace/models"),
|
|
125
|
+
repo_root / "models",
|
|
126
|
+
repo_root / ".trace/models",
|
|
127
|
+
Path("D:/Startup/TRACE/models"),
|
|
128
|
+
Path("D:/Startup/TRACE/.trace/models"),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
sb_ok = False
|
|
132
|
+
laya_ok = False
|
|
133
|
+
|
|
134
|
+
for r in candidate_roots:
|
|
135
|
+
if not sb_ok and is_model_installed("securebert-finetuned", r / "securebert-finetuned"):
|
|
136
|
+
sb_ok = True
|
|
137
|
+
if not laya_ok and is_model_installed("laya-finetuned", r / "laya-finetuned"):
|
|
138
|
+
laya_ok = True
|
|
139
|
+
if sb_ok and laya_ok:
|
|
140
|
+
return True
|
|
141
|
+
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
|
|
91
145
|
def ensure_model(model_name: str) -> Path:
|
|
92
146
|
"""Ensures model is installed locally; downloads missing files from GitHub LFS/Repo if needed."""
|
|
93
147
|
models_dir = get_default_models_dir()
|
|
@@ -109,42 +163,54 @@ def ensure_model(model_name: str) -> Path:
|
|
|
109
163
|
continue
|
|
110
164
|
|
|
111
165
|
temp_path = target_dir / f"{filename}.tmp"
|
|
112
|
-
|
|
166
|
+
urls = meta.get("urls", [])
|
|
113
167
|
print(f" \033[38;2;255;158;59m›\033[0m Fetching {filename}...")
|
|
114
168
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
169
|
+
success = False
|
|
170
|
+
last_error = None
|
|
171
|
+
for url in urls:
|
|
172
|
+
try:
|
|
173
|
+
req = urllib.request.Request(
|
|
174
|
+
url,
|
|
175
|
+
headers={"User-Agent": "TRACE-Security-Engine/2.1 (github:VK-Amogh/TRACE)"},
|
|
176
|
+
)
|
|
177
|
+
with urllib.request.urlopen(req, timeout=120) as resp:
|
|
178
|
+
total_size = int(resp.headers.get("Content-Length", 0))
|
|
179
|
+
downloaded = 0
|
|
180
|
+
block_size = 1024 * 1024 # 1 MB chunk
|
|
181
|
+
|
|
182
|
+
with open(temp_path, "wb") as f:
|
|
183
|
+
while True:
|
|
184
|
+
chunk = resp.read(block_size)
|
|
185
|
+
if not chunk:
|
|
186
|
+
break
|
|
187
|
+
f.write(chunk)
|
|
188
|
+
downloaded += len(chunk)
|
|
189
|
+
if total_size > 0:
|
|
190
|
+
percent = (downloaded / total_size) * 100
|
|
191
|
+
mb_down = downloaded / (1024 * 1024)
|
|
192
|
+
mb_tot = total_size / (1024 * 1024)
|
|
193
|
+
sys.stdout.write(f"\r [{percent:.1f}%] {mb_down:.1f} MB / {mb_tot:.1f} MB")
|
|
194
|
+
sys.stdout.flush()
|
|
195
|
+
|
|
196
|
+
print()
|
|
197
|
+
if temp_path.exists():
|
|
198
|
+
temp_path.replace(file_path)
|
|
199
|
+
success = True
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
except Exception as e:
|
|
203
|
+
last_error = e
|
|
204
|
+
if temp_path.exists():
|
|
205
|
+
try:
|
|
206
|
+
temp_path.unlink()
|
|
207
|
+
except Exception:
|
|
208
|
+
pass
|
|
209
|
+
continue
|
|
210
|
+
|
|
211
|
+
if not success:
|
|
212
|
+
logger.warning(f"Failed to download {filename}: {last_error}")
|
|
213
|
+
raise RuntimeError(f"Could not download {filename} for {model_name}: {last_error}")
|
|
148
214
|
|
|
149
215
|
print(f"\033[38;2;16;185;129m✓ Successfully provisioned {model_name}\033[0m\n")
|
|
150
216
|
return target_dir
|
|
@@ -154,3 +220,48 @@ def ensure_all_models() -> None:
|
|
|
154
220
|
"""Proactively ensures all fine-tuned models are downloaded and verified."""
|
|
155
221
|
for model_name in MODEL_REGISTRY:
|
|
156
222
|
ensure_model(model_name)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def prompt_and_bootstrap_models(interactive: bool = True) -> bool:
|
|
226
|
+
"""Interactive gate: checks if models are installed, prompts user to agree and downloads them."""
|
|
227
|
+
if is_environment_ready():
|
|
228
|
+
return True
|
|
229
|
+
|
|
230
|
+
CYAN = "\033[38;2;56;189;248m"
|
|
231
|
+
GREEN = "\033[38;2;16;185;129m"
|
|
232
|
+
ORANGE = "\033[38;2;255;158;59m"
|
|
233
|
+
BOLD = "\033[1m"
|
|
234
|
+
DIM = "\033[2m"
|
|
235
|
+
RESET = "\033[0m"
|
|
236
|
+
|
|
237
|
+
print(f"\n {GREEN}┌{'─' * 74}┐{RESET}")
|
|
238
|
+
|
|
239
|
+
print(f" {GREEN}│{RESET} {BOLD}TRACE Neural Intelligence Engine — First-Time Environment Setup{RESET} {GREEN}│{RESET}")
|
|
240
|
+
print(f" {GREEN}├{'─' * 74}┤{RESET}")
|
|
241
|
+
print(f" {GREEN}│{RESET} TRACE requires fine-tuned neural models for autonomous AST attack-path {GREEN}│{RESET}")
|
|
242
|
+
print(f" {GREEN}│{RESET} correlation and vulnerability verification: {GREEN}│{RESET}")
|
|
243
|
+
print(f" {GREEN}│{RESET} • {CYAN}SecureBERT 2.0{RESET} (Fine-Tuned OWASP API Vulnerability Classifier) {GREEN}│{RESET}")
|
|
244
|
+
print(f" {GREEN}│{RESET} • {CYAN}Laya System 1{RESET} (Non-Autoregressive Fast Decision & Priority Router) {GREEN}│{RESET}")
|
|
245
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
246
|
+
print(f" {GREEN}│{RESET} {DIM}Dedicated Location : ~/.trace/models/{RESET} {GREEN}│{RESET}")
|
|
247
|
+
print(f" {GREEN}│{RESET} {DIM}Source : Official TRACE Git LFS Hub (github:VK-Amogh/TRACE){RESET} {GREEN}│{RESET}")
|
|
248
|
+
print(f" {GREEN}└{'─' * 74}┘{RESET}\n")
|
|
249
|
+
|
|
250
|
+
if not interactive or not sys.stdin.isatty():
|
|
251
|
+
print(f" {ORANGE}[!] Non-interactive environment detected. Run 'trace setup-models' to install.{RESET}\n")
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
try:
|
|
255
|
+
ans = input(f" {ORANGE}›{RESET} {BOLD}Download and configure fine-tuned neural models now? (y/n) [y]:{RESET} ").strip().lower()
|
|
256
|
+
if ans in ("", "y", "yes"):
|
|
257
|
+
ensure_all_models()
|
|
258
|
+
print(f" {GREEN}✓ TRACE Neural Intelligence Engine initialized successfully.{RESET}\n")
|
|
259
|
+
return True
|
|
260
|
+
else:
|
|
261
|
+
print(f"\n {ORANGE}[!] TRACE setup cancelled.{RESET}")
|
|
262
|
+
print(f" {DIM}Fine-tuned neural intelligence models are required to perform security audits.{RESET}")
|
|
263
|
+
print(f" {DIM}Run 'trace setup-models' at any time to configure the engine.{RESET}\n")
|
|
264
|
+
return False
|
|
265
|
+
except (KeyboardInterrupt, EOFError):
|
|
266
|
+
print(f"\n {ORANGE}[!] Cancelled.{RESET}\n")
|
|
267
|
+
return False
|
|
Binary file
|
|
@@ -58,9 +58,12 @@ class LayaDecisionEngine:
|
|
|
58
58
|
"""Resolves Laya model directory across project root, package dir, and home."""
|
|
59
59
|
candidates = [
|
|
60
60
|
Path.home() / ".trace/models/laya-finetuned",
|
|
61
|
+
Path("models/laya-finetuned"),
|
|
61
62
|
Path(".trace/models/laya-finetuned"),
|
|
63
|
+
Path(__file__).resolve().parents[4] / "models/laya-finetuned",
|
|
62
64
|
Path(__file__).resolve().parents[4] / ".trace/models/laya-finetuned",
|
|
63
65
|
Path(__file__).resolve().parents[2] / "models/laya-finetuned",
|
|
66
|
+
Path("D:/Startup/TRACE/models/laya-finetuned"),
|
|
64
67
|
Path("D:/Startup/TRACE/.trace/models/laya-finetuned"),
|
|
65
68
|
]
|
|
66
69
|
repo_root = Path(__file__).resolve().parents[4]
|
|
Binary file
|
|
@@ -35,9 +35,12 @@ class SecureBERTClassifier:
|
|
|
35
35
|
):
|
|
36
36
|
candidates = [
|
|
37
37
|
Path.home() / ".trace/models/securebert-finetuned",
|
|
38
|
+
Path("models/securebert-finetuned"),
|
|
38
39
|
Path(".trace/models/securebert-finetuned"),
|
|
40
|
+
Path(__file__).resolve().parents[4] / "models/securebert-finetuned",
|
|
39
41
|
Path(__file__).resolve().parents[4] / ".trace/models/securebert-finetuned",
|
|
40
42
|
Path(__file__).resolve().parents[2] / "models/securebert-finetuned",
|
|
43
|
+
Path("D:/Startup/TRACE/models/securebert-finetuned"),
|
|
41
44
|
Path("D:/Startup/TRACE/.trace/models/securebert-finetuned"),
|
|
42
45
|
]
|
|
43
46
|
default_model = None
|
|
@@ -278,6 +278,10 @@ def run_interactive_story() -> None:
|
|
|
278
278
|
"""Executes the complete interactive, step-by-step security audit story."""
|
|
279
279
|
render_big_banner(animated=True)
|
|
280
280
|
|
|
281
|
+
from trace_engine.intelligence.downloader import prompt_and_bootstrap_models
|
|
282
|
+
if not prompt_and_bootstrap_models(interactive=True):
|
|
283
|
+
return
|
|
284
|
+
|
|
281
285
|
console.print(f" [{WHITE}]Welcome, Security Operator. TRACE stands ready to audit, verify, and heal codebases.[/{WHITE}]")
|
|
282
286
|
console.print(f" [{DIM_WHITE}]Navigate using the options below (type 'exit' or 'q' at any prompt to quit).[/{DIM_WHITE}]\n")
|
|
283
287
|
|
|
@@ -288,26 +292,33 @@ def run_interactive_story() -> None:
|
|
|
288
292
|
console.print(f" [{ORANGE}][1][/{ORANGE}] [{BOLD_WHITE}]Full Security Audit[/{BOLD_WHITE}] [{GREEN}]★ RECOMMENDED[/{GREEN}] [{DIM_WHITE}]─ Multi-language AST, APM graph, & Neural AI[/{DIM_WHITE}]")
|
|
289
293
|
console.print(f" [{ORANGE}][2][/{ORANGE}] [{BOLD_WHITE}]Targeted Vulnerability Probes[/{BOLD_WHITE}] [{DIM_WHITE}]─ Select specific testpacks (BOLA, SQLi, SSRF, BFLA)[/{DIM_WHITE}]")
|
|
290
294
|
console.print(f" [{ORANGE}][3][/{ORANGE}] [{BOLD_WHITE}]Autonomous AST Self-Healing[/{BOLD_WHITE}] [{DIM_WHITE}]─ Surgical refactoring with live rollback verification[/{DIM_WHITE}]")
|
|
291
|
-
console.print(f" [{ORANGE}][4][/{ORANGE}] [{BOLD_WHITE}]
|
|
292
|
-
console.print(f" [{ORANGE}][5][/{ORANGE}] [{BOLD_WHITE}]
|
|
293
|
-
console.print(f" [{ORANGE}][6][/{ORANGE}] [{BOLD_WHITE}]
|
|
295
|
+
console.print(f" [{ORANGE}][4][/{ORANGE}] [{BOLD_WHITE}]Start Claude Code Local MCP Server[/{BOLD_WHITE}] [{GREEN}]★ LOCAL MCP[/{GREEN}] [{DIM_WHITE}]─ Run HTTP/SSE bridge for Claude Code chat[/{DIM_WHITE}]")
|
|
296
|
+
console.print(f" [{ORANGE}][5][/{ORANGE}] [{BOLD_WHITE}]How TRACE Works & Architecture Guide[/{BOLD_WHITE}] [{DIM_WHITE}]─ Interactive architectural diagram & neural models[/{DIM_WHITE}]")
|
|
297
|
+
console.print(f" [{ORANGE}][6][/{ORANGE}] [{BOLD_WHITE}]Environment Diagnostics (Doctor)[/{BOLD_WHITE}] [{DIM_WHITE}]─ Inspect local security tools and dependencies[/{DIM_WHITE}]")
|
|
298
|
+
console.print(f" [{ORANGE}][7][/{ORANGE}] [{BOLD_WHITE}]Exit[/{BOLD_WHITE}]\n")
|
|
294
299
|
|
|
295
300
|
choice = ask_input(
|
|
296
301
|
f" [{ORANGE}]›[/{ORANGE}] [{BOLD_WHITE}]Choose option[/{BOLD_WHITE}]",
|
|
297
|
-
choices=["1", "2", "3", "4", "5", "6"],
|
|
302
|
+
choices=["1", "2", "3", "4", "5", "6", "7"],
|
|
298
303
|
default="1",
|
|
299
304
|
show_default=True,
|
|
300
305
|
)
|
|
301
306
|
|
|
302
|
-
if choice == "
|
|
307
|
+
if choice == "7":
|
|
303
308
|
console.print(f"\n [{GREEN}]✓ TRACE session closed. Happy hacking![/{GREEN}]\n")
|
|
304
309
|
return
|
|
305
310
|
|
|
306
311
|
if choice == "4":
|
|
307
|
-
|
|
312
|
+
from trace_engine.mcp.server import TraceMCPServer
|
|
313
|
+
mcp_obj = TraceMCPServer()
|
|
314
|
+
mcp_obj.run_sse_server(host="127.0.0.1", port=8765)
|
|
308
315
|
return
|
|
309
316
|
|
|
310
317
|
if choice == "5":
|
|
318
|
+
run_architecture_guide()
|
|
319
|
+
return
|
|
320
|
+
|
|
321
|
+
if choice == "6":
|
|
311
322
|
display_story_header(2, 2, "Environment Diagnostics", "Auditing local dependencies and runtime requirements")
|
|
312
323
|
from trace_engine.doctor import run_doctor
|
|
313
324
|
report = run_doctor(Path("."))
|
|
Binary file
|
|
@@ -396,3 +396,174 @@ class TraceMCPServer:
|
|
|
396
396
|
}
|
|
397
397
|
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
398
398
|
sys.stdout.flush()
|
|
399
|
+
|
|
400
|
+
def run_sse_server(self, host: str = "127.0.0.1", port: int = 8765):
|
|
401
|
+
"""Run an MCP HTTP/SSE transport server for Claude Code and local AI coding agents."""
|
|
402
|
+
import uuid
|
|
403
|
+
import queue
|
|
404
|
+
import urllib.parse
|
|
405
|
+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
|
|
406
|
+
|
|
407
|
+
sessions: Dict[str, queue.Queue] = {}
|
|
408
|
+
server_instance = self
|
|
409
|
+
|
|
410
|
+
class MCPHandler(BaseHTTPRequestHandler):
|
|
411
|
+
def log_message(self, format, *args):
|
|
412
|
+
pass # Suppress request clutter in terminal
|
|
413
|
+
|
|
414
|
+
def do_OPTIONS(self):
|
|
415
|
+
self.send_response(200)
|
|
416
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
417
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
418
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
|
419
|
+
self.end_headers()
|
|
420
|
+
|
|
421
|
+
def do_GET(self):
|
|
422
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
423
|
+
if parsed.path == "/sse":
|
|
424
|
+
session_id = uuid.uuid4().hex
|
|
425
|
+
msg_queue: queue.Queue = queue.Queue()
|
|
426
|
+
sessions[session_id] = msg_queue
|
|
427
|
+
|
|
428
|
+
self.send_response(200)
|
|
429
|
+
self.send_header("Content-Type", "text/event-stream")
|
|
430
|
+
self.send_header("Cache-Control", "no-cache")
|
|
431
|
+
self.send_header("Connection", "keep-alive")
|
|
432
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
433
|
+
self.end_headers()
|
|
434
|
+
|
|
435
|
+
# Emit endpoint event as defined in MCP SSE transport specification
|
|
436
|
+
endpoint_msg = f"event: endpoint\r\ndata: /message?sessionId={session_id}\r\n\r\n"
|
|
437
|
+
self.wfile.write(endpoint_msg.encode("utf-8"))
|
|
438
|
+
self.wfile.flush()
|
|
439
|
+
|
|
440
|
+
try:
|
|
441
|
+
while server_instance.running:
|
|
442
|
+
try:
|
|
443
|
+
msg = msg_queue.get(timeout=1.0)
|
|
444
|
+
if msg is None:
|
|
445
|
+
break
|
|
446
|
+
event_str = f"event: message\r\ndata: {json.dumps(msg)}\r\n\r\n"
|
|
447
|
+
self.wfile.write(event_str.encode("utf-8"))
|
|
448
|
+
self.wfile.flush()
|
|
449
|
+
except queue.Empty:
|
|
450
|
+
self.wfile.write(b": keepalive\r\n\r\n")
|
|
451
|
+
self.wfile.flush()
|
|
452
|
+
except (BrokenPipeError, ConnectionResetError):
|
|
453
|
+
pass
|
|
454
|
+
finally:
|
|
455
|
+
sessions.pop(session_id, None)
|
|
456
|
+
|
|
457
|
+
elif parsed.path in ("/health", "/status"):
|
|
458
|
+
self.send_response(200)
|
|
459
|
+
self.send_header("Content-Type", "application/json")
|
|
460
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
461
|
+
self.end_headers()
|
|
462
|
+
status_info = {
|
|
463
|
+
"status": "online",
|
|
464
|
+
"server": "TRACE MCP Server v2.1",
|
|
465
|
+
"transport": "sse",
|
|
466
|
+
"sse_url": f"http://{host}:{port}/sse",
|
|
467
|
+
"active_sessions": len(sessions),
|
|
468
|
+
"tools": [t["name"] for t in TRACE_TOOLS],
|
|
469
|
+
}
|
|
470
|
+
self.wfile.write(json.dumps(status_info, indent=2).encode("utf-8"))
|
|
471
|
+
|
|
472
|
+
else:
|
|
473
|
+
self.send_response(200)
|
|
474
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
475
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
476
|
+
self.end_headers()
|
|
477
|
+
html = f"""<!DOCTYPE html>
|
|
478
|
+
<html>
|
|
479
|
+
<head>
|
|
480
|
+
<meta charset="utf-8">
|
|
481
|
+
<title>TRACE Local MCP Server</title>
|
|
482
|
+
<style>
|
|
483
|
+
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; background: #0b0f17; color: #f8fafc; padding: 40px; margin: 0; }}
|
|
484
|
+
.container {{ max-width: 680px; margin: 0 auto; background: #131b2e; border: 1px solid #10b981; border-radius: 12px; padding: 32px; box-shadow: 0 10px 25px rgba(0,0,0,0.5); }}
|
|
485
|
+
h1 {{ color: #10b981; margin-top: 0; display: flex; align-items: center; gap: 10px; font-size: 24px; }}
|
|
486
|
+
.badge {{ background: #10b981; color: #0b0f17; padding: 3px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; }}
|
|
487
|
+
p {{ color: #94a3b8; font-size: 14px; line-height: 1.6; }}
|
|
488
|
+
.cmd-box {{ background: #070a12; border: 1px solid #1e293b; border-radius: 6px; padding: 12px 16px; margin: 12px 0; font-family: monospace; color: #38bdf8; font-size: 13px; word-break: break-all; }}
|
|
489
|
+
.section-title {{ color: #ffffff; font-size: 14px; font-weight: bold; margin-top: 20px; }}
|
|
490
|
+
</style>
|
|
491
|
+
</head>
|
|
492
|
+
<body>
|
|
493
|
+
<div class="container">
|
|
494
|
+
<h1>TRACE MCP Server <span class="badge">ONLINE</span></h1>
|
|
495
|
+
<p>Local Model Context Protocol (MCP) server listening for Claude Code and AI coding agents.</p>
|
|
496
|
+
|
|
497
|
+
<div class="section-title">Local MCP Endpoint Link:</div>
|
|
498
|
+
<div class="cmd-box">http://{host}:{port}/sse</div>
|
|
499
|
+
|
|
500
|
+
<div class="section-title">Connect via Claude Code CLI:</div>
|
|
501
|
+
<div class="cmd-box">claude mcp add --transport sse trace http://{host}:{port}/sse</div>
|
|
502
|
+
|
|
503
|
+
<div class="section-title">Connect in Claude Chat:</div>
|
|
504
|
+
<p>Paste the local MCP link into your Claude chat to grant autonomous AST scanning and verification powers:</p>
|
|
505
|
+
<div class="cmd-box">http://{host}:{port}/sse</div>
|
|
506
|
+
</div>
|
|
507
|
+
</body>
|
|
508
|
+
</html>"""
|
|
509
|
+
self.wfile.write(html.encode("utf-8"))
|
|
510
|
+
|
|
511
|
+
def do_POST(self):
|
|
512
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
513
|
+
if parsed.path.startswith("/message"):
|
|
514
|
+
query = urllib.parse.parse_qs(parsed.query)
|
|
515
|
+
session_id = query.get("sessionId", [None])[0]
|
|
516
|
+
|
|
517
|
+
content_len = int(self.headers.get("Content-Length", 0))
|
|
518
|
+
body = self.rfile.read(content_len).decode("utf-8")
|
|
519
|
+
try:
|
|
520
|
+
req = json.loads(body)
|
|
521
|
+
resp = server_instance.handle_request(req)
|
|
522
|
+
except Exception as e:
|
|
523
|
+
resp = {
|
|
524
|
+
"jsonrpc": "2.0",
|
|
525
|
+
"id": None,
|
|
526
|
+
"error": {"code": -32603, "message": str(e)},
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if session_id and session_id in sessions and resp:
|
|
530
|
+
sessions[session_id].put(resp)
|
|
531
|
+
|
|
532
|
+
self.send_response(200)
|
|
533
|
+
self.send_header("Content-Type", "application/json")
|
|
534
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
535
|
+
self.end_headers()
|
|
536
|
+
self.wfile.write(json.dumps(resp or {"jsonrpc": "2.0", "result": None}).encode("utf-8"))
|
|
537
|
+
else:
|
|
538
|
+
self.send_response(404)
|
|
539
|
+
self.end_headers()
|
|
540
|
+
|
|
541
|
+
GREEN = "\033[38;2;16;185;129m"
|
|
542
|
+
CYAN = "\033[38;2;56;189;248m"
|
|
543
|
+
ORANGE = "\033[38;2;255;158;59m"
|
|
544
|
+
BOLD = "\033[1m"
|
|
545
|
+
DIM = "\033[2m"
|
|
546
|
+
RESET = "\033[0m"
|
|
547
|
+
|
|
548
|
+
print(f"\n {GREEN}┌{'─' * 74}┐{RESET}")
|
|
549
|
+
print(f" {GREEN}│{RESET} {BOLD}TRACE Local MCP Server (Model Context Protocol){RESET} {GREEN}│{RESET}")
|
|
550
|
+
print(f" {GREEN}├{'─' * 74}┤{RESET}")
|
|
551
|
+
print(f" {GREEN}│{RESET} {BOLD}Local MCP Link :{RESET} {CYAN}http://{host}:{port}/sse{RESET}{' ' * max(0, 39 - len(f'http://{host}:{port}/sse'))}{GREEN}│{RESET}")
|
|
552
|
+
print(f" {GREEN}│{RESET} {BOLD}Web Dashboard :{RESET} {CYAN}http://{host}:{port}/{RESET}{' ' * max(0, 42 - len(f'http://{host}:{port}/'))}{GREEN}│{RESET}")
|
|
553
|
+
print(f" {GREEN}│{RESET} {BOLD}Status :{RESET} {GREEN}ONLINE{RESET} (Listening for Claude Code & AI Coding Agents) {GREEN}│{RESET}")
|
|
554
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
555
|
+
print(f" {GREEN}│{RESET} {BOLD}Connect in Claude Code CLI:{RESET} {GREEN}│{RESET}")
|
|
556
|
+
print(f" {GREEN}│{RESET} {ORANGE}›{RESET} {BOLD}claude mcp add --transport sse trace http://{host}:{port}/sse{RESET} {GREEN}│{RESET}")
|
|
557
|
+
print(f" {GREEN}│{RESET} {GREEN}│{RESET}")
|
|
558
|
+
print(f" {GREEN}│{RESET} {BOLD}Or paste this link directly in your Claude Code chat:{RESET} {GREEN}│{RESET}")
|
|
559
|
+
print(f" {GREEN}│{RESET} {CYAN}http://{host}:{port}/sse{RESET}{' ' * max(0, 49 - len(f'http://{host}:{port}/sse'))}{GREEN}│{RESET}")
|
|
560
|
+
print(f" {GREEN}└{'─' * 74}┘{RESET}\n")
|
|
561
|
+
print(f" {DIM}Press Ctrl+C to terminate the local MCP server.{RESET}\n")
|
|
562
|
+
|
|
563
|
+
httpd = ThreadingHTTPServer((host, port), MCPHandler)
|
|
564
|
+
try:
|
|
565
|
+
httpd.serve_forever()
|
|
566
|
+
except KeyboardInterrupt:
|
|
567
|
+
print(f"\n {ORANGE}[TRACE] MCP Server stopped.{RESET}\n")
|
|
568
|
+
finally:
|
|
569
|
+
httpd.server_close()
|