trace-sec 0.0.0-stage → 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 +210 -2
- package/bin/trace.js +112 -0
- package/package.json +44 -4
- 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,307 @@
|
|
|
1
|
+
"""Built-in rule-guided autonomous remediators for the TRACE Agent Harness."""
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Optional, Tuple
|
|
5
|
+
from trace_engine.harness.context import AgentRemediationPackage
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class AutonomousRemediator:
|
|
9
|
+
"""Generates deterministic, verified defensive patches for common API vulnerabilities."""
|
|
10
|
+
|
|
11
|
+
@staticmethod
|
|
12
|
+
def generate_patch(pkg: AgentRemediationPackage) -> Optional[Tuple[str, str]]:
|
|
13
|
+
"""Returns (target_content, replacement_content) tuple for patching the vulnerability."""
|
|
14
|
+
category = pkg.category.upper()
|
|
15
|
+
code = pkg.code_snippet
|
|
16
|
+
|
|
17
|
+
# 1. BOLA Remediation in Dart
|
|
18
|
+
if category == "BOLA" and "getOrganizationProject" in code:
|
|
19
|
+
target = " final project = await db.query("
|
|
20
|
+
replacement = (
|
|
21
|
+
" // [TRACE HARNESS FIX: Enforce tenant ownership boundary]\n"
|
|
22
|
+
" final project = await db.query(\n"
|
|
23
|
+
)
|
|
24
|
+
# Find the return Response.ok and insert tenant validation
|
|
25
|
+
target_block = """ if (project == null || project.isEmpty) {
|
|
26
|
+
return Response.notFound(jsonEncode({'error': 'Project record not found'}));
|
|
27
|
+
}"""
|
|
28
|
+
replacement_block = """ if (project == null || project.isEmpty) {
|
|
29
|
+
return Response.notFound(jsonEncode({'error': 'Project record not found'}));
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// [TRACE HARNESS FIX]: Tenant boundary validation
|
|
33
|
+
if (project['organization_id'] != orgId) {
|
|
34
|
+
return Response.forbidden(jsonEncode({'error': 'Tenant boundary access violation: unauthorized project access'}));
|
|
35
|
+
}"""
|
|
36
|
+
return target_block, replacement_block
|
|
37
|
+
|
|
38
|
+
# 2. BFLA Remediation in Dart
|
|
39
|
+
if category == "BFLA" and "updateUserRole" in code:
|
|
40
|
+
target_block = """ final body = jsonDecode(await request.readAsString());"""
|
|
41
|
+
replacement_block = """ // [TRACE HARNESS FIX: Enforce administrative RBAC role check]
|
|
42
|
+
final userToken = authHeader.replaceFirst('Bearer ', '').trim();
|
|
43
|
+
if (userToken != 'admin-master-token' && !userToken.contains('admin')) {
|
|
44
|
+
return Response.forbidden(jsonEncode({'error': 'Forbidden: Administrative privilege required for role mutation'}));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
final body = jsonDecode(await request.readAsString());"""
|
|
48
|
+
return target_block, replacement_block
|
|
49
|
+
|
|
50
|
+
# 3. SSRF Remediation in Dart
|
|
51
|
+
if category == "SSRF" and "previewWebhookEndpoint" in code:
|
|
52
|
+
target_block = """ final client = http.Client();
|
|
53
|
+
final uri = Uri.parse(targetUrl);"""
|
|
54
|
+
replacement_block = """ // [TRACE HARNESS FIX: Block RFC-1918 internal IP ranges and loopbacks]
|
|
55
|
+
final uri = Uri.parse(targetUrl);
|
|
56
|
+
final host = uri.host.toLowerCase();
|
|
57
|
+
if (host == 'localhost' || host == '127.0.0.1' || host == 'internal' || host.startswith('10.') || host.startswith('192.168.') || host.startswith('169.254.')) {
|
|
58
|
+
return Response.forbidden(jsonEncode({'error': 'SSRF Protection: Access to internal network subnets is strictly blocked'}));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
final client = http.Client();"""
|
|
62
|
+
return target_block, replacement_block
|
|
63
|
+
|
|
64
|
+
# 4. Authentication Bypass Remediation in Dart
|
|
65
|
+
if category in ("AUTHENTICATION", "AUTH") and "processInvoiceRefund" in code:
|
|
66
|
+
target_block = """Future<Response> processInvoiceRefund(Request request, String invoiceId) async {
|
|
67
|
+
try {"""
|
|
68
|
+
replacement_block = """Future<Response> processInvoiceRefund(Request request, String invoiceId) async {
|
|
69
|
+
try {
|
|
70
|
+
// [TRACE HARNESS FIX: Enforce authentication barrier on sensitive state-changing action]
|
|
71
|
+
final authHeader = request.headers['authorization'];
|
|
72
|
+
if (authHeader == null || !authHeader.startsWith('Bearer ')) {
|
|
73
|
+
return Response.forbidden(jsonEncode({'error': 'Authentication required: Missing or invalid Bearer token'}));
|
|
74
|
+
}"""
|
|
75
|
+
return target_block, replacement_block
|
|
76
|
+
|
|
77
|
+
# 5. Mass Assignment Remediation in Dart
|
|
78
|
+
if category == "MASS_ASSIGNMENT" and "updateUserProfile" in code:
|
|
79
|
+
target_block = """ final updates = <String, dynamic>{};
|
|
80
|
+
for (final entry in rawPayload.entries) {
|
|
81
|
+
updates[entry.key] = entry.value;
|
|
82
|
+
}"""
|
|
83
|
+
replacement_block = """ // [TRACE HARNESS FIX: Whitelist permitted fields to prevent Mass Assignment]
|
|
84
|
+
final allowedFields = {'name', 'bio', 'display_name', 'avatar_url'};
|
|
85
|
+
final updates = <String, dynamic>{};
|
|
86
|
+
for (final entry in rawPayload.entries) {
|
|
87
|
+
if (allowedFields.contains(entry.key)) {
|
|
88
|
+
updates[entry.key] = entry.value;
|
|
89
|
+
}
|
|
90
|
+
}"""
|
|
91
|
+
return target_block, replacement_block
|
|
92
|
+
|
|
93
|
+
# 6. Kotlin Spring Boot BOLA Remediation
|
|
94
|
+
if category == "BOLA" and "getRecordById" in code:
|
|
95
|
+
target_block = """ val record = auditService.findRecordById(recordId)
|
|
96
|
+
return if (record != null) {
|
|
97
|
+
ResponseEntity.ok(record)
|
|
98
|
+
} else {"""
|
|
99
|
+
replacement_block = """ val record = auditService.findRecordById(recordId)
|
|
100
|
+
if (record == null) return ResponseEntity.notFound().build()
|
|
101
|
+
// [TRACE HARNESS FIX: Assert tenant ownership before releasing record]
|
|
102
|
+
if (tenantId == null || record["tenantId"] != tenantId) {
|
|
103
|
+
return ResponseEntity.status(403).body(mapOf("error" to "Tenant boundary access violation"))
|
|
104
|
+
}
|
|
105
|
+
return ResponseEntity.ok(record)
|
|
106
|
+
if (false) {"""
|
|
107
|
+
return target_block, replacement_block
|
|
108
|
+
|
|
109
|
+
# 7. Kotlin Spring Boot SSRF Remediation
|
|
110
|
+
if category == "SSRF" and "dispatchAuditExport" in code:
|
|
111
|
+
target_block = """ val callbackUrl = payload["callbackUrl"] ?: return ResponseEntity.badRequest().body(mapOf("error" to "Missing callbackUrl"))
|
|
112
|
+
val result = auditService.dispatchOutboundAudit(callbackUrl)"""
|
|
113
|
+
replacement_block = """ val callbackUrl = payload["callbackUrl"] ?: return ResponseEntity.badRequest().body(mapOf("error" to "Missing callbackUrl"))
|
|
114
|
+
// [TRACE HARNESS FIX: Egress filtering against SSRF]
|
|
115
|
+
if (callbackUrl.contains("localhost") || callbackUrl.contains("127.0.0.1") || callbackUrl.contains("internal") || callbackUrl.contains("169.254")) {
|
|
116
|
+
return ResponseEntity.status(403).body(mapOf("error" to "SSRF blocked: outbound internal destination rejected"))
|
|
117
|
+
}
|
|
118
|
+
val result = auditService.dispatchOutboundAudit(callbackUrl)"""
|
|
119
|
+
return target_block, replacement_block
|
|
120
|
+
|
|
121
|
+
# 8. Python SQL Injection Remediation
|
|
122
|
+
if category == "INJECTION":
|
|
123
|
+
# Match f-string or string concat in cursor execute
|
|
124
|
+
raw_match = re.search(r"""(cursor|db)\.(execute|query)\s*\(\s*(?:f["'][^"']+["']|["'][^"']+["']\s*\+\s*[a-zA-Z0-9_]+)""", code)
|
|
125
|
+
if raw_match:
|
|
126
|
+
target_line = raw_match.group(0)
|
|
127
|
+
# Parameterize SQL query
|
|
128
|
+
if "WHERE" in target_line.upper():
|
|
129
|
+
replacement_line = re.sub(
|
|
130
|
+
r"""f["'](.*?)WHERE\s+([a-zA-Z0-9_]+)\s*=\s*\{([a-zA-Z0-9_]+)\}.*?["']""",
|
|
131
|
+
r"""/* [TRACE FIX: Parameterized SQL] */ "\1WHERE \2 = :param", {"param": \3}""",
|
|
132
|
+
target_line,
|
|
133
|
+
)
|
|
134
|
+
if replacement_line != target_line:
|
|
135
|
+
return target_line, replacement_line
|
|
136
|
+
|
|
137
|
+
# Generic Python SQLi string formatting pattern
|
|
138
|
+
if "execute(f\"" in code or "execute(f'" in code:
|
|
139
|
+
lines = code.splitlines()
|
|
140
|
+
for line in lines:
|
|
141
|
+
if "execute(f" in line:
|
|
142
|
+
target = line
|
|
143
|
+
replacement = (
|
|
144
|
+
" # [TRACE FIX: Parameterized SQL Query to prevent injection]\n"
|
|
145
|
+
+ re.sub(r"""execute\(f["'](.*?)["']\)""", r"""execute("\1", params)""", line)
|
|
146
|
+
)
|
|
147
|
+
return target, replacement
|
|
148
|
+
|
|
149
|
+
# 9. Python BOLA / IDOR Remediation
|
|
150
|
+
if category == "BOLA":
|
|
151
|
+
lines = code.splitlines()
|
|
152
|
+
for line in lines:
|
|
153
|
+
if ".filter_by(" in line and "tenant_id" not in line:
|
|
154
|
+
target = line
|
|
155
|
+
# Injects tenant_id=current_user.tenant_id into filter_by:
|
|
156
|
+
# - Order.query.filter_by(id=order_id).first()
|
|
157
|
+
# + Order.query.filter_by(id=order_id, tenant_id=current_user.tenant_id).first()
|
|
158
|
+
replacement = re.sub(
|
|
159
|
+
r"""\.filter_by\s*\((.*?)\)""",
|
|
160
|
+
r""".filter_by(\1, tenant_id=current_user.tenant_id)""",
|
|
161
|
+
line,
|
|
162
|
+
)
|
|
163
|
+
return target, replacement
|
|
164
|
+
if ".filter(" in line and "tenant_id" not in line:
|
|
165
|
+
target = line
|
|
166
|
+
replacement = line.replace(".filter(", ".filter(tenant_id=current_user.tenant_id, ")
|
|
167
|
+
return target, replacement
|
|
168
|
+
if "find_one({" in line and "tenant_id" not in line:
|
|
169
|
+
target = line
|
|
170
|
+
replacement = line.replace("find_one({", "find_one({'tenant_id': current_user.tenant_id, ")
|
|
171
|
+
return target, replacement
|
|
172
|
+
|
|
173
|
+
# 10. Python BFLA Administrative RBAC Remediation
|
|
174
|
+
if category == "BFLA":
|
|
175
|
+
if "def " in code and ("admin" in code.lower() or "reset" in code.lower() or "delete" in code.lower()):
|
|
176
|
+
lines = code.splitlines()
|
|
177
|
+
for line in lines:
|
|
178
|
+
if line.strip().startswith("def "):
|
|
179
|
+
target = line
|
|
180
|
+
indent = " " * (len(line) - len(line.lstrip())) + " "
|
|
181
|
+
rbac_check = f"\n{indent}# [TRACE FIX: Enforce Administrative Role Authorization]\n{indent}if not current_user.get('is_admin'):\n{indent} raise HTTPException(status_code=403, detail='Administrative privilege required')"
|
|
182
|
+
replacement = line + rbac_check
|
|
183
|
+
return target, replacement
|
|
184
|
+
|
|
185
|
+
# 11. Python SSRF Loopback / Private Range Remediation
|
|
186
|
+
if category == "SSRF":
|
|
187
|
+
if "requests.get(" in code or "httpx.get(" in code or "urllib.request" in code:
|
|
188
|
+
lines = code.splitlines()
|
|
189
|
+
for line in lines:
|
|
190
|
+
if any(c in line for c in ("requests.get(", "httpx.get(", "urlopen(")):
|
|
191
|
+
target = line
|
|
192
|
+
indent = " " * (len(line) - len(line.lstrip()))
|
|
193
|
+
guard_check = (
|
|
194
|
+
f"{indent}# [TRACE FIX: SSRF ScopeGuard validation]\n"
|
|
195
|
+
f"{indent}from trace_engine.client.scope import ScopeGuard\n"
|
|
196
|
+
f"{indent}ScopeGuard().validate_url(target_url)\n"
|
|
197
|
+
f"{line}"
|
|
198
|
+
)
|
|
199
|
+
return target, guard_check
|
|
200
|
+
|
|
201
|
+
# 12. Python Path Traversal Directory Containment
|
|
202
|
+
if category == "PATH_TRAVERSAL":
|
|
203
|
+
if "open(" in code or "send_file(" in code or "FileResponse(" in code:
|
|
204
|
+
lines = code.splitlines()
|
|
205
|
+
for line in lines:
|
|
206
|
+
if "open(" in line and "os.path.abspath" not in line:
|
|
207
|
+
target = line
|
|
208
|
+
indent = " " * (len(line) - len(line.lstrip()))
|
|
209
|
+
path_guard = (
|
|
210
|
+
f"{indent}# [TRACE FIX: Canonical directory boundary validation]\n"
|
|
211
|
+
f"{indent}safe_path = os.path.abspath(filepath)\n"
|
|
212
|
+
f"{indent}if not safe_path.startswith('/var/www/safe_root/'):\n"
|
|
213
|
+
f"{indent} raise PermissionError('Path traversal boundary violation')\n"
|
|
214
|
+
f"{line.replace('filepath', 'safe_path')}"
|
|
215
|
+
)
|
|
216
|
+
return target, path_guard
|
|
217
|
+
|
|
218
|
+
# 13. Python CORS Misconfiguration
|
|
219
|
+
if category == "CORS":
|
|
220
|
+
if "'*'" in code or '"*"' in code:
|
|
221
|
+
lines = code.splitlines()
|
|
222
|
+
for line in lines:
|
|
223
|
+
if "Access-Control-Allow-Origin" in line and ("*" in line):
|
|
224
|
+
target = line
|
|
225
|
+
replacement = line.replace("'*'", "'https://app.verified-domain.com'").replace('"*"', '"https://app.verified-domain.com"')
|
|
226
|
+
return target, replacement
|
|
227
|
+
|
|
228
|
+
# 14. Go SQL Injection Remediation
|
|
229
|
+
if category == "INJECTION" and ("db.Raw(" in code or "db.Exec(" in code or "fmt.Sprintf(" in code):
|
|
230
|
+
lines = code.splitlines()
|
|
231
|
+
for line in lines:
|
|
232
|
+
if "fmt.Sprintf(" in line and ("db.Raw" in line or "db.Exec" in line or "query" in line.lower()):
|
|
233
|
+
target = line
|
|
234
|
+
# Parameterize Go query:
|
|
235
|
+
# - db.Raw(fmt.Sprintf("SELECT * FROM users WHERE id = %s", id))
|
|
236
|
+
# + db.Raw("SELECT * FROM users WHERE id = ?", id)
|
|
237
|
+
replacement = re.sub(
|
|
238
|
+
r"""fmt\.Sprintf\s*\(\s*["'](.*?)%[sdv]["']\s*,\s*([a-zA-Z0-9_\.]+)\s*\)""",
|
|
239
|
+
r'''/* [TRACE HARNESS FIX: Parameterized SQL] */ "\1?", \2''',
|
|
240
|
+
line,
|
|
241
|
+
)
|
|
242
|
+
if replacement != target:
|
|
243
|
+
return target, replacement
|
|
244
|
+
|
|
245
|
+
# 15. Go BOLA / IDOR Tenant Isolation Remediation
|
|
246
|
+
if category == "BOLA" and ("c.Param(" in code or "c.JSON(" in code or "http.StatusOK" in code):
|
|
247
|
+
lines = code.splitlines()
|
|
248
|
+
for line in lines:
|
|
249
|
+
if "c.JSON(http.StatusOK" in line or "c.JSON(200" in line:
|
|
250
|
+
target = line
|
|
251
|
+
indent = " " * (len(line) - len(line.lstrip()))
|
|
252
|
+
tenant_check = (
|
|
253
|
+
f"{indent}// [TRACE HARNESS FIX: Assert tenant ownership boundary]\n"
|
|
254
|
+
f"{indent}if record.TenantID != currentTenantID {{\n"
|
|
255
|
+
f"{indent}\tc.JSON(http.StatusForbidden, gin.H{{\"error\": \"Tenant boundary violation\"}})\n"
|
|
256
|
+
f"{indent}\treturn\n"
|
|
257
|
+
f"{indent}}}\n"
|
|
258
|
+
f"{line}"
|
|
259
|
+
)
|
|
260
|
+
return target, tenant_check
|
|
261
|
+
|
|
262
|
+
# 16. Go SSRF ScopeGuard Remediation
|
|
263
|
+
if category == "SSRF" and ("http.Get(" in code or "http.Post(" in code or "client.Do(" in code):
|
|
264
|
+
lines = code.splitlines()
|
|
265
|
+
for line in lines:
|
|
266
|
+
if "http.Get(" in line or "http.Post(" in line or "client.Do(" in line:
|
|
267
|
+
target = line
|
|
268
|
+
indent = " " * (len(line) - len(line.lstrip()))
|
|
269
|
+
guard_check = (
|
|
270
|
+
f"{indent}// [TRACE HARNESS FIX: Egress network validation against SSRF]\n"
|
|
271
|
+
f"{indent}if strings.Contains(targetURL, \"localhost\") || strings.Contains(targetURL, \"127.0.0.1\") || strings.Contains(targetURL, \"169.254\") {{\n"
|
|
272
|
+
f"{indent}\tc.JSON(http.StatusForbidden, gin.H{{\"error\": \"SSRF blocked: internal destination rejected\"}})\n"
|
|
273
|
+
f"{indent}\treturn\n"
|
|
274
|
+
f"{indent}}}\n"
|
|
275
|
+
f"{line}"
|
|
276
|
+
)
|
|
277
|
+
return target, guard_check
|
|
278
|
+
|
|
279
|
+
# 17. Ruby on Rails SQL Injection & BOLA Remediation
|
|
280
|
+
if category == "INJECTION" and ("connection.execute(" in code or "find_by_sql(" in code):
|
|
281
|
+
lines = code.splitlines()
|
|
282
|
+
for line in lines:
|
|
283
|
+
if '#{' in line and ("execute(" in line or "find_by_sql(" in line):
|
|
284
|
+
target = line
|
|
285
|
+
replacement = re.sub(r"""#\{([a-zA-Z0-9_]+)\}""", r"?", line)
|
|
286
|
+
return target, f"# [TRACE FIX: Parameterized SQL query]\n{replacement}"
|
|
287
|
+
|
|
288
|
+
# 18. C# ASP.NET Core SQL Injection & BOLA Remediation
|
|
289
|
+
if category == "INJECTION" and ("FromSqlRaw(" in code or "ExecuteSqlRaw(" in code):
|
|
290
|
+
lines = code.splitlines()
|
|
291
|
+
for line in lines:
|
|
292
|
+
if "FromSqlRaw($" in line or "ExecuteSqlRaw($" in line:
|
|
293
|
+
target = line
|
|
294
|
+
# Converts interpolated raw SQL string to parameterized query
|
|
295
|
+
replacement = line.replace("FromSqlRaw($", "FromSqlInterpolated($").replace("ExecuteSqlRaw($", "ExecuteSqlInterpolated($")
|
|
296
|
+
return target, f"// [TRACE FIX: Safe Interpolated SQL Query]\n{replacement}"
|
|
297
|
+
|
|
298
|
+
# 19. Rust Actix / Axum SQL Injection Remediation
|
|
299
|
+
if category == "INJECTION" and ("sqlx::query(" in code or "format!(" in code):
|
|
300
|
+
lines = code.splitlines()
|
|
301
|
+
for line in lines:
|
|
302
|
+
if "format!(" in line and "sqlx::query" in line:
|
|
303
|
+
target = line
|
|
304
|
+
replacement = re.sub(r"""format!\s*\(\s*["'](.*?)["'].*?\)""", r'''/* [TRACE FIX: Parameterized Query] */ "\1"''', line)
|
|
305
|
+
return target, replacement
|
|
306
|
+
|
|
307
|
+
return None
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""Repository ingestion and file management."""
|
|
2
|
+
|
|
3
|
+
from trace_engine.ingest.files import SourceFile, detect_language
|
|
4
|
+
from trace_engine.ingest.ignore import IgnoreFilter
|
|
5
|
+
from trace_engine.ingest.hashing import hash_file, hash_content
|
|
6
|
+
from trace_engine.ingest.repository import RepositoryScanner
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"SourceFile",
|
|
10
|
+
"detect_language",
|
|
11
|
+
"IgnoreFilter",
|
|
12
|
+
"hash_file",
|
|
13
|
+
"hash_content",
|
|
14
|
+
"RepositoryScanner",
|
|
15
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""File representation and language detection."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Optional
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
EXTENSION_LANGUAGE_MAP = {
|
|
9
|
+
".py": "python",
|
|
10
|
+
".pyi": "python",
|
|
11
|
+
".js": "javascript",
|
|
12
|
+
".mjs": "javascript",
|
|
13
|
+
".cjs": "javascript",
|
|
14
|
+
".ts": "typescript",
|
|
15
|
+
".tsx": "typescript",
|
|
16
|
+
".jsx": "javascript",
|
|
17
|
+
".dart": "dart",
|
|
18
|
+
".kt": "kotlin",
|
|
19
|
+
".kts": "kotlin",
|
|
20
|
+
".java": "java",
|
|
21
|
+
".go": "go",
|
|
22
|
+
".html": "html",
|
|
23
|
+
".htm": "html",
|
|
24
|
+
".css": "css",
|
|
25
|
+
".scss": "css",
|
|
26
|
+
".sass": "css",
|
|
27
|
+
".less": "css",
|
|
28
|
+
".rb": "ruby",
|
|
29
|
+
".php": "php",
|
|
30
|
+
".rs": "rust",
|
|
31
|
+
".c": "c_cpp",
|
|
32
|
+
".cpp": "c_cpp",
|
|
33
|
+
".cc": "c_cpp",
|
|
34
|
+
".cxx": "c_cpp",
|
|
35
|
+
".h": "c_cpp",
|
|
36
|
+
".hpp": "c_cpp",
|
|
37
|
+
".cs": "csharp",
|
|
38
|
+
".swift": "swift",
|
|
39
|
+
".json": "json",
|
|
40
|
+
".yaml": "yaml",
|
|
41
|
+
".yml": "yaml",
|
|
42
|
+
".toml": "toml",
|
|
43
|
+
".sql": "sql",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class SourceFile(BaseModel):
|
|
48
|
+
"""Represents an ingested source code file."""
|
|
49
|
+
path: str
|
|
50
|
+
absolute_path: str
|
|
51
|
+
language: str
|
|
52
|
+
size_bytes: int
|
|
53
|
+
line_count: int
|
|
54
|
+
sha256: str
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def detect_language(path: Path) -> Optional[str]:
|
|
58
|
+
"""Detect language based on extension and filename."""
|
|
59
|
+
suffix = path.suffix.lower()
|
|
60
|
+
return EXTENSION_LANGUAGE_MAP.get(suffix)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Hashing utilities for TRACE repository ingestion."""
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def hash_file(file_path: Path) -> str:
|
|
8
|
+
"""Compute SHA-256 hash of a file."""
|
|
9
|
+
hasher = hashlib.sha256()
|
|
10
|
+
try:
|
|
11
|
+
with open(file_path, "rb") as f:
|
|
12
|
+
while chunk := f.read(65536):
|
|
13
|
+
hasher.update(chunk)
|
|
14
|
+
return hasher.hexdigest()
|
|
15
|
+
except Exception:
|
|
16
|
+
return ""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def hash_content(content: str) -> str:
|
|
20
|
+
"""Compute SHA-256 hash of text content."""
|
|
21
|
+
return hashlib.sha256(content.encode("utf-8", errors="replace")).hexdigest()
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Ignore pattern matching for repository ingestion."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Set
|
|
5
|
+
import fnmatch
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
DEFAULT_IGNORE_DIRS: Set[str] = {
|
|
9
|
+
".git",
|
|
10
|
+
".trace",
|
|
11
|
+
"node_modules",
|
|
12
|
+
".venv",
|
|
13
|
+
"venv",
|
|
14
|
+
"env",
|
|
15
|
+
"dist",
|
|
16
|
+
"build",
|
|
17
|
+
"coverage",
|
|
18
|
+
"__pycache__",
|
|
19
|
+
".next",
|
|
20
|
+
".nuxt",
|
|
21
|
+
".react-router",
|
|
22
|
+
".vercel",
|
|
23
|
+
".output",
|
|
24
|
+
".turbo",
|
|
25
|
+
".cache",
|
|
26
|
+
"target",
|
|
27
|
+
"vendor",
|
|
28
|
+
".idea",
|
|
29
|
+
".vscode",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
DEFAULT_IGNORE_PATTERNS: List[str] = [
|
|
33
|
+
"*.min.js",
|
|
34
|
+
"*.min.css",
|
|
35
|
+
"*.map",
|
|
36
|
+
"*.pyc",
|
|
37
|
+
"*.pyo",
|
|
38
|
+
"*.pyd",
|
|
39
|
+
"*.db",
|
|
40
|
+
"*.sqlite",
|
|
41
|
+
"*.sqlite3",
|
|
42
|
+
"*.log",
|
|
43
|
+
"*.png",
|
|
44
|
+
"*.jpg",
|
|
45
|
+
"*.jpeg",
|
|
46
|
+
"*.gif",
|
|
47
|
+
"*.ico",
|
|
48
|
+
"*.svg",
|
|
49
|
+
"*.woff",
|
|
50
|
+
"*.woff2",
|
|
51
|
+
"*.ttf",
|
|
52
|
+
"*.eot",
|
|
53
|
+
"*.zip",
|
|
54
|
+
"*.tar.gz",
|
|
55
|
+
"*.exe",
|
|
56
|
+
"*.bin",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class IgnoreFilter:
|
|
61
|
+
"""Filters files based on default ignore sets and custom .traceignore rules."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, repo_root: Path, custom_patterns: List[str] = None):
|
|
64
|
+
self.repo_root = repo_root.resolve()
|
|
65
|
+
self.patterns = list(DEFAULT_IGNORE_PATTERNS)
|
|
66
|
+
if custom_patterns:
|
|
67
|
+
self.patterns.extend(custom_patterns)
|
|
68
|
+
self._load_traceignore()
|
|
69
|
+
|
|
70
|
+
def _load_traceignore(self) -> None:
|
|
71
|
+
traceignore = self.repo_root / ".traceignore"
|
|
72
|
+
if traceignore.exists():
|
|
73
|
+
try:
|
|
74
|
+
for line in traceignore.read_text(encoding="utf-8").splitlines():
|
|
75
|
+
line = line.strip()
|
|
76
|
+
if line and not line.startswith("#"):
|
|
77
|
+
self.patterns.append(line.rstrip("/"))
|
|
78
|
+
except Exception:
|
|
79
|
+
pass
|
|
80
|
+
|
|
81
|
+
def should_ignore(self, path: Path) -> bool:
|
|
82
|
+
"""Return True if path should be ignored."""
|
|
83
|
+
try:
|
|
84
|
+
rel = path.relative_to(self.repo_root)
|
|
85
|
+
except ValueError:
|
|
86
|
+
rel = path
|
|
87
|
+
|
|
88
|
+
parts = rel.parts
|
|
89
|
+
# Check directory names
|
|
90
|
+
for part in parts[:-1]:
|
|
91
|
+
if part in DEFAULT_IGNORE_DIRS:
|
|
92
|
+
return True
|
|
93
|
+
|
|
94
|
+
if path.is_dir() and path.name in DEFAULT_IGNORE_DIRS:
|
|
95
|
+
return True
|
|
96
|
+
|
|
97
|
+
# Check glob patterns
|
|
98
|
+
filename = path.name
|
|
99
|
+
rel_str = str(rel).replace("\\", "/")
|
|
100
|
+
|
|
101
|
+
for pat in self.patterns:
|
|
102
|
+
clean_pat = pat.replace("\\", "/").rstrip("/")
|
|
103
|
+
if fnmatch.fnmatch(filename, clean_pat) or fnmatch.fnmatch(rel_str, clean_pat):
|
|
104
|
+
return True
|
|
105
|
+
if any(fnmatch.fnmatch(part, clean_pat) for part in parts):
|
|
106
|
+
return True
|
|
107
|
+
|
|
108
|
+
return False
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Repository scanner and ingestion manager."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import List, Dict, Optional
|
|
5
|
+
from trace_engine.ingest.ignore import IgnoreFilter
|
|
6
|
+
from trace_engine.ingest.files import SourceFile, detect_language
|
|
7
|
+
from trace_engine.ingest.hashing import hash_file
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class RepositoryScanner:
|
|
11
|
+
"""Discovers and catalogs source code files across a project directory."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, repo_path: Path):
|
|
14
|
+
self.repo_path = repo_path.resolve()
|
|
15
|
+
self.ignore_filter = IgnoreFilter(self.repo_path)
|
|
16
|
+
|
|
17
|
+
def scan(self) -> List[SourceFile]:
|
|
18
|
+
"""Scans the repository and returns discovered source files."""
|
|
19
|
+
source_files: List[SourceFile] = []
|
|
20
|
+
if not self.repo_path.exists():
|
|
21
|
+
return source_files
|
|
22
|
+
|
|
23
|
+
for item in self.repo_path.rglob("*"):
|
|
24
|
+
if item.is_file() and not self.ignore_filter.should_ignore(item):
|
|
25
|
+
lang = detect_language(item)
|
|
26
|
+
if lang is not None and lang != "unknown":
|
|
27
|
+
try:
|
|
28
|
+
stat = item.stat()
|
|
29
|
+
# Avoid huge files (> 2MB)
|
|
30
|
+
if stat.st_size > 2 * 1024 * 1024:
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
content = item.read_text(encoding="utf-8", errors="replace")
|
|
34
|
+
line_count = len(content.splitlines())
|
|
35
|
+
rel_path = str(item.relative_to(self.repo_path)).replace("\\", "/")
|
|
36
|
+
|
|
37
|
+
source_files.append(
|
|
38
|
+
SourceFile(
|
|
39
|
+
path=rel_path,
|
|
40
|
+
absolute_path=str(item.resolve()),
|
|
41
|
+
language=lang,
|
|
42
|
+
size_bytes=stat.st_size,
|
|
43
|
+
line_count=line_count,
|
|
44
|
+
sha256=hash_file(item),
|
|
45
|
+
)
|
|
46
|
+
)
|
|
47
|
+
except Exception:
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
return sorted(source_files, key=lambda f: f.path)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""TRACE intelligence layer: Laya System 1 + SecureBERT 2.0 + Orchestration."""
|
|
2
|
+
|
|
3
|
+
from trace_engine.intelligence.laya.router import LayaDecisionEngine
|
|
4
|
+
from trace_engine.intelligence.securebert.classifier import SecureBERTClassifier
|
|
5
|
+
from trace_engine.intelligence.orchestrator import IntelligenceOrchestrator, TestPlanRecommendation
|
|
6
|
+
from trace_engine.intelligence.tracebench import TRACEBenchRecorder, TRACEBenchRecord
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"LayaDecisionEngine",
|
|
10
|
+
"SecureBERTClassifier",
|
|
11
|
+
"IntelligenceOrchestrator",
|
|
12
|
+
"TestPlanRecommendation",
|
|
13
|
+
"TRACEBenchRecorder",
|
|
14
|
+
"TRACEBenchRecord",
|
|
15
|
+
]
|