torusguard 1.0.0 → 1.3.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/.torusguard/.manifest.json +89 -75
- package/.torusguard/references/csharp-security.md +41 -0
- package/.torusguard/references/go-security.md +41 -0
- package/.torusguard/references/java-security.md +40 -0
- package/.torusguard/references/polyglot-security-matrix.md +25 -0
- package/.torusguard/references/rust-security.md +40 -0
- package/.torusguard/rules/custom/.gitkeep +1 -0
- package/.torusguard/rules/custom/README.md +30 -0
- package/.torusguard/runs/report-latest.html +328 -0
- package/.torusguard/schemas/golden-recipe.schema.json +75 -0
- package/.torusguard/scripts/diff_guard.py +131 -4
- package/.torusguard/scripts/finding_scorer.py +33 -5
- package/.torusguard/scripts/html_reporter.py +628 -0
- package/.torusguard/scripts/manifest_builder.py +5 -5
- package/.torusguard/scripts/memory_engine.py +521 -99
- package/.torusguard/scripts/monorepo_detector.py +123 -10
- package/.torusguard/scripts/rules_sync.py +321 -0
- package/.torusguard/scripts/stack_detect.py +458 -26
- package/.torusguard/skills/torusguard/references/csharp-security.md +41 -0
- package/.torusguard/skills/torusguard/references/go-security.md +41 -0
- package/.torusguard/skills/torusguard/references/java-security.md +40 -0
- package/.torusguard/skills/torusguard/references/polyglot-security-matrix.md +25 -0
- package/.torusguard/skills/torusguard/references/rust-security.md +40 -0
- package/README.md +307 -410
- package/bin/torusguard.js +108 -4
- package/package.json +1 -1
- package/skills/torusguard/bootstrap.py +2 -2
- package/skills/torusguard/payload/.manifest.json +89 -75
- package/skills/torusguard/payload/references/csharp-security.md +41 -0
- package/skills/torusguard/payload/references/go-security.md +41 -0
- package/skills/torusguard/payload/references/java-security.md +40 -0
- package/skills/torusguard/payload/references/polyglot-security-matrix.md +25 -0
- package/skills/torusguard/payload/references/rust-security.md +40 -0
- package/skills/torusguard/payload/rules/custom/.gitkeep +1 -0
- package/skills/torusguard/payload/rules/custom/README.md +30 -0
- package/skills/torusguard/payload/schemas/golden-recipe.schema.json +75 -0
- package/skills/torusguard/payload/scripts/diff_guard.py +131 -4
- package/skills/torusguard/payload/scripts/finding_scorer.py +33 -5
- package/skills/torusguard/payload/scripts/html_reporter.py +628 -0
- package/skills/torusguard/payload/scripts/manifest_builder.py +5 -5
- package/skills/torusguard/payload/scripts/memory_engine.py +521 -99
- package/skills/torusguard/payload/scripts/monorepo_detector.py +123 -10
- package/skills/torusguard/payload/scripts/rules_sync.py +321 -0
- package/skills/torusguard/payload/scripts/stack_detect.py +458 -26
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +2 -2
- package/skills/torusguard/payload/skills/torusguard/references/csharp-security.md +41 -0
- package/skills/torusguard/payload/skills/torusguard/references/go-security.md +41 -0
- package/skills/torusguard/payload/skills/torusguard/references/java-security.md +40 -0
- package/skills/torusguard/payload/skills/torusguard/references/polyglot-security-matrix.md +25 -0
- package/skills/torusguard/payload/skills/torusguard/references/rust-security.md +40 -0
- package/.torusguard/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/finding_scorer.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/memory_engine.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
- package/skills/torusguard/__pycache__/bootstrap.cpython-311.pyc +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://torusguard.dev/schemas/golden-recipe.schema.json",
|
|
4
|
+
"title": "TorusGuard Golden Fix Recipe Schema",
|
|
5
|
+
"description": "Schema for reusable, verified AST remediation code snippets distilled from passing security patches.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": [
|
|
8
|
+
"recipe_id",
|
|
9
|
+
"rule_id",
|
|
10
|
+
"file_type",
|
|
11
|
+
"before_snippet",
|
|
12
|
+
"after_snippet",
|
|
13
|
+
"diff_snippet",
|
|
14
|
+
"ponytail_metrics",
|
|
15
|
+
"verified_count",
|
|
16
|
+
"last_verified"
|
|
17
|
+
],
|
|
18
|
+
"properties": {
|
|
19
|
+
"recipe_id": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"pattern": "^recipe-[A-Za-z0-9_-]+$"
|
|
22
|
+
},
|
|
23
|
+
"rule_id": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"pattern": "^TG-[A-Z]+-[0-9]{3}$"
|
|
26
|
+
},
|
|
27
|
+
"file_type": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "File extension or language identifier (e.g., .py, .ts, .js)"
|
|
30
|
+
},
|
|
31
|
+
"framework": {
|
|
32
|
+
"type": ["string", "null"],
|
|
33
|
+
"description": "Target web framework (e.g., Django, FastAPI, Next.js)"
|
|
34
|
+
},
|
|
35
|
+
"description": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Human-readable summary of the fix strategy"
|
|
38
|
+
},
|
|
39
|
+
"before_snippet": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Vulnerable code pattern before remediation"
|
|
42
|
+
},
|
|
43
|
+
"after_snippet": {
|
|
44
|
+
"type": "string",
|
|
45
|
+
"description": "Remediated code pattern adhering to Ponytail bounds"
|
|
46
|
+
},
|
|
47
|
+
"diff_snippet": {
|
|
48
|
+
"type": "string",
|
|
49
|
+
"description": "Unified diff snippet representing the surgical fix"
|
|
50
|
+
},
|
|
51
|
+
"ponytail_metrics": {
|
|
52
|
+
"type": "object",
|
|
53
|
+
"required": ["additions", "deletions"],
|
|
54
|
+
"properties": {
|
|
55
|
+
"additions": {
|
|
56
|
+
"type": "integer",
|
|
57
|
+
"maximum": 35
|
|
58
|
+
},
|
|
59
|
+
"deletions": {
|
|
60
|
+
"type": "integer",
|
|
61
|
+
"maximum": 25
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"verified_count": {
|
|
66
|
+
"type": "integer",
|
|
67
|
+
"minimum": 1
|
|
68
|
+
},
|
|
69
|
+
"last_verified": {
|
|
70
|
+
"type": "string",
|
|
71
|
+
"format": "date-time"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
"additionalProperties": false
|
|
75
|
+
}
|
|
@@ -15,12 +15,39 @@ from pathlib import Path
|
|
|
15
15
|
from typing import Dict, List, Any, Optional
|
|
16
16
|
|
|
17
17
|
BYPASS_PATTERNS = [
|
|
18
|
-
(re.compile(r'(
|
|
18
|
+
(re.compile(r'(?:#|//|/\*)\s*(?:TODO\s*:?\s*)?(?:bypass|skip|disable)[_\s]*(?:auth|security|check|guard|filter|csrf|tenant)', re.IGNORECASE), "Suspicious authentication or security check bypass comment"),
|
|
19
|
+
# TLS / Certificate validation bypasses across Python, Go, Node, Java, C#, PHP, Rust
|
|
19
20
|
(re.compile(r'\bverify\s*=\s*False\b'), "Disabled TLS certificate verification (verify=False)"),
|
|
21
|
+
(re.compile(r'\bInsecureSkipVerify\s*:\s*true\b'), "Disabled Go TLS certificate verification (InsecureSkipVerify: true)"),
|
|
22
|
+
(re.compile(r'\bNODE_TLS_REJECT_UNAUTHORIZED\s*=\s*[\'"]?0[\'"]?'), "Disabled Node.js TLS verification (NODE_TLS_REJECT_UNAUTHORIZED=0)"),
|
|
23
|
+
(re.compile(r'\brejectUnauthorized\s*:\s*false\b'), "Disabled Node.js TLS rejectUnauthorized"),
|
|
24
|
+
(re.compile(r'\b(?:TrustAllStrategy|NoopHostnameVerifier|DangerousAcceptAnyServerCertificateValidator)\b'), "Disabled Java / C# TLS certificate validation"),
|
|
25
|
+
(re.compile(r'\bServerCertificateCustomValidationCallback\s*=\s*(?:\([^)]*\)\s*=>\s*true|HttpClientHandler\.DangerousAcceptAnyServerCertificateValidator)'), "Disabled C# TLS certificate callback"),
|
|
26
|
+
(re.compile(r'\bCURLOPT_SSL_VERIFYPEER\s*(?:=>|,)\s*(?:false|0)\b', re.IGNORECASE), "Disabled PHP curl SSL verification (CURLOPT_SSL_VERIFYPEER => false)"),
|
|
27
|
+
(re.compile(r'\bdanger_accept_invalid_certs\s*\(\s*true\s*\)'), "Disabled Rust reqwest TLS certificate validation"),
|
|
28
|
+
# Auth & CSRF bypasses
|
|
20
29
|
(re.compile(r'\b(?:skip_auth|bypass_auth|disable_auth|allow_all|permit_all)\s*=\s*True\b', re.IGNORECASE), "Explicit security bypass flag enabled"),
|
|
21
30
|
(re.compile(r'@(?:csrf_exempt|allow_anonymous|disable_token_check)\b'), "Decorator disabling route-level security or CSRF protection"),
|
|
31
|
+
(re.compile(r'\[(?:AllowAnonymous|Authorize\s*\(\s*Roles\s*=\s*"\*"\s*\))\]'), "Attribute disabling ASP.NET authentication or authorization"),
|
|
32
|
+
(re.compile(r'\.csrf\(\)\.disable\(\)|\.csrf\([^)]*AbstractHttpConfigurer::disable\)'), "Spring Security CSRF protection explicitly disabled"),
|
|
33
|
+
# CORS wildcard with credentials
|
|
22
34
|
(re.compile(r'\bCORS_ALLOW_ALL_ORIGINS\s*=\s*True\b'), "Wildcard CORS origin enabled in patch"),
|
|
23
|
-
(re.compile(r'(
|
|
35
|
+
(re.compile(r'@CrossOrigin\s*\(\s*(?:origins\s*=\s*)?["\']\*["\']'), "Wildcard CORS origin annotation in Java/Spring"),
|
|
36
|
+
# Native dangerous constructs
|
|
37
|
+
(re.compile(r'\bextract\s*\(\s*\$_(?:POST|GET|REQUEST)\s*\)'), "Dangerous PHP extract() on untrusted user input"),
|
|
38
|
+
(re.compile(r'\bunsafe\s*\{'), "Unvetted Rust unsafe block introduced in patch"),
|
|
39
|
+
# Polyglot additional security bypasses (Python, Ruby, Java, Kotlin, Elixir, Dart, C++)
|
|
40
|
+
(re.compile(r'\bSESSION_COOKIE_SECURE\s*=\s*False\b'), "Session cookie secure flag disabled"),
|
|
41
|
+
(re.compile(r'\bparams\.permit!'), "Rails strong parameters mass assignment bypass (params.permit!)"),
|
|
42
|
+
(re.compile(r'\b(?:csrf(?:\(\))?\.disable\(\)|\.csrf\(\)\.disable\(\)|\.csrf\([^)]*AbstractHttpConfigurer::disable\))'), "CSRF protection explicitly disabled"),
|
|
43
|
+
(re.compile(r'\b(?:verify_peer\s*:\s*false|verify\s*:\s*:verify_none)\b'), "Disabled Elixir TLS certificate verification"),
|
|
44
|
+
(re.compile(r'\bbadCertificateCallback\s*='), "Disabled Dart/Flutter badCertificateCallback validation"),
|
|
45
|
+
(re.compile(r'\bSSL_VERIFY_NONE\b'), "Disabled C/C++ OpenSSL certificate verification (SSL_VERIFY_NONE)"),
|
|
46
|
+
(re.compile(r'\bpermitAll\(\)'), "Unrestricted access granted via permitAll()"),
|
|
47
|
+
(re.compile(r'\bverify\s*(?:=>|=)\s*false\b', re.IGNORECASE), "Disabled TLS/HTTP client verification (verify => false)"),
|
|
48
|
+
# Linters and scanners suppressions
|
|
49
|
+
(re.compile(r'(?:#|//|/\*)\s*nosec\b', re.IGNORECASE), "Suppression comment (nosec) hiding potential vulnerability"),
|
|
50
|
+
(re.compile(r'@SuppressWarnings\s*\(\s*["\'](?:security|all)["\']\s*\)'), "Java security suppression annotation")
|
|
24
51
|
]
|
|
25
52
|
|
|
26
53
|
SECRET_PATTERNS = [
|
|
@@ -35,7 +62,10 @@ TENANT_FILTER_PATTERNS = [
|
|
|
35
62
|
re.compile(r'\.filter\([^)]*\btenant\s*='),
|
|
36
63
|
re.compile(r'\.filter_by\([^)]*\btenant\s*='),
|
|
37
64
|
re.compile(r'\bwhere\s+tenant_id\s*='),
|
|
38
|
-
re.compile(r'\btenant_id\s*==?')
|
|
65
|
+
re.compile(r'\btenant_id\s*==?'),
|
|
66
|
+
re.compile(r'\btenantId\s*:'),
|
|
67
|
+
re.compile(r'\.Where\([^)]*(?:tenant_id|TenantId)'),
|
|
68
|
+
re.compile(r'\bwhere\s*:\s*\{[^}]*tenant')
|
|
39
69
|
]
|
|
40
70
|
|
|
41
71
|
|
|
@@ -198,14 +228,110 @@ def audit_diff_file(file_path: str, check_memory: bool = False, root_dir: Option
|
|
|
198
228
|
return audit_diff(content, check_memory=check_memory, root_dir=root_dir)
|
|
199
229
|
|
|
200
230
|
|
|
231
|
+
def check_diff_content(diff_content: str, file_path: str = "") -> List[Dict[str, Any]]:
|
|
232
|
+
"""Convenience helper to audit diff string directly and return list of violations."""
|
|
233
|
+
res = audit_diff(diff_content)
|
|
234
|
+
return res.get("violations", [])
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def install_pre_commit_hook(root_dir: Optional[Path] = None) -> bool:
|
|
238
|
+
"""Install TorusGuard pre-commit security hook into .git/hooks/pre-commit."""
|
|
239
|
+
base = Path(root_dir or Path.cwd()).resolve()
|
|
240
|
+
git_dir = base / ".git"
|
|
241
|
+
if not git_dir.exists():
|
|
242
|
+
for parent in base.parents:
|
|
243
|
+
if (parent / ".git").exists():
|
|
244
|
+
git_dir = parent / ".git"
|
|
245
|
+
break
|
|
246
|
+
if not git_dir.exists():
|
|
247
|
+
print("[ERROR] Diff Guard: Not a git repository (no .git directory found).", file=sys.stderr)
|
|
248
|
+
return False
|
|
249
|
+
|
|
250
|
+
hooks_dir = git_dir / "hooks"
|
|
251
|
+
hooks_dir.mkdir(parents=True, exist_ok=True)
|
|
252
|
+
hook_file = hooks_dir / "pre-commit"
|
|
253
|
+
|
|
254
|
+
hook_script = """#!/usr/bin/env sh
|
|
255
|
+
# TorusGuard Pre-Commit Security Diff Guard
|
|
256
|
+
# Automatically blocks commits containing security bypasses, secret leaks, or tenant stripping.
|
|
257
|
+
python .torusguard/scripts/diff_guard.py --pre-commit
|
|
258
|
+
"""
|
|
259
|
+
try:
|
|
260
|
+
hook_file.write_text(hook_script, encoding="utf-8")
|
|
261
|
+
if os.name != "nt":
|
|
262
|
+
import stat
|
|
263
|
+
os.chmod(hook_file, hook_file.stat().st_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH)
|
|
264
|
+
print(f"[SUCCESS] Installed TorusGuard pre-commit hook at: {hook_file}")
|
|
265
|
+
return True
|
|
266
|
+
except Exception as e:
|
|
267
|
+
print(f"[ERROR] Diff Guard: Failed to install pre-commit hook: {e}", file=sys.stderr)
|
|
268
|
+
return False
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def uninstall_pre_commit_hook(root_dir: Optional[Path] = None) -> bool:
|
|
272
|
+
"""Uninstall TorusGuard pre-commit hook from .git/hooks/pre-commit."""
|
|
273
|
+
base = Path(root_dir or Path.cwd()).resolve()
|
|
274
|
+
git_dir = base / ".git"
|
|
275
|
+
if not git_dir.exists():
|
|
276
|
+
for parent in base.parents:
|
|
277
|
+
if (parent / ".git").exists():
|
|
278
|
+
git_dir = parent / ".git"
|
|
279
|
+
break
|
|
280
|
+
if not git_dir.exists():
|
|
281
|
+
print("[WARN] Diff Guard: No .git directory found.", file=sys.stderr)
|
|
282
|
+
return False
|
|
283
|
+
|
|
284
|
+
hook_file = git_dir / "hooks" / "pre-commit"
|
|
285
|
+
if hook_file.exists():
|
|
286
|
+
try:
|
|
287
|
+
hook_file.unlink()
|
|
288
|
+
print("[SUCCESS] Uninstalled TorusGuard pre-commit hook.")
|
|
289
|
+
return True
|
|
290
|
+
except Exception as e:
|
|
291
|
+
print(f"[ERROR] Diff Guard: Failed to delete hook file: {e}", file=sys.stderr)
|
|
292
|
+
return False
|
|
293
|
+
else:
|
|
294
|
+
print("[INFO] Diff Guard: No pre-commit hook currently installed.")
|
|
295
|
+
return True
|
|
296
|
+
|
|
297
|
+
|
|
201
298
|
def main():
|
|
202
299
|
parser = argparse.ArgumentParser(description="TorusGuard Unified Diff Security Scanner")
|
|
203
300
|
parser.add_argument("diff_path", nargs="?", help="Path to unified diff file (or read stdin if omitted)")
|
|
204
301
|
parser.add_argument("--check-memory", action="store_true", help="Check additions against persistent memory regression watch patterns")
|
|
302
|
+
parser.add_argument("--pre-commit", action="store_true", help="Audit git staged diff (--cached) for pre-commit hook")
|
|
303
|
+
parser.add_argument("--install-hook", action="store_true", help="Install TorusGuard pre-commit hook into .git/hooks/pre-commit")
|
|
304
|
+
parser.add_argument("--uninstall-hook", action="store_true", help="Uninstall TorusGuard pre-commit hook")
|
|
205
305
|
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
|
206
306
|
args = parser.parse_args()
|
|
207
307
|
|
|
208
|
-
if args.
|
|
308
|
+
if args.install_hook:
|
|
309
|
+
sys.exit(0 if install_pre_commit_hook() else 1)
|
|
310
|
+
|
|
311
|
+
if args.uninstall_hook:
|
|
312
|
+
sys.exit(0 if uninstall_pre_commit_hook() else 1)
|
|
313
|
+
|
|
314
|
+
if args.pre_commit:
|
|
315
|
+
import subprocess
|
|
316
|
+
try:
|
|
317
|
+
res_git = subprocess.run(
|
|
318
|
+
["git", "diff", "--cached"],
|
|
319
|
+
capture_output=True,
|
|
320
|
+
text=True,
|
|
321
|
+
encoding="utf-8",
|
|
322
|
+
errors="replace"
|
|
323
|
+
)
|
|
324
|
+
diff_text = res_git.stdout
|
|
325
|
+
except Exception as e:
|
|
326
|
+
print(f"[ERROR] Diff Guard: Could not read git staged diff: {e}", file=sys.stderr)
|
|
327
|
+
sys.exit(1)
|
|
328
|
+
|
|
329
|
+
if not diff_text.strip():
|
|
330
|
+
print("[PASS] Diff Guard (pre-commit): No staged changes detected.")
|
|
331
|
+
sys.exit(0)
|
|
332
|
+
|
|
333
|
+
res = audit_diff(diff_text, check_memory=True)
|
|
334
|
+
elif args.diff_path:
|
|
209
335
|
res = audit_diff_file(args.diff_path, check_memory=args.check_memory)
|
|
210
336
|
else:
|
|
211
337
|
if sys.stdin.isatty():
|
|
@@ -229,3 +355,4 @@ def main():
|
|
|
229
355
|
|
|
230
356
|
if __name__ == "__main__":
|
|
231
357
|
main()
|
|
358
|
+
|
|
@@ -44,11 +44,13 @@ def compute_memory_boost(
|
|
|
44
44
|
try:
|
|
45
45
|
with open(patterns_file, "r", encoding="utf-8") as f:
|
|
46
46
|
patterns = json.load(f)
|
|
47
|
-
except
|
|
47
|
+
except (json.JSONDecodeError, OSError, UnicodeDecodeError):
|
|
48
48
|
return 0
|
|
49
49
|
|
|
50
50
|
boost = 0
|
|
51
51
|
matched_file = False
|
|
52
|
+
matched_dir = False
|
|
53
|
+
target_path = Path(file_path) if file_path else None
|
|
52
54
|
|
|
53
55
|
for pat in patterns:
|
|
54
56
|
if pat.get("rule_id") == rule_id:
|
|
@@ -62,18 +64,37 @@ def compute_memory_boost(
|
|
|
62
64
|
elif ptype == "security_idiom":
|
|
63
65
|
boost += 5
|
|
64
66
|
|
|
65
|
-
# File-specific correlation bonus
|
|
66
|
-
|
|
67
|
+
# File-specific and directory-level correlation bonus
|
|
68
|
+
affected = pat.get("affected_files", [])
|
|
69
|
+
if file_path and file_path in affected:
|
|
67
70
|
matched_file = True
|
|
71
|
+
elif target_path and any(target_path.parent == Path(af).parent for af in affected if af):
|
|
72
|
+
matched_dir = True
|
|
68
73
|
|
|
69
74
|
if matched_file and boost > 0:
|
|
70
75
|
boost += 5
|
|
71
76
|
elif matched_file and boost < 0:
|
|
72
77
|
boost -= 10
|
|
78
|
+
elif matched_dir and boost > 0:
|
|
79
|
+
boost += 2
|
|
80
|
+
elif matched_dir and boost < 0:
|
|
81
|
+
boost -= 4
|
|
73
82
|
|
|
74
83
|
return boost
|
|
75
84
|
|
|
76
85
|
|
|
86
|
+
def is_test_path(file_path: Optional[str]) -> bool:
|
|
87
|
+
"""Detects whether a file path belongs to a test suite, fixture, or mock."""
|
|
88
|
+
if not file_path:
|
|
89
|
+
return False
|
|
90
|
+
clean = file_path.replace("\\", "/").lower()
|
|
91
|
+
test_markers = [
|
|
92
|
+
"/test/", "/tests/", "/spec/", "/specs/", "/fixtures/", "/mock/", "/mocks/",
|
|
93
|
+
"/e2e/", "__tests__", "test_", "_test.", ".test.", ".spec.", "testcase"
|
|
94
|
+
]
|
|
95
|
+
return any(marker in clean for marker in test_markers) or clean.endswith(("_test.go", "test.java", "test.py", ".spec.ts", ".test.ts", ".spec.js", ".test.js"))
|
|
96
|
+
|
|
97
|
+
|
|
77
98
|
def compute_confidence_score(
|
|
78
99
|
evidence_quality: int = 35,
|
|
79
100
|
reproduction_success: int = 0,
|
|
@@ -84,7 +105,7 @@ def compute_confidence_score(
|
|
|
84
105
|
rule_id: Optional[str] = None,
|
|
85
106
|
file_path: Optional[str] = None,
|
|
86
107
|
root_dir: Optional[Path] = None
|
|
87
|
-
) -> Tuple[int, str, Dict[str,
|
|
108
|
+
) -> Tuple[int, str, Dict[str, Any]]:
|
|
88
109
|
"""
|
|
89
110
|
Computes total score and assigns confidence band.
|
|
90
111
|
Max points:
|
|
@@ -94,6 +115,7 @@ def compute_confidence_score(
|
|
|
94
115
|
- environmental_clarity: 15
|
|
95
116
|
- manual_review_status: 10
|
|
96
117
|
- memory_boost: -30 to +20 (modifier from persistent memory)
|
|
118
|
+
- test_deduction: -30 if file is located in a test/mock path
|
|
97
119
|
Total is clamped to [0, 100].
|
|
98
120
|
"""
|
|
99
121
|
eq = min(max(evidence_quality, 0), 35)
|
|
@@ -107,7 +129,11 @@ def compute_confidence_score(
|
|
|
107
129
|
if rule_id and eff_mem_boost == 0:
|
|
108
130
|
eff_mem_boost = compute_memory_boost(rule_id, file_path=file_path, root_dir=root_dir)
|
|
109
131
|
|
|
110
|
-
|
|
132
|
+
# Test path noise suppression: -30 deduction for test mocks/fixtures
|
|
133
|
+
is_test = is_test_path(file_path)
|
|
134
|
+
test_deduction = -30 if is_test else 0
|
|
135
|
+
|
|
136
|
+
raw_total = eq + rs + ic + ec + mr + eff_mem_boost + test_deduction
|
|
111
137
|
total = min(max(raw_total, 0), 100)
|
|
112
138
|
|
|
113
139
|
if total >= 90:
|
|
@@ -126,6 +152,8 @@ def compute_confidence_score(
|
|
|
126
152
|
"environmental_clarity": ec,
|
|
127
153
|
"manual_review_status": mr,
|
|
128
154
|
"memory_boost": eff_mem_boost,
|
|
155
|
+
"test_exemption": is_test,
|
|
156
|
+
"test_deduction": test_deduction,
|
|
129
157
|
"total_score": total,
|
|
130
158
|
"classification_band": band
|
|
131
159
|
}
|