trace-sec 2.1.10 → 2.1.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "trace-sec",
3
- "version": "2.1.10",
3
+ "version": "2.1.11",
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 = "2.1.10"
7
+ version = "2.1.11"
8
8
  description = "Threat Reconnaissance & Attack-path Correlation Engine"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -289,22 +289,69 @@ def test(
289
289
  active_tokens={"user-a": "user-a", "user-b": "user-b", "admin": "admin"},
290
290
  )
291
291
 
292
- console.print(f"\n[bold green]Executing Controlled Runtime Tests[/bold green] against [white]{target}[/white]...")
292
+ # Check target connectivity
293
+ target_online = False
294
+ try:
295
+ import httpx
296
+ with httpx.Client(timeout=1.0) as chk:
297
+ chk.get(target)
298
+ target_online = True
299
+ except Exception:
300
+ target_online = False
301
+
302
+ if target_online:
303
+ console.print(f"\n[bold green]Executing Controlled Runtime Tests[/bold green] against [white]{target}[/white]...")
304
+ else:
305
+ console.print(f"\n[yellow]Notice:[/yellow] Target runtime [white]{target}[/white] is offline or unreachable.")
306
+ console.print(f" [dim]• Active HTTP exploit probes require a running service (e.g. localhost or lab testbed).[/dim]")
307
+ console.print(f" [dim]• Auditing codebase via Neuro-Symbolic Attack-Path Model (Safe Offline Mode)...[/dim]\n")
308
+
293
309
  executed = 0
294
310
  confirmed = 0
311
+ correlator = EvidenceCorrelator()
312
+ findings: List[Finding] = []
295
313
 
296
314
  for hyp in hypotheses:
297
- pack = default_registry.get(hyp.recommended_test_pack)
298
- if pack:
299
- executed += 1
300
- res = pack.execute(hyp, client, context)
301
- if res.confirmed:
302
- confirmed += 1
303
- console.print(f" [bold red]CONFIRMED:[/bold red] [{hyp.id}] {res.summary}")
304
- else:
305
- console.print(f" [dim]Passed / Inconclusive:[/dim] [{hyp.id}] {res.summary}")
306
-
307
- console.print(f"\n[dim]Executed {executed} test packs. {confirmed} attack paths confirmed.[/dim]\n")
315
+ test_res = None
316
+ if target_online:
317
+ pack = default_registry.get(hyp.recommended_test_pack)
318
+ if pack:
319
+ executed += 1
320
+ try:
321
+ test_res = pack.execute(hyp, client, context)
322
+ if test_res and test_res.confirmed:
323
+ confirmed += 1
324
+ console.print(f" [bold red]CONFIRMED:[/bold red] [{hyp.id}] {test_res.summary}")
325
+ else:
326
+ console.print(f" [dim]Passed / Inconclusive:[/dim] [{hyp.id}] {test_res.summary}")
327
+ except Exception:
328
+ test_res = None
329
+
330
+ finding = correlator.correlate(hyp, test_res, graph_model)
331
+ if finding:
332
+ findings.append(finding)
333
+
334
+ # Persist findings & APM
335
+ trace_dir = init_trace_dir(project_dir)
336
+ save_apm_sqlite(graph_model, trace_dir / "graph.db")
337
+ store = FindingStore(trace_dir)
338
+ store.save_findings(findings)
339
+
340
+ try:
341
+ root_trace = Path(".trace")
342
+ root_trace.mkdir(parents=True, exist_ok=True)
343
+ (root_trace / "last_scan_repo.txt").write_text(str(project_dir), encoding="utf-8")
344
+ if project_dir != Path(".").resolve():
345
+ root_store = FindingStore(root_trace)
346
+ root_store.save_findings(findings)
347
+ except Exception:
348
+ pass
349
+
350
+ if target_online:
351
+ console.print(f"\n[dim]Executed {executed} test packs. {confirmed} attack paths confirmed at runtime.[/dim]\n")
352
+
353
+ print_findings_table(findings)
354
+ console.print(f"[dim]Total findings identified: {len(findings)} (Saved to {trace_dir / 'findings.json'})[/dim]\n")
308
355
 
309
356
 
310
357
  @app.command()
@@ -438,13 +438,14 @@ def run_interactive_story() -> None:
438
438
  display_story_header(4, 4, "Attack Vector Selection", "Select specific vulnerability testpacks to deploy")
439
439
 
440
440
  pack_mapping = {
441
- "1": ("BOLA / IDOR", "bola", "Multi-tenant object access & IDOR cross-account authorization leak"),
442
- "2": ("SQL & Command Injection", "injection", "Statistical timing oracle (p < 0.01) & error extraction"),
443
- "3": ("SSRF ScopeGuard", "ssrf", "Egress network validation against RFC 1918 loopback & metadata IPs"),
444
- "4": ("BFLA / RBAC Elevation", "bfla", "Broken function-level authorization & administrative privilege check"),
445
- "5": ("Path Traversal", "path_traversal", "Canonical directory escape & arbitrary file disclosure probes"),
446
- "6": ("CORS Misconfiguration", "cors", "Wildcard Access-Control-Allow-Origin & credential reflection"),
447
- "7": ("Mass Assignment", "mass_assignment", "Unbounded payload parameter binding to persistent entities"),
441
+ "1": ("Authentication & Auth Gates", "authentication", "Missing token validation & unauthenticated state-changing routes"),
442
+ "2": ("BOLA / IDOR", "bola", "Multi-tenant object access & IDOR cross-account authorization leak"),
443
+ "3": ("SQL & Command Injection", "injection", "Statistical timing oracle (p < 0.01) & error extraction"),
444
+ "4": ("SSRF ScopeGuard", "ssrf", "Egress network validation against RFC 1918 loopback & metadata IPs"),
445
+ "5": ("BFLA / RBAC Elevation", "bfla", "Broken function-level authorization & administrative privilege check"),
446
+ "6": ("Path Traversal", "path_traversal", "Canonical directory escape & arbitrary file disclosure probes"),
447
+ "7": ("CORS Misconfiguration", "cors", "Wildcard Access-Control-Allow-Origin & credential reflection"),
448
+ "8": ("Mass Assignment", "mass_assignment", "Unbounded payload parameter binding to persistent entities"),
448
449
  }
449
450
 
450
451
  console.print(f" [{BOLD_WHITE}]Available Testpacks:[/{BOLD_WHITE}]")
@@ -518,6 +519,15 @@ def run_interactive_story() -> None:
518
519
 
519
520
  if selected_packs:
520
521
  hypotheses = [h for h in all_hypotheses if h.recommended_test_pack in selected_packs]
522
+ if not hypotheses and all_hypotheses:
523
+ avail_cats = list({h.category.value for h in all_hypotheses})
524
+ console.print(
525
+ f"\n [{ORANGE}]Notice:[/{ORANGE}] [{WHITE}]No endpoints matched the selected testpack filter ({', '.join(selected_packs)}).[/{WHITE}]"
526
+ )
527
+ console.print(
528
+ f" [{DIM_WHITE}]Codebase contains attack vectors for: {', '.join(avail_cats)}. Auditing all codebase attack paths to prevent false negatives...[/{DIM_WHITE}]\n"
529
+ )
530
+ hypotheses = all_hypotheses
521
531
  else:
522
532
  hypotheses = all_hypotheses
523
533
 
@@ -48,12 +48,18 @@ class AuthenticationTestPack(TestPack):
48
48
  f"Send unauthenticated {method} request to {target_url}",
49
49
  f"Received HTTP {obs.response_status} rather than expected 401/403",
50
50
  ]
51
+ elif obs.response_status == 0:
52
+ confirmed = False
53
+ summary = f"Target host unreachable ({obs.response_body}). Live probe inconclusive."
54
+ else:
55
+ confirmed = False
56
+ summary = f"Authentication enforced (server responded with HTTP {obs.response_status})."
51
57
 
52
58
  return TestExecutionResult(
53
59
  testpack_name=self.name,
54
60
  hypothesis_id=hypothesis.id,
55
61
  confirmed=confirmed,
56
- confidence=0.90 if confirmed else 0.15,
62
+ confidence=0.90 if confirmed else (None if obs.response_status == 0 else 0.15),
57
63
  summary=summary,
58
64
  observations=observations,
59
65
  reproduction_steps=steps,
@@ -58,12 +58,18 @@ class BflaTestPack(TestPack):
58
58
  f"Invoke privileged endpoint {method} {target_url}",
59
59
  f"Received HTTP {obs.response_status} indicating unauthorized function execution",
60
60
  ]
61
+ elif obs.response_status == 0:
62
+ confirmed = False
63
+ summary = f"Target host unreachable ({obs.response_body}). Live probe inconclusive."
64
+ else:
65
+ confirmed = False
66
+ summary = f"Function-level authorization enforced (received HTTP {obs.response_status})."
61
67
 
62
68
  return TestExecutionResult(
63
69
  testpack_name=self.name,
64
70
  hypothesis_id=hypothesis.id,
65
71
  confirmed=confirmed,
66
- confidence=0.92 if confirmed else 0.20,
72
+ confidence=0.92 if confirmed else (None if obs.response_status == 0 else 0.20),
67
73
  summary=summary,
68
74
  observations=observations,
69
75
  reproduction_steps=steps,
@@ -83,11 +83,15 @@ class BolaTestPack(TestPack):
83
83
  ]
84
84
  break
85
85
 
86
+ any_reachable = any(obs.response_status != 0 for obs in observations)
87
+ if not confirmed and not any_reachable:
88
+ summary = "Target host unreachable. Dynamic probe inconclusive."
89
+
86
90
  return TestExecutionResult(
87
91
  testpack_name=self.name,
88
92
  hypothesis_id=hypothesis.id,
89
93
  confirmed=confirmed,
90
- confidence=0.98 if confirmed else 0.20,
94
+ confidence=0.98 if confirmed else (None if not any_reachable else 0.20),
91
95
  summary=summary,
92
96
  observations=observations,
93
97
  reproduction_steps=steps,