trace-sec 2.1.2 → 2.1.4

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 CHANGED
@@ -51,8 +51,8 @@ if (args[0] === 'install-skill' || args[0] === 'setup-agent' || args[0] === 'ins
51
51
  trace: {
52
52
  command: isWin ? 'cmd.exe' : 'npx',
53
53
  args: isWin
54
- ? ['/c', 'npx', '-y', 'github:VK-Amogh/TRACE', 'mcp']
55
- : ['-y', 'github:VK-Amogh/TRACE', 'mcp'],
54
+ ? ['/c', 'npx', '-y', 'trace-sec', 'mcp']
55
+ : ['-y', 'trace-sec', 'mcp'],
56
56
  env: {
57
57
  PYTHONUNBUFFERED: '1',
58
58
  },
@@ -73,7 +73,7 @@ if (args[0] === 'install-skill' || args[0] === 'setup-agent' || args[0] === 'ins
73
73
  console.log(' • \x1b[32mAntigravity & Agentic IDEs:\x1b[0m .agents/plugins/trace-security active');
74
74
  console.log(' • \x1b[32mVerification Tools:\x1b[0m trace_scan, trace_findings, trace_verify, trace_harness_task');
75
75
  console.log('\n\x1b[90mTo register in Claude Code CLI directly:\x1b[0m');
76
- console.log(' \x1b[37mclaude mcp add trace -- npx github:VK-Amogh/TRACE mcp\x1b[0m\n');
76
+ console.log(' \x1b[37mclaude mcp add trace -- npx trace-sec mcp\x1b[0m\n');
77
77
  process.exit(0);
78
78
  } else {
79
79
  console.error(`[TRACE] Bundle directory not found at ${bundleDir}`);
@@ -83,10 +83,14 @@ if (args[0] === 'install-skill' || args[0] === 'setup-agent' || args[0] === 'ins
83
83
 
84
84
  // Find virtual environment python or system python
85
85
  const isWin = process.platform === 'win32';
86
+ const userHome = process.env.USERPROFILE || process.env.HOME || '';
86
87
  const candidatePythons = [
87
88
  process.env.TRACE_PYTHON,
88
- join(process.cwd(), '.venv', isWin ? 'Scripts/python.exe' : 'bin/python'),
89
- join(projectRoot, '.venv', isWin ? 'Scripts/python.exe' : 'bin/python'),
89
+ join(process.cwd(), '.venv', isWin ? 'Scripts' : 'bin', isWin ? 'python.exe' : 'python'),
90
+ join(projectRoot, '.venv', isWin ? 'Scripts' : 'bin', isWin ? 'python.exe' : 'python'),
91
+ isWin ? 'D:\\Startup\\TRACE\\.venv\\Scripts\\python.exe' : null,
92
+ join(userHome, '.trace', 'venv', isWin ? 'Scripts' : 'bin', isWin ? 'python.exe' : 'python'),
93
+ join(userHome, '.trace', 'models', 'venv', isWin ? 'Scripts' : 'bin', isWin ? 'python.exe' : 'python'),
90
94
  isWin ? 'python' : 'python3',
91
95
  ];
92
96
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trace-sec",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "trace-sec"
7
- version = "1.0.0"
7
+ version = "2.1.4"
8
8
  description = "Threat Reconnaissance & Attack-path Correlation Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -1178,6 +1178,82 @@ def bench_intelligence():
1178
1178
  run_intelligence_benchmark()
1179
1179
 
1180
1180
 
1181
+ @app.command(name="setup-models")
1182
+ @app.command(name="download-models")
1183
+ def setup_models():
1184
+ """Download and provision fine-tuned SecureBERT 2.0 and Laya System 1 weights into ~/.trace/models."""
1185
+ from trace_engine.intelligence.downloader import ensure_all_models
1186
+ console.print("\n[bold green]TRACE Neural Model Provisioner[/bold green]")
1187
+ console.print("[dim]Fetching fine-tuned weights from TRACE Git LFS Hub into ~/.trace/models...[/dim]\n")
1188
+ ensure_all_models()
1189
+ console.print("[bold green]✓ All fine-tuned models are installed, verified, and ready.[/bold green]\n")
1190
+
1191
+
1192
+ @app.command(name="install-skill")
1193
+ @app.command(name="setup-agent")
1194
+ def install_skill(
1195
+ target_dir: Path = typer.Option(Path("."), "--target", "-t", help="Target repository directory to install skills and MCP"),
1196
+ ):
1197
+ """Install TRACE autonomous agent skill & MCP configurations for Claude Code, Antigravity, and Cursor."""
1198
+ import shutil
1199
+ project_root = Path(__file__).resolve().parents[2]
1200
+ bundle_dir = project_root / "src" / "trace_engine" / "plugin" / "bundle"
1201
+ target = target_dir.resolve()
1202
+
1203
+ console.print("\n[bold green]Installing TRACE Autonomous Security Agent Skill & MCP Tools...[/bold green]")
1204
+
1205
+ if bundle_dir.exists():
1206
+ # 1. Antigravity plugin (.agents/plugins/trace-security)
1207
+ agent_plugin_dir = target / ".agents" / "plugins" / "trace-security"
1208
+ agent_plugin_dir.parent.mkdir(parents=True, exist_ok=True)
1209
+ shutil.copytree(bundle_dir, agent_plugin_dir, dirs_exist_ok=True)
1210
+ console.print(f" [bold green]✓[/bold green] Antigravity Plugin : [white]{agent_plugin_dir}[/white]")
1211
+
1212
+ # 2. Agent Skill & Claude Code Skill
1213
+ skill_src = bundle_dir / "skills" / "trace-security-harness"
1214
+ if skill_src.exists():
1215
+ agent_skill_dir = target / ".agents" / "skills" / "trace-security-harness"
1216
+ agent_skill_dir.parent.mkdir(parents=True, exist_ok=True)
1217
+ shutil.copytree(skill_src, agent_skill_dir, dirs_exist_ok=True)
1218
+ console.print(f" [bold green]✓[/bold green] Agent Skill : [white]{agent_skill_dir}[/white]")
1219
+
1220
+ claude_skill_dir = target / ".claude" / "skills" / "trace-security"
1221
+ claude_skill_dir.parent.mkdir(parents=True, exist_ok=True)
1222
+ shutil.copytree(skill_src, claude_skill_dir, dirs_exist_ok=True)
1223
+ console.print(f" [bold green]✓[/bold green] Claude Code Skill : [white]{claude_skill_dir}[/white]")
1224
+
1225
+ # 3. MCP Configuration
1226
+ import json
1227
+ is_win = sys.platform == "win32"
1228
+ mcp_config = {
1229
+ "mcpServers": {
1230
+ "trace": {
1231
+ "command": "cmd.exe" if is_win else "npx",
1232
+ "args": ["/c", "npx", "-y", "trace-sec", "mcp"] if is_win else ["-y", "trace-sec", "mcp"],
1233
+ "env": {
1234
+ "PYTHONUNBUFFERED": "1"
1235
+ }
1236
+ }
1237
+ }
1238
+ }
1239
+ mcp_json = target / ".mcp.json"
1240
+ mcp_json.write_text(json.dumps(mcp_config, indent=2), encoding="utf-8")
1241
+ console.print(f" [bold green]✓[/bold green] Claude Code MCP : [white]{mcp_json}[/white]")
1242
+
1243
+ cursor_dir = target / ".cursor"
1244
+ cursor_dir.mkdir(parents=True, exist_ok=True)
1245
+ (cursor_dir / "mcp.json").write_text(json.dumps(mcp_config, indent=2), encoding="utf-8")
1246
+ console.print(f" [bold green]✓[/bold green] Cursor IDE MCP : [white]{cursor_dir / 'mcp.json'}[/white]")
1247
+
1248
+ console.print("\n[bold]Capabilities Enabled for AI Coding Agents:[/bold]")
1249
+ console.print(" • [bold green]Claude Code Integration:[/bold green] Skills + MCP server configured")
1250
+ console.print(" • [bold green]Antigravity & Agentic IDEs:[/bold green] .agents/plugins/trace-security active")
1251
+ console.print(" • [bold green]Verification Tools:[/bold green] trace_scan, trace_findings, trace_verify, trace_harness_task\n")
1252
+ else:
1253
+ console.print(f"[bold red]Error:[/bold red] Plugin bundle not found at {bundle_dir}")
1254
+ raise typer.Exit(code=1)
1255
+
1256
+
1181
1257
  if __name__ == "__main__":
1182
1258
  app()
1183
1259
 
@@ -0,0 +1,156 @@
1
+ """Automated model downloader and provisioner for TRACE fine-tuned weights."""
2
+
3
+ import os
4
+ import sys
5
+ import logging
6
+ import urllib.request
7
+ from pathlib import Path
8
+ from typing import Optional, Dict, List, Any
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ REPO_OWNER = "VK-Amogh"
13
+ REPO_NAME = "TRACE"
14
+ BRANCH = "main"
15
+
16
+ MODEL_REGISTRY: Dict[str, Dict[str, Any]] = {
17
+ "securebert-finetuned": {
18
+ "files": {
19
+ "config.json": {
20
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/config.json",
21
+ "min_size": 500,
22
+ },
23
+ "tokenizer.json": {
24
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer.json",
25
+ "min_size": 1000000,
26
+ },
27
+ "tokenizer_config.json": {
28
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/tokenizer_config.json",
29
+ "min_size": 200,
30
+ },
31
+ "model.safetensors": {
32
+ "url": f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/securebert-finetuned/model.safetensors",
33
+ "min_size": 100000000, # ~498 MB
34
+ },
35
+ },
36
+ },
37
+ "laya-finetuned": {
38
+ "files": {
39
+ "laya_metadata.json": {
40
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_metadata.json",
41
+ "min_size": 300,
42
+ },
43
+ "tokenizer.json": {
44
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer.json",
45
+ "min_size": 500000,
46
+ },
47
+ "tokenizer_config.json": {
48
+ "url": f"https://raw.githubusercontent.com/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/tokenizer_config.json",
49
+ "min_size": 200,
50
+ },
51
+ "laya_dual_head.onnx": {
52
+ "url": f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.onnx",
53
+ "min_size": 50000000, # ~267 MB
54
+ },
55
+ "laya_dual_head.pt": {
56
+ "url": f"https://media.githubusercontent.com/media/{REPO_OWNER}/{REPO_NAME}/{BRANCH}/.trace/models/laya-finetuned/laya_dual_head.pt",
57
+ "min_size": 50000000, # ~267 MB
58
+ },
59
+ },
60
+ },
61
+ }
62
+
63
+
64
+ def get_default_models_dir() -> Path:
65
+ """Returns the default persistent directory for TRACE models (~/.trace/models)."""
66
+ target = Path.home() / ".trace" / "models"
67
+ target.mkdir(parents=True, exist_ok=True)
68
+ return target
69
+
70
+
71
+ def is_model_installed(model_name: str, candidate_dir: Optional[Path] = None) -> bool:
72
+ """Checks if all required model files are present and valid (not empty or git lfs pointer stubs)."""
73
+ if model_name not in MODEL_REGISTRY:
74
+ return False
75
+
76
+ spec = MODEL_REGISTRY[model_name]
77
+ target_dir = candidate_dir or (get_default_models_dir() / model_name)
78
+ if not target_dir.exists():
79
+ return False
80
+
81
+ for filename, meta in spec["files"].items():
82
+ file_path = target_dir / filename
83
+ if not file_path.exists():
84
+ return False
85
+ if file_path.stat().st_size < meta["min_size"]:
86
+ return False
87
+
88
+ return True
89
+
90
+
91
+ def ensure_model(model_name: str) -> Path:
92
+ """Ensures model is installed locally; downloads missing files from GitHub LFS/Repo if needed."""
93
+ models_dir = get_default_models_dir()
94
+ target_dir = models_dir / model_name
95
+ target_dir.mkdir(parents=True, exist_ok=True)
96
+
97
+ if is_model_installed(model_name, target_dir):
98
+ return target_dir
99
+
100
+ if model_name not in MODEL_REGISTRY:
101
+ raise ValueError(f"Unknown model: {model_name}")
102
+
103
+ spec = MODEL_REGISTRY[model_name]
104
+ print(f"\n\033[38;2;16;185;129m[TRACE Model Hub]\033[0m Downloading fine-tuned {model_name} neural weights...")
105
+
106
+ for filename, meta in spec["files"].items():
107
+ file_path = target_dir / filename
108
+ if file_path.exists() and file_path.stat().st_size >= meta["min_size"]:
109
+ continue
110
+
111
+ temp_path = target_dir / f"{filename}.tmp"
112
+ url = meta["url"]
113
+ print(f" \033[38;2;255;158;59m›\033[0m Fetching {filename}...")
114
+
115
+ try:
116
+ req = urllib.request.Request(
117
+ url,
118
+ headers={"User-Agent": "TRACE-Security-Engine/2.1 (github:VK-Amogh/TRACE)"},
119
+ )
120
+ with urllib.request.urlopen(req, timeout=120) as resp:
121
+ total_size = int(resp.headers.get("Content-Length", 0))
122
+ downloaded = 0
123
+ block_size = 1024 * 1024 # 1 MB chunk
124
+
125
+ with open(temp_path, "wb") as f:
126
+ while True:
127
+ chunk = resp.read(block_size)
128
+ if not chunk:
129
+ break
130
+ f.write(chunk)
131
+ downloaded += len(chunk)
132
+ if total_size > 0:
133
+ percent = (downloaded / total_size) * 100
134
+ mb_down = downloaded / (1024 * 1024)
135
+ mb_tot = total_size / (1024 * 1024)
136
+ sys.stdout.write(f"\r [{percent:.1f}%] {mb_down:.1f} MB / {mb_tot:.1f} MB")
137
+ sys.stdout.flush()
138
+
139
+ print()
140
+ if temp_path.exists():
141
+ temp_path.replace(file_path)
142
+
143
+ except Exception as e:
144
+ if temp_path.exists():
145
+ temp_path.unlink()
146
+ logger.warning(f"Failed to download {filename} from {url}: {e}")
147
+ raise RuntimeError(f"Could not download {filename} for {model_name}: {e}")
148
+
149
+ print(f"\033[38;2;16;185;129m✓ Successfully provisioned {model_name}\033[0m\n")
150
+ return target_dir
151
+
152
+
153
+ def ensure_all_models() -> None:
154
+ """Proactively ensures all fine-tuned models are downloaded and verified."""
155
+ for model_name in MODEL_REGISTRY:
156
+ ensure_model(model_name)
@@ -57,10 +57,11 @@ class LayaDecisionEngine:
57
57
  def _resolve_laya_dir(self) -> Optional[Path]:
58
58
  """Resolves Laya model directory across project root, package dir, and home."""
59
59
  candidates = [
60
+ Path.home() / ".trace/models/laya-finetuned",
60
61
  Path(".trace/models/laya-finetuned"),
61
62
  Path(__file__).resolve().parents[4] / ".trace/models/laya-finetuned",
62
63
  Path(__file__).resolve().parents[2] / "models/laya-finetuned",
63
- Path.home() / ".trace/models/laya-finetuned",
64
+ Path("D:/Startup/TRACE/.trace/models/laya-finetuned"),
64
65
  ]
65
66
  repo_root = Path(__file__).resolve().parents[4]
66
67
  for p in candidates:
@@ -76,6 +77,13 @@ class LayaDecisionEngine:
76
77
  return p.resolve()
77
78
  if onnx_file.exists() and onnx_file.stat().st_size >= 10000:
78
79
  return p.resolve()
80
+
81
+ try:
82
+ from trace_engine.intelligence.downloader import ensure_model
83
+ return ensure_model("laya-finetuned")
84
+ except Exception as e:
85
+ logger.debug(f"Automatic Laya download deferred: {e}")
86
+
79
87
  return None
80
88
 
81
89
  def _load_onnx_model(self) -> None:
@@ -125,8 +133,11 @@ class LayaDecisionEngine:
125
133
  def _load_agent(self) -> None:
126
134
  """Attempt to load Laya in-process model."""
127
135
  try:
128
- import laya
129
- self._agent = laya.load(self.model_id)
136
+ import warnings
137
+ with warnings.catch_warnings():
138
+ warnings.filterwarnings("ignore", category=RuntimeWarning)
139
+ import laya
140
+ self._agent = laya.load(self.model_id)
130
141
  logger.info(f"Laya agent loaded: {self.model_id}")
131
142
  except Exception as e:
132
143
  logger.debug(f"Laya in-process loading: {e}. Using calibrated fallback.")
@@ -34,12 +34,14 @@ class SecureBERTClassifier:
34
34
  cache_path: Optional[Path] = None,
35
35
  ):
36
36
  candidates = [
37
+ Path.home() / ".trace/models/securebert-finetuned",
37
38
  Path(".trace/models/securebert-finetuned"),
38
39
  Path(__file__).resolve().parents[4] / ".trace/models/securebert-finetuned",
39
40
  Path(__file__).resolve().parents[2] / "models/securebert-finetuned",
40
- Path.home() / ".trace/models/securebert-finetuned",
41
+ Path("D:/Startup/TRACE/.trace/models/securebert-finetuned"),
41
42
  ]
42
- default_model = "ehsanaghaei/SecureBERT"
43
+ default_model = None
44
+ repo_root = Path(__file__).resolve().parents[4]
43
45
  for cand in candidates:
44
46
  weight_file = cand / "model.safetensors"
45
47
  if weight_file.exists():
@@ -53,6 +55,15 @@ class SecureBERTClassifier:
53
55
  default_model = str(cand.resolve())
54
56
  break
55
57
 
58
+ if not default_model:
59
+ try:
60
+ from trace_engine.intelligence.downloader import ensure_model
61
+ model_dir = ensure_model("securebert-finetuned")
62
+ default_model = str(model_dir.resolve())
63
+ except Exception as e:
64
+ logger.debug(f"Automatic model download deferred: {e}")
65
+ default_model = "ehsanaghaei/SecureBERT"
66
+
56
67
  self.model_name = model_name or default_model
57
68
  cache_file = cache_path or Path(".trace/cache/securebert_cache.json")
58
69
  self.cache = SecureBERTCache(cache_file)
@@ -68,6 +79,8 @@ class SecureBERTClassifier:
68
79
  self._initialized = True
69
80
  try:
70
81
  import torch
82
+ import transformers
83
+ transformers.logging.set_verbosity_error()
71
84
  from transformers import AutoTokenizer, AutoModelForSequenceClassification
72
85
 
73
86
  self._device = "cuda" if torch.cuda.is_available() else "cpu"