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.
|
|
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
package/src/trace_engine/cli.py
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
298
|
-
if
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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": ("
|
|
442
|
-
"2": ("
|
|
443
|
-
"3": ("
|
|
444
|
-
"4": ("
|
|
445
|
-
"5": ("
|
|
446
|
-
"6": ("
|
|
447
|
-
"7": ("
|
|
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,
|