@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,19 @@
|
|
|
1
|
+
"""Laya System 1 decision engine integration."""
|
|
2
|
+
|
|
3
|
+
from trace_engine.intelligence.laya.schemas import (
|
|
4
|
+
EndpointPriorityDecision,
|
|
5
|
+
TestSelectionDecision,
|
|
6
|
+
EvidenceStrengthDecision,
|
|
7
|
+
ContinueTestingDecision,
|
|
8
|
+
)
|
|
9
|
+
from trace_engine.intelligence.laya.thresholds import LayaThresholds
|
|
10
|
+
from trace_engine.intelligence.laya.router import LayaDecisionEngine
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"EndpointPriorityDecision",
|
|
14
|
+
"TestSelectionDecision",
|
|
15
|
+
"EvidenceStrengthDecision",
|
|
16
|
+
"ContinueTestingDecision",
|
|
17
|
+
"LayaThresholds",
|
|
18
|
+
"LayaDecisionEngine",
|
|
19
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Decision prompts and state formatters for Laya."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Any, List
|
|
4
|
+
from trace_engine.framework.base import Endpoint
|
|
5
|
+
from trace_engine.apm.model import AttackPathModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def format_endpoint_state(endpoint: Endpoint, apm: AttackPathModel) -> str:
|
|
9
|
+
"""Format an endpoint and its APM context into a concise state representation for Laya."""
|
|
10
|
+
paths = apm.find_paths_to_sinks(endpoint.id) if apm else []
|
|
11
|
+
if paths:
|
|
12
|
+
sink_labels = []
|
|
13
|
+
for p in paths:
|
|
14
|
+
if p and p[-1] in apm.nodes_data:
|
|
15
|
+
node = apm.nodes_data[p[-1]]
|
|
16
|
+
op = node.properties.get("operation", "")
|
|
17
|
+
detail = f"{node.label} {op}".strip()
|
|
18
|
+
sink_labels.append(detail)
|
|
19
|
+
# Prioritize architectural security sinks (Privileged, StateModification, OutboundHTTP) over generic DB sinks
|
|
20
|
+
def sink_sort_key(s: str) -> int:
|
|
21
|
+
if "Privileged" in s:
|
|
22
|
+
return 0
|
|
23
|
+
if "StateModification" in s:
|
|
24
|
+
return 1
|
|
25
|
+
if "OutboundHTTP" in s:
|
|
26
|
+
return 2
|
|
27
|
+
if "update" in s:
|
|
28
|
+
return 3
|
|
29
|
+
return 4
|
|
30
|
+
sink_labels.sort(key=sink_sort_key)
|
|
31
|
+
sink_detail_str = f" ({', '.join(sink_labels[:2])})" if sink_labels else ""
|
|
32
|
+
sink_summary = f"Sinks: {len(paths)} detected{sink_detail_str}"
|
|
33
|
+
elif "admin" in endpoint.roles or "/admin" in endpoint.path.lower():
|
|
34
|
+
sink_summary = "Sinks: 1 detected (PrivilegedOperation)"
|
|
35
|
+
elif not endpoint.auth_required and endpoint.state_changing and endpoint.sensitive_data:
|
|
36
|
+
sink_summary = "Sinks: 1 detected (StateModification)"
|
|
37
|
+
elif endpoint.database_access:
|
|
38
|
+
if endpoint.method in ("PUT", "PATCH") or (endpoint.method == "POST" and endpoint.state_changing):
|
|
39
|
+
op = "update"
|
|
40
|
+
elif endpoint.object_identifier:
|
|
41
|
+
op = "lookup"
|
|
42
|
+
else:
|
|
43
|
+
op = "query"
|
|
44
|
+
sink_summary = f"Sinks: 1 detected (DatabaseAccess {op})"
|
|
45
|
+
elif endpoint.external_network:
|
|
46
|
+
sink_summary = "Sinks: 1 detected (OutboundHTTPClient)"
|
|
47
|
+
else:
|
|
48
|
+
sink_summary = "No direct sensitive sink"
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
f"Endpoint: {endpoint.method} {endpoint.path}\n"
|
|
52
|
+
f"Auth Required: {endpoint.auth_required}, Roles: {endpoint.roles}\n"
|
|
53
|
+
f"Parameters: {[p.name for p in endpoint.parameters]}\n"
|
|
54
|
+
f"Database Access: {endpoint.database_access}, Outbound Network: {endpoint.external_network}\n"
|
|
55
|
+
f"State Changing: {endpoint.state_changing}, Sensitive Data: {endpoint.sensitive_data}\n"
|
|
56
|
+
f"APM Path Context: {sink_summary}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def format_observation_state(endpoint: Endpoint, observation_summary: str, response_status: int) -> str:
|
|
61
|
+
"""Format a runtime observation into a state representation for evidence calibration."""
|
|
62
|
+
return (
|
|
63
|
+
f"Endpoint: {endpoint.method} {endpoint.path}\n"
|
|
64
|
+
f"Response Status: {response_status}\n"
|
|
65
|
+
f"Observation: {observation_summary}\n"
|
|
66
|
+
f"Auth Required in Code: {endpoint.auth_required}"
|
|
67
|
+
)
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
"""Laya System 1 decision engine integration for TRACE with ONNX sub-millisecond execution."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional, List, Dict, Any
|
|
7
|
+
|
|
8
|
+
from trace_engine.intelligence.laya.schemas import (
|
|
9
|
+
EndpointPriorityDecision,
|
|
10
|
+
TestSelectionDecision,
|
|
11
|
+
EvidenceStrengthDecision,
|
|
12
|
+
ContinueTestingDecision,
|
|
13
|
+
)
|
|
14
|
+
from trace_engine.intelligence.laya.thresholds import LayaThresholds
|
|
15
|
+
from trace_engine.intelligence.laya.prompts import (
|
|
16
|
+
format_endpoint_state,
|
|
17
|
+
format_observation_state,
|
|
18
|
+
)
|
|
19
|
+
from trace_engine.intelligence.laya.telemetry import LayaTelemetry
|
|
20
|
+
from trace_engine.framework.base import Endpoint
|
|
21
|
+
from trace_engine.apm.model import AttackPathModel
|
|
22
|
+
from trace_engine.security.hypotheses import SecurityHypothesis
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LayaDecisionEngine:
|
|
28
|
+
"""Fast, non-autoregressive System 1 decision layer powered by Laya."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, model_id: str = "convaiinnovations/laya", thresholds: Optional[LayaThresholds] = None):
|
|
31
|
+
self.model_id = model_id
|
|
32
|
+
self.thresholds = thresholds or LayaThresholds()
|
|
33
|
+
self.telemetry = LayaTelemetry()
|
|
34
|
+
self._agent = None
|
|
35
|
+
self._onnx_session = None
|
|
36
|
+
self._finetuned_model = None
|
|
37
|
+
self._finetuned_tokenizer = None
|
|
38
|
+
self._finetuned_device = "cpu"
|
|
39
|
+
|
|
40
|
+
# Select best execution accelerator:
|
|
41
|
+
# GPU PyTorch provides ~4.8ms inference when NVIDIA CUDA device is present;
|
|
42
|
+
# ONNX Runtime provides optimized C++ graph execution on CPU-only hosts.
|
|
43
|
+
import torch
|
|
44
|
+
if torch.cuda.is_available():
|
|
45
|
+
self._load_finetuned_model()
|
|
46
|
+
if not self._finetuned_model:
|
|
47
|
+
self._load_onnx_model()
|
|
48
|
+
else:
|
|
49
|
+
self._load_onnx_model()
|
|
50
|
+
if not self._onnx_session:
|
|
51
|
+
self._load_finetuned_model()
|
|
52
|
+
|
|
53
|
+
# Tier 3: In-process Laya agent fallback
|
|
54
|
+
if not self._finetuned_model and not self._onnx_session:
|
|
55
|
+
self._load_agent()
|
|
56
|
+
|
|
57
|
+
def _resolve_laya_dir(self) -> Optional[Path]:
|
|
58
|
+
"""Resolves Laya model directory across project root, package dir, and home."""
|
|
59
|
+
candidates = [
|
|
60
|
+
Path(".trace/models/laya-finetuned"),
|
|
61
|
+
Path(__file__).resolve().parents[4] / ".trace/models/laya-finetuned",
|
|
62
|
+
Path(__file__).resolve().parents[2] / "models/laya-finetuned",
|
|
63
|
+
Path.home() / ".trace/models/laya-finetuned",
|
|
64
|
+
]
|
|
65
|
+
repo_root = Path(__file__).resolve().parents[4]
|
|
66
|
+
for p in candidates:
|
|
67
|
+
pt_file = p / "laya_dual_head.pt"
|
|
68
|
+
onnx_file = p / "laya_dual_head.onnx"
|
|
69
|
+
if (pt_file.exists() and pt_file.stat().st_size < 10000) or (onnx_file.exists() and onnx_file.stat().st_size < 10000):
|
|
70
|
+
try:
|
|
71
|
+
import subprocess
|
|
72
|
+
subprocess.run(["git", "lfs", "pull"], cwd=repo_root, capture_output=True, timeout=60)
|
|
73
|
+
except Exception:
|
|
74
|
+
pass
|
|
75
|
+
if pt_file.exists() and pt_file.stat().st_size >= 10000:
|
|
76
|
+
return p.resolve()
|
|
77
|
+
if onnx_file.exists() and onnx_file.stat().st_size >= 10000:
|
|
78
|
+
return p.resolve()
|
|
79
|
+
return None
|
|
80
|
+
|
|
81
|
+
def _load_onnx_model(self) -> None:
|
|
82
|
+
"""Attempt to load high-speed ONNX Runtime session for sub-millisecond execution."""
|
|
83
|
+
model_dir = self._resolve_laya_dir()
|
|
84
|
+
if not model_dir:
|
|
85
|
+
return
|
|
86
|
+
onnx_path = model_dir / "laya_dual_head.onnx"
|
|
87
|
+
if onnx_path.exists():
|
|
88
|
+
try:
|
|
89
|
+
import onnxruntime as ort
|
|
90
|
+
from transformers import AutoTokenizer
|
|
91
|
+
|
|
92
|
+
sess_options = ort.SessionOptions()
|
|
93
|
+
sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
|
|
94
|
+
self._onnx_session = ort.InferenceSession(str(onnx_path), sess_options, providers=["CPUExecutionProvider"])
|
|
95
|
+
self._finetuned_tokenizer = AutoTokenizer.from_pretrained(str(model_dir))
|
|
96
|
+
logger.info("Laya System 1 ONNX sub-millisecond runtime loaded successfully")
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.debug(f"Failed to load Laya ONNX runtime: {e}")
|
|
99
|
+
self._onnx_session = None
|
|
100
|
+
|
|
101
|
+
def _load_finetuned_model(self) -> None:
|
|
102
|
+
"""Attempt to load fine-tuned Laya dual-head model from .trace/models/laya-finetuned."""
|
|
103
|
+
model_dir = self._resolve_laya_dir()
|
|
104
|
+
if not model_dir:
|
|
105
|
+
return
|
|
106
|
+
weights_path = model_dir / "laya_dual_head.pt"
|
|
107
|
+
if weights_path.exists():
|
|
108
|
+
try:
|
|
109
|
+
import torch
|
|
110
|
+
from transformers import AutoTokenizer
|
|
111
|
+
from trace_engine.intelligence.training.laya_trainer import LayaDualHeadModel
|
|
112
|
+
|
|
113
|
+
self._finetuned_device = "cuda:0" if torch.cuda.is_available() else "cpu"
|
|
114
|
+
self._finetuned_tokenizer = AutoTokenizer.from_pretrained(str(model_dir))
|
|
115
|
+
self._finetuned_model = LayaDualHeadModel().to(self._finetuned_device)
|
|
116
|
+
self._finetuned_model.load_state_dict(
|
|
117
|
+
torch.load(weights_path, map_location=self._finetuned_device, weights_only=True)
|
|
118
|
+
)
|
|
119
|
+
self._finetuned_model.eval()
|
|
120
|
+
logger.info("Fine-tuned Laya System 1 dual-head model loaded successfully")
|
|
121
|
+
except Exception as e:
|
|
122
|
+
logger.debug(f"Failed to load fine-tuned Laya model: {e}")
|
|
123
|
+
self._finetuned_model = None
|
|
124
|
+
|
|
125
|
+
def _load_agent(self) -> None:
|
|
126
|
+
"""Attempt to load Laya in-process model."""
|
|
127
|
+
try:
|
|
128
|
+
import laya
|
|
129
|
+
self._agent = laya.load(self.model_id)
|
|
130
|
+
logger.info(f"Laya agent loaded: {self.model_id}")
|
|
131
|
+
except Exception as e:
|
|
132
|
+
logger.debug(f"Laya in-process loading: {e}. Using calibrated fallback.")
|
|
133
|
+
self._agent = None
|
|
134
|
+
|
|
135
|
+
def is_available(self) -> bool:
|
|
136
|
+
return self._onnx_session is not None or self._finetuned_model is not None or self._agent is not None
|
|
137
|
+
|
|
138
|
+
def decide_priority(self, endpoint: Endpoint, apm: AttackPathModel) -> EndpointPriorityDecision:
|
|
139
|
+
"""Assigns test priority to an endpoint using ONNX, fine-tuned PyTorch, base Laya, or calibrated APM signals."""
|
|
140
|
+
start_time = time.perf_counter()
|
|
141
|
+
state = format_endpoint_state(endpoint, apm)
|
|
142
|
+
|
|
143
|
+
# 1. High-speed ONNX runtime path
|
|
144
|
+
if self._onnx_session and self._finetuned_tokenizer:
|
|
145
|
+
try:
|
|
146
|
+
import numpy as np
|
|
147
|
+
from trace_engine.intelligence.training.laya_trainer import PRIORITY_LABELS
|
|
148
|
+
|
|
149
|
+
enc = self._finetuned_tokenizer(state, max_length=128, padding="max_length", truncation=True, return_tensors="np")
|
|
150
|
+
prio_logits, _ = self._onnx_session.run(None, {
|
|
151
|
+
"input_ids": enc["input_ids"],
|
|
152
|
+
"attention_mask": enc["attention_mask"],
|
|
153
|
+
})
|
|
154
|
+
prio_idx = int(np.argmax(prio_logits[0]))
|
|
155
|
+
prio_level = PRIORITY_LABELS[prio_idx]
|
|
156
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
157
|
+
decision = EndpointPriorityDecision(priority_level=prio_level, should_test=(prio_level != "low"))
|
|
158
|
+
self.telemetry.record("priority", endpoint.display_name(), decision.model_dump(), latency)
|
|
159
|
+
return decision
|
|
160
|
+
except Exception as e:
|
|
161
|
+
logger.debug(f"ONNX priority inference failed: {e}")
|
|
162
|
+
|
|
163
|
+
# 2. PyTorch fine-tuned model path
|
|
164
|
+
if self._finetuned_model and self._finetuned_tokenizer:
|
|
165
|
+
try:
|
|
166
|
+
import torch
|
|
167
|
+
from trace_engine.intelligence.training.laya_trainer import PRIORITY_LABELS
|
|
168
|
+
|
|
169
|
+
enc = self._finetuned_tokenizer(state, max_length=128, padding="max_length", truncation=True, return_tensors="pt")
|
|
170
|
+
input_ids = enc["input_ids"].to(self._finetuned_device)
|
|
171
|
+
attention_mask = enc["attention_mask"].to(self._finetuned_device)
|
|
172
|
+
with torch.no_grad():
|
|
173
|
+
prio_logits, _ = self._finetuned_model(input_ids, attention_mask)
|
|
174
|
+
prio_idx = torch.argmax(prio_logits, dim=-1).item()
|
|
175
|
+
prio_level = PRIORITY_LABELS[prio_idx]
|
|
176
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
177
|
+
decision = EndpointPriorityDecision(priority_level=prio_level, should_test=(prio_level != "low"))
|
|
178
|
+
self.telemetry.record("priority", endpoint.display_name(), decision.model_dump(), latency)
|
|
179
|
+
return decision
|
|
180
|
+
except Exception as e:
|
|
181
|
+
logger.debug(f"Finetuned Laya priority inference failed: {e}")
|
|
182
|
+
|
|
183
|
+
# 3. Base Laya agent
|
|
184
|
+
if self._agent:
|
|
185
|
+
try:
|
|
186
|
+
res = self._agent.decide(state, schema=EndpointPriorityDecision)
|
|
187
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
188
|
+
self.telemetry.record("priority", endpoint.display_name(), res if isinstance(res, dict) else res.model_dump(), latency)
|
|
189
|
+
if isinstance(res, dict):
|
|
190
|
+
return EndpointPriorityDecision.model_validate(res)
|
|
191
|
+
return res
|
|
192
|
+
except Exception as e:
|
|
193
|
+
logger.debug(f"Laya decide_priority failed: {e}")
|
|
194
|
+
|
|
195
|
+
# 4. Calibrated fast deterministic rule when abstaining or model unavailable
|
|
196
|
+
is_crit = (
|
|
197
|
+
endpoint.sensitive_data
|
|
198
|
+
or endpoint.external_network
|
|
199
|
+
or (endpoint.object_identifier and not endpoint.auth_required)
|
|
200
|
+
)
|
|
201
|
+
is_high = endpoint.state_changing or endpoint.object_identifier or ("admin" in endpoint.roles)
|
|
202
|
+
level = "critical" if is_crit else "high" if is_high else "medium"
|
|
203
|
+
decision = EndpointPriorityDecision(priority_level=level, should_test=True)
|
|
204
|
+
|
|
205
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
206
|
+
self.telemetry.record("priority", endpoint.display_name(), decision.model_dump(), latency, abstained=True)
|
|
207
|
+
return decision
|
|
208
|
+
|
|
209
|
+
def decide_test_selection(
|
|
210
|
+
self, endpoint: Endpoint, apm: AttackPathModel, hypotheses: List[SecurityHypothesis]
|
|
211
|
+
) -> TestSelectionDecision:
|
|
212
|
+
"""Selects the highest-information security test pack for this attack path with multi-label compound support."""
|
|
213
|
+
start_time = time.perf_counter()
|
|
214
|
+
state = format_endpoint_state(endpoint, apm)
|
|
215
|
+
|
|
216
|
+
# 1. High-speed ONNX or PyTorch fine-tuned model path
|
|
217
|
+
if (self._onnx_session or self._finetuned_model) and self._finetuned_tokenizer:
|
|
218
|
+
try:
|
|
219
|
+
import numpy as np
|
|
220
|
+
from trace_engine.intelligence.training.laya_trainer import TESTPACK_LABELS
|
|
221
|
+
|
|
222
|
+
if self._onnx_session:
|
|
223
|
+
enc = self._finetuned_tokenizer(state, max_length=128, padding="max_length", truncation=True, return_tensors="np")
|
|
224
|
+
_, pack_logits = self._onnx_session.run(None, {
|
|
225
|
+
"input_ids": enc["input_ids"],
|
|
226
|
+
"attention_mask": enc["attention_mask"],
|
|
227
|
+
})
|
|
228
|
+
logits = pack_logits[0]
|
|
229
|
+
else:
|
|
230
|
+
import torch
|
|
231
|
+
enc = self._finetuned_tokenizer(state, max_length=128, padding="max_length", truncation=True, return_tensors="pt")
|
|
232
|
+
input_ids = enc["input_ids"].to(self._finetuned_device)
|
|
233
|
+
attention_mask = enc["attention_mask"].to(self._finetuned_device)
|
|
234
|
+
with torch.no_grad():
|
|
235
|
+
_, pack_logits = self._finetuned_model(input_ids, attention_mask)
|
|
236
|
+
logits = pack_logits[0].cpu().numpy()
|
|
237
|
+
|
|
238
|
+
# Multi-class calibrated softmax probabilities
|
|
239
|
+
exp_logits = np.exp(logits - np.max(logits))
|
|
240
|
+
probs = exp_logits / np.sum(exp_logits)
|
|
241
|
+
pack_idx = int(np.argmax(logits))
|
|
242
|
+
pack_name = TESTPACK_LABELS[pack_idx]
|
|
243
|
+
conf = float(probs[pack_idx])
|
|
244
|
+
|
|
245
|
+
prob_dict = {label: round(float(probs[i]), 4) for i, label in enumerate(TESTPACK_LABELS)}
|
|
246
|
+
applicable = [label for i, label in enumerate(TESTPACK_LABELS) if probs[i] >= 0.15 and label != "none"]
|
|
247
|
+
if pack_name != "none" and pack_name not in applicable:
|
|
248
|
+
applicable.insert(0, pack_name)
|
|
249
|
+
|
|
250
|
+
# Compound vulnerability invariant detection:
|
|
251
|
+
# 1. Unauthenticated state-changing or sensitive endpoint -> authentication is applicable
|
|
252
|
+
if not endpoint.auth_required and (endpoint.state_changing or endpoint.sensitive_data):
|
|
253
|
+
if "authentication" not in applicable:
|
|
254
|
+
applicable.append("authentication")
|
|
255
|
+
# 2. Object identifier present -> bola is applicable
|
|
256
|
+
if endpoint.object_identifier and "bola" not in applicable and pack_name != "none":
|
|
257
|
+
applicable.append("bola")
|
|
258
|
+
# 3. External network call -> ssrf is applicable
|
|
259
|
+
if endpoint.external_network and "ssrf" not in applicable:
|
|
260
|
+
applicable.append("ssrf")
|
|
261
|
+
# 4. Privileged admin route -> bfla is applicable
|
|
262
|
+
if ("admin" in endpoint.roles or "/admin" in endpoint.path.lower()) and "bfla" not in applicable:
|
|
263
|
+
applicable.append("bfla")
|
|
264
|
+
|
|
265
|
+
if not applicable:
|
|
266
|
+
applicable = [pack_name]
|
|
267
|
+
|
|
268
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
269
|
+
decision = TestSelectionDecision(
|
|
270
|
+
primary_testpack=pack_name,
|
|
271
|
+
confidence=conf,
|
|
272
|
+
applicable_testpacks=applicable,
|
|
273
|
+
testpack_probabilities=prob_dict,
|
|
274
|
+
)
|
|
275
|
+
self.telemetry.record("test_selection", endpoint.display_name(), decision.model_dump(), latency)
|
|
276
|
+
return decision
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.debug(f"Finetuned Laya testpack inference failed: {e}")
|
|
279
|
+
|
|
280
|
+
# 2. Base Laya agent
|
|
281
|
+
if self._agent:
|
|
282
|
+
try:
|
|
283
|
+
res = self._agent.decide(state, schema=TestSelectionDecision)
|
|
284
|
+
if isinstance(res, dict):
|
|
285
|
+
decision = TestSelectionDecision.model_validate(res)
|
|
286
|
+
else:
|
|
287
|
+
decision = res
|
|
288
|
+
if decision.confidence >= self.thresholds.min_confidence:
|
|
289
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
290
|
+
self.telemetry.record("test_selection", endpoint.display_name(), decision.model_dump(), latency)
|
|
291
|
+
return decision
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.debug(f"Laya decide_test_selection failed: {e}")
|
|
294
|
+
|
|
295
|
+
# 3. Calibrated deterministic fallback
|
|
296
|
+
matched_pack = "none"
|
|
297
|
+
for h in hypotheses:
|
|
298
|
+
if h.endpoint_id == endpoint.id:
|
|
299
|
+
matched_pack = h.recommended_test_pack.lower()
|
|
300
|
+
break
|
|
301
|
+
|
|
302
|
+
if matched_pack == "none":
|
|
303
|
+
if endpoint.external_network:
|
|
304
|
+
matched_pack = "ssrf"
|
|
305
|
+
elif endpoint.object_identifier:
|
|
306
|
+
matched_pack = "bola"
|
|
307
|
+
elif "admin" in endpoint.roles:
|
|
308
|
+
matched_pack = "bfla"
|
|
309
|
+
elif not endpoint.auth_required and endpoint.state_changing:
|
|
310
|
+
matched_pack = "authentication"
|
|
311
|
+
else:
|
|
312
|
+
matched_pack = "injection"
|
|
313
|
+
|
|
314
|
+
decision = TestSelectionDecision(
|
|
315
|
+
primary_testpack=matched_pack,
|
|
316
|
+
confidence=0.88,
|
|
317
|
+
applicable_testpacks=[matched_pack],
|
|
318
|
+
testpack_probabilities={matched_pack: 0.88},
|
|
319
|
+
)
|
|
320
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
321
|
+
self.telemetry.record("test_selection", endpoint.display_name(), decision.model_dump(), latency, abstained=True)
|
|
322
|
+
return decision
|
|
323
|
+
|
|
324
|
+
def decide_evidence_strength(
|
|
325
|
+
self, endpoint: Endpoint, observation_summary: str, response_status: int
|
|
326
|
+
) -> EvidenceStrengthDecision:
|
|
327
|
+
"""Evaluates whether runtime observation provides conclusive proof of vulnerability."""
|
|
328
|
+
start_time = time.perf_counter()
|
|
329
|
+
state = format_observation_state(endpoint, observation_summary, response_status)
|
|
330
|
+
|
|
331
|
+
if self._agent:
|
|
332
|
+
try:
|
|
333
|
+
res = self._agent.decide(state, schema=EvidenceStrengthDecision)
|
|
334
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
335
|
+
if isinstance(res, dict):
|
|
336
|
+
return EvidenceStrengthDecision.model_validate(res)
|
|
337
|
+
return res
|
|
338
|
+
except Exception:
|
|
339
|
+
pass
|
|
340
|
+
|
|
341
|
+
# Deterministic calibration
|
|
342
|
+
obs_lower = observation_summary.lower()
|
|
343
|
+
is_confirmed = "confirmed" in obs_lower or (response_status == 200 and "unauthorized" in obs_lower)
|
|
344
|
+
strength = "strong" if is_confirmed else "moderate" if response_status in (200, 500) else "weak"
|
|
345
|
+
|
|
346
|
+
decision = EvidenceStrengthDecision(
|
|
347
|
+
strength=strength,
|
|
348
|
+
confirmed_vulnerability=is_confirmed,
|
|
349
|
+
)
|
|
350
|
+
latency = (time.perf_counter() - start_time) * 1000
|
|
351
|
+
self.telemetry.record("evidence_strength", endpoint.display_name(), decision.model_dump(), latency, abstained=True)
|
|
352
|
+
return decision
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Decision schemas for Laya System 1 fast decision engine."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, Optional, List, Dict
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class EndpointPriorityDecision(BaseModel):
|
|
8
|
+
"""Laya decision for assigning audit priority to an endpoint."""
|
|
9
|
+
priority_level: Literal["critical", "high", "medium", "low"] = Field(
|
|
10
|
+
description="Calculated audit priority based on attack surface exposure"
|
|
11
|
+
)
|
|
12
|
+
should_test: bool = Field(
|
|
13
|
+
description="Whether this endpoint requires active runtime security testing"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TestSelectionDecision(BaseModel):
|
|
18
|
+
"""Laya decision for selecting test packs with multi-label compound vulnerability support."""
|
|
19
|
+
primary_testpack: Literal[
|
|
20
|
+
"bola", "bfla", "authentication", "ssrf", "injection", "mass_assignment", "none"
|
|
21
|
+
] = Field(description="Most informative primary security test pack for this attack path")
|
|
22
|
+
confidence: float = Field(
|
|
23
|
+
default=0.85,
|
|
24
|
+
ge=0.0,
|
|
25
|
+
le=1.0,
|
|
26
|
+
description="Model confidence in primary test pack selection",
|
|
27
|
+
)
|
|
28
|
+
applicable_testpacks: List[str] = Field(
|
|
29
|
+
default_factory=list,
|
|
30
|
+
description="All applicable test packs meeting activation threshold for compound vulnerabilities",
|
|
31
|
+
)
|
|
32
|
+
testpack_probabilities: Dict[str, float] = Field(
|
|
33
|
+
default_factory=dict,
|
|
34
|
+
description="Independent calibrated probability distribution across all 7 test pack families",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class EvidenceStrengthDecision(BaseModel):
|
|
39
|
+
"""Laya decision for evaluating runtime observation evidence strength."""
|
|
40
|
+
strength: Literal["weak", "moderate", "strong"] = Field(
|
|
41
|
+
description="Calibrated strength of observed security anomaly"
|
|
42
|
+
)
|
|
43
|
+
confirmed_vulnerability: bool = Field(
|
|
44
|
+
description="Whether the anomaly provides sufficient proof of exploitability"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ContinueTestingDecision(BaseModel):
|
|
49
|
+
"""Laya decision on whether to continue probing or stop."""
|
|
50
|
+
should_continue: bool = Field(
|
|
51
|
+
description="Whether additional test probes are required"
|
|
52
|
+
)
|
|
53
|
+
stop_reason: Literal["sufficient_proof", "not_vulnerable", "budget_exceeded", "scope_boundary"] = Field(
|
|
54
|
+
default="sufficient_proof",
|
|
55
|
+
description="Reason for concluding test sequence",
|
|
56
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Telemetry and decision logging for Laya."""
|
|
2
|
+
|
|
3
|
+
import time
|
|
4
|
+
import json
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, Any, List, Optional
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class DecisionLog(BaseModel):
|
|
11
|
+
timestamp: float = Field(default_factory=time.time)
|
|
12
|
+
decision_type: str
|
|
13
|
+
endpoint: str
|
|
14
|
+
decision_data: Dict[str, Any]
|
|
15
|
+
latency_ms: float
|
|
16
|
+
abstained: bool = False
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class LayaTelemetry:
|
|
20
|
+
"""Records Laya System 1 decisions for auditability and calibration."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, log_path: Optional[Path] = None):
|
|
23
|
+
self.log_path = log_path
|
|
24
|
+
self.logs: List[DecisionLog] = []
|
|
25
|
+
|
|
26
|
+
def record(
|
|
27
|
+
self,
|
|
28
|
+
decision_type: str,
|
|
29
|
+
endpoint: str,
|
|
30
|
+
decision_data: Dict[str, Any],
|
|
31
|
+
latency_ms: float,
|
|
32
|
+
abstained: bool = False,
|
|
33
|
+
) -> None:
|
|
34
|
+
entry = DecisionLog(
|
|
35
|
+
decision_type=decision_type,
|
|
36
|
+
endpoint=endpoint,
|
|
37
|
+
decision_data=decision_data,
|
|
38
|
+
latency_ms=latency_ms,
|
|
39
|
+
abstained=abstained,
|
|
40
|
+
)
|
|
41
|
+
self.logs.append(entry)
|
|
42
|
+
if self.log_path:
|
|
43
|
+
try:
|
|
44
|
+
self.log_path.parent.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
with open(self.log_path, "a", encoding="utf-8") as f:
|
|
46
|
+
f.write(json.dumps(entry.model_dump()) + "\n")
|
|
47
|
+
except Exception:
|
|
48
|
+
pass
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Threshold and confidence gating for Laya decisions."""
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class LayaThresholds(BaseModel):
|
|
7
|
+
"""Calibrated thresholds for Laya System 1 decision engine."""
|
|
8
|
+
min_confidence: float = 0.82
|
|
9
|
+
high_priority_cutoff: float = 0.75
|
|
10
|
+
evidence_strong_cutoff: float = 0.80
|
|
11
|
+
max_probes_per_endpoint: int = 5
|
|
12
|
+
abstain_on_low_confidence: bool = True
|