torusguard 1.3.2 → 1.3.4
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/runs/report-latest.html +3 -3
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-115-9a059e/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-119-61557c/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-265-996ca3/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-503-d11cf8/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-db-004-79-91f41f/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-107-516a40/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-128-7809a1/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-002-256-c8a22e/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-input-003-53-d167b4/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-platform-001-183-47fd5e/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-209-4f6424/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-257-89bf34/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-341-ce228c/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-355-7b190b/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-64-5af31a/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/metadata.json +15 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/minimal_patch_plan.md +21 -0
- package/.torusguard/runs/run-20260910-120238-audit/bundles/bnd-tg-sec-001-9-f51467/patch.diff +9 -0
- package/.torusguard/runs/run-20260910-120238-audit/findings.json +1520 -0
- package/.torusguard/runs/run-20260910-120238-audit/findings.md +560 -0
- package/.torusguard/runs/run-20260910-120238-audit/manifest.json +13 -0
- package/.torusguard/runs/run-20260910-120238-audit/recheck.md +86 -0
- package/.torusguard/runs/run-20260910-120238-audit/remediation.md +262 -0
- package/.torusguard/runs/run-20260910-120238-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260910-121238-audit/findings.json +794 -0
- package/.torusguard/runs/run-20260910-121238-audit/findings.md +344 -0
- package/.torusguard/runs/run-20260910-121238-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260910-121238-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260910-130454-audit/findings.json +893 -0
- package/.torusguard/runs/run-20260910-130454-audit/findings.md +386 -0
- package/.torusguard/runs/run-20260910-130454-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260910-130454-audit/summary.md +5 -0
- package/.torusguard/runs/run-20260910-130519-audit/findings.json +563 -0
- package/.torusguard/runs/run-20260910-130519-audit/findings.md +246 -0
- package/.torusguard/runs/run-20260910-130519-audit/manifest.json +12 -0
- package/.torusguard/runs/run-20260910-130519-audit/summary.md +5 -0
- package/.torusguard/scripts/__pycache__/audit_runner.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/html_reporter.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/rules_sync.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/stack_detect.cpython-311.pyc +0 -0
- package/.torusguard/scripts/__pycache__/term_ui.cpython-311.pyc +0 -0
- package/.torusguard/scripts/apply_runner.py +138 -100
- package/.torusguard/scripts/audit_runner.py +219 -116
- package/.torusguard/scripts/harden_runner.py +144 -64
- package/.torusguard/scripts/html_reporter.py +8 -3
- package/.torusguard/scripts/recheck_runner.py +80 -63
- package/.torusguard/scripts/recipes_runner.py +38 -31
- package/.torusguard/scripts/stack_detect.py +608 -584
- package/.torusguard/scripts/term_ui.py +162 -0
- package/.torusguard/skills/torusguard/SKILL.md +61 -31
- package/.torusguard/skills/torusguard-apply/SKILL.md +80 -46
- package/.torusguard/skills/torusguard-audit/SKILL.md +83 -70
- package/.torusguard/skills/torusguard-authorize/SKILL.md +5 -4
- package/.torusguard/skills/torusguard-exploit-check/SKILL.md +6 -5
- package/.torusguard/skills/torusguard-full/SKILL.md +1 -1
- package/.torusguard/skills/torusguard-harden/SKILL.md +94 -63
- package/.torusguard/skills/torusguard-init/SKILL.md +64 -52
- package/.torusguard/skills/torusguard-recheck/SKILL.md +77 -53
- package/.torusguard/skills/torusguard-report/SKILL.md +72 -54
- package/.torusguard/skills/torusguard-status/SKILL.md +58 -42
- package/.torusguard/skills/torusguard-verify/SKILL.md +8 -7
- package/.torusguard/skills/torusguard-web-validate/SKILL.md +8 -7
- package/README.md +4 -3
- package/bin/torusguard.js +544 -437
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +61 -31
- package/skills/torusguard/payload/scripts/apply_runner.py +138 -100
- package/skills/torusguard/payload/scripts/audit_runner.py +219 -116
- package/skills/torusguard/payload/scripts/harden_runner.py +144 -64
- package/skills/torusguard/payload/scripts/html_reporter.py +8 -3
- package/skills/torusguard/payload/scripts/recheck_runner.py +80 -63
- package/skills/torusguard/payload/scripts/recipes_runner.py +38 -31
- package/skills/torusguard/payload/scripts/stack_detect.py +608 -584
- package/skills/torusguard/payload/scripts/term_ui.py +162 -0
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +61 -31
- package/skills/torusguard/payload/skills/torusguard-apply/SKILL.md +80 -46
- package/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +83 -70
- package/skills/torusguard/payload/skills/torusguard-authorize/SKILL.md +5 -4
- package/skills/torusguard/payload/skills/torusguard-exploit-check/SKILL.md +6 -5
- package/skills/torusguard/payload/skills/torusguard-full/SKILL.md +1 -1
- package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +94 -63
- package/skills/torusguard/payload/skills/torusguard-init/SKILL.md +64 -52
- package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +77 -53
- package/skills/torusguard/payload/skills/torusguard-report/SKILL.md +72 -54
- package/skills/torusguard/payload/skills/torusguard-status/SKILL.md +58 -42
- package/skills/torusguard/payload/skills/torusguard-verify/SKILL.md +8 -7
- package/skills/torusguard/payload/skills/torusguard-web-validate/SKILL.md +8 -7
- package/skills/torusguard-apply/SKILL.md +80 -46
- package/skills/torusguard-audit/SKILL.md +83 -70
- package/skills/torusguard-authorize/SKILL.md +5 -4
- package/skills/torusguard-exploit-check/SKILL.md +6 -5
- package/skills/torusguard-full/SKILL.md +1 -1
- package/skills/torusguard-harden/SKILL.md +94 -63
- package/skills/torusguard-init/SKILL.md +64 -52
- package/skills/torusguard-recheck/SKILL.md +77 -53
- package/skills/torusguard-report/SKILL.md +72 -54
- package/skills/torusguard-status/SKILL.md +58 -42
- package/skills/torusguard-verify/SKILL.md +8 -7
- package/skills/torusguard-web-validate/SKILL.md +8 -7
|
@@ -1,584 +1,608 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
TorusGuard Universal Polyglot Stack Detector (v1.3.0)
|
|
4
|
-
Inspects project directory to detect programming language, framework, ORM/data layer,
|
|
5
|
-
and dependency manifests across 16+ languages, 30+ frameworks, and 20+ data layers.
|
|
6
|
-
Zero external dependencies (100% Python 3.10+ standard library).
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
import sys
|
|
10
|
-
import json
|
|
11
|
-
import argparse
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
from typing import Any
|
|
14
|
-
|
|
15
|
-
# Windows console UTF-8 support
|
|
16
|
-
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
|
-
try:
|
|
18
|
-
getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
|
|
19
|
-
except (AttributeError, OSError):
|
|
20
|
-
pass
|
|
21
|
-
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
|
-
try:
|
|
23
|
-
getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
|
|
24
|
-
except (AttributeError, OSError):
|
|
25
|
-
pass
|
|
26
|
-
|
|
27
|
-
# Comprehensive Manifest Catalog across 16+ Language Ecosystems
|
|
28
|
-
MANIFEST_CATALOG: dict[str, str] = {
|
|
29
|
-
# Python
|
|
30
|
-
"pyproject.toml": "Python",
|
|
31
|
-
"requirements.txt": "Python",
|
|
32
|
-
"Pipfile": "Python",
|
|
33
|
-
"poetry.lock": "Python",
|
|
34
|
-
"manage.py": "Python",
|
|
35
|
-
"setup.py": "Python",
|
|
36
|
-
# JavaScript / TypeScript
|
|
37
|
-
"package.json": "JavaScript / TypeScript",
|
|
38
|
-
"pnpm-lock.yaml": "JavaScript / TypeScript",
|
|
39
|
-
"yarn.lock": "JavaScript / TypeScript",
|
|
40
|
-
"bun.lockb": "JavaScript / TypeScript",
|
|
41
|
-
"deno.json": "JavaScript / TypeScript",
|
|
42
|
-
# Go
|
|
43
|
-
"go.mod": "Go",
|
|
44
|
-
"go.sum": "Go",
|
|
45
|
-
# Rust
|
|
46
|
-
"Cargo.toml": "Rust",
|
|
47
|
-
"Cargo.lock": "Rust",
|
|
48
|
-
# Java / Kotlin
|
|
49
|
-
"pom.xml": "Java",
|
|
50
|
-
"build.gradle": "Java / Kotlin",
|
|
51
|
-
"build.gradle.kts": "Kotlin",
|
|
52
|
-
"gradlew": "Java / Kotlin",
|
|
53
|
-
# C# / .NET
|
|
54
|
-
"global.json": "C# / .NET",
|
|
55
|
-
# PHP
|
|
56
|
-
"composer.json": "PHP",
|
|
57
|
-
"composer.lock": "PHP",
|
|
58
|
-
# Ruby
|
|
59
|
-
"Gemfile": "Ruby",
|
|
60
|
-
"Gemfile.lock": "Ruby",
|
|
61
|
-
# Elixir
|
|
62
|
-
"mix.exs": "Elixir",
|
|
63
|
-
"mix.lock": "Elixir",
|
|
64
|
-
# Dart / Flutter
|
|
65
|
-
"pubspec.yaml": "Dart / Flutter",
|
|
66
|
-
"pubspec.lock": "Dart / Flutter",
|
|
67
|
-
# C / C++
|
|
68
|
-
"CMakeLists.txt": "C / C++",
|
|
69
|
-
"Makefile": "C / C++",
|
|
70
|
-
"meson.build": "C / C++",
|
|
71
|
-
# Scala
|
|
72
|
-
"build.sbt": "Scala",
|
|
73
|
-
# Swift
|
|
74
|
-
"Package.swift": "Swift"
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
# Extension mappings for fallback file census
|
|
78
|
-
EXTENSION_MAP: dict[str, str] = {
|
|
79
|
-
".py": "Python",
|
|
80
|
-
".ts": "TypeScript",
|
|
81
|
-
".tsx": "TypeScript",
|
|
82
|
-
".js": "JavaScript",
|
|
83
|
-
".jsx": "JavaScript",
|
|
84
|
-
".go": "Go",
|
|
85
|
-
".rs": "Rust",
|
|
86
|
-
".java": "Java",
|
|
87
|
-
".cs": "C#",
|
|
88
|
-
".php": "PHP",
|
|
89
|
-
".rb": "Ruby",
|
|
90
|
-
".kt": "Kotlin",
|
|
91
|
-
".kts": "Kotlin",
|
|
92
|
-
".swift": "Swift",
|
|
93
|
-
".dart": "Dart",
|
|
94
|
-
".ex": "Elixir",
|
|
95
|
-
".exs": "Elixir",
|
|
96
|
-
".scala": "Scala",
|
|
97
|
-
".c": "C",
|
|
98
|
-
".cpp": "C++",
|
|
99
|
-
".cc": "C++",
|
|
100
|
-
".h": "C / C++",
|
|
101
|
-
".hpp": "C++",
|
|
102
|
-
".sh": "Shell / Bash",
|
|
103
|
-
".sql": "SQL"
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def census_file_extensions(project_root: Path) -> dict[str, int]:
|
|
108
|
-
"""Tally source file extensions to infer primary languages when no manifest is present."""
|
|
109
|
-
census: dict[str, int] = {}
|
|
110
|
-
ignored_dirs = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build", ".torusguard", "target"}
|
|
111
|
-
|
|
112
|
-
for p in project_root.rglob("*"):
|
|
113
|
-
if p.is_file() and not any(part in ignored_dirs for part in p.parts):
|
|
114
|
-
ext = p.suffix.lower()
|
|
115
|
-
if ext in EXTENSION_MAP:
|
|
116
|
-
lang = EXTENSION_MAP[ext]
|
|
117
|
-
census[lang] = census.get(lang, 0) + 1
|
|
118
|
-
return census
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if
|
|
154
|
-
profile["dependency_files"].append(
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
profile
|
|
177
|
-
profile
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
profile
|
|
206
|
-
|
|
207
|
-
profile
|
|
208
|
-
profile["
|
|
209
|
-
profile["
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
profile
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
profile["
|
|
219
|
-
profile["
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
profile
|
|
263
|
-
profile["
|
|
264
|
-
|
|
265
|
-
profile["
|
|
266
|
-
profile["
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
profile["
|
|
270
|
-
profile["
|
|
271
|
-
profile["
|
|
272
|
-
elif "
|
|
273
|
-
profile
|
|
274
|
-
profile["confidence"] = "Confirmed"
|
|
275
|
-
profile["detection_evidence"].append({"file": "package.json", "indicator": "
|
|
276
|
-
elif "
|
|
277
|
-
profile
|
|
278
|
-
profile["confidence"] = "Confirmed"
|
|
279
|
-
profile["detection_evidence"].append({"file": "package.json", "indicator": "
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
profile["
|
|
283
|
-
profile["
|
|
284
|
-
|
|
285
|
-
profile
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
profile["
|
|
292
|
-
|
|
293
|
-
profile["
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
profile
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
profile
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
TorusGuard Universal Polyglot Stack Detector (v1.3.0)
|
|
4
|
+
Inspects project directory to detect programming language, framework, ORM/data layer,
|
|
5
|
+
and dependency manifests across 16+ languages, 30+ frameworks, and 20+ data layers.
|
|
6
|
+
Zero external dependencies (100% Python 3.10+ standard library).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import sys
|
|
10
|
+
import json
|
|
11
|
+
import argparse
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
# Windows console UTF-8 support
|
|
16
|
+
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
|
+
try:
|
|
18
|
+
getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
|
|
19
|
+
except (AttributeError, OSError):
|
|
20
|
+
pass
|
|
21
|
+
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
|
+
try:
|
|
23
|
+
getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
|
|
24
|
+
except (AttributeError, OSError):
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
# Comprehensive Manifest Catalog across 16+ Language Ecosystems
|
|
28
|
+
MANIFEST_CATALOG: dict[str, str] = {
|
|
29
|
+
# Python
|
|
30
|
+
"pyproject.toml": "Python",
|
|
31
|
+
"requirements.txt": "Python",
|
|
32
|
+
"Pipfile": "Python",
|
|
33
|
+
"poetry.lock": "Python",
|
|
34
|
+
"manage.py": "Python",
|
|
35
|
+
"setup.py": "Python",
|
|
36
|
+
# JavaScript / TypeScript
|
|
37
|
+
"package.json": "JavaScript / TypeScript",
|
|
38
|
+
"pnpm-lock.yaml": "JavaScript / TypeScript",
|
|
39
|
+
"yarn.lock": "JavaScript / TypeScript",
|
|
40
|
+
"bun.lockb": "JavaScript / TypeScript",
|
|
41
|
+
"deno.json": "JavaScript / TypeScript",
|
|
42
|
+
# Go
|
|
43
|
+
"go.mod": "Go",
|
|
44
|
+
"go.sum": "Go",
|
|
45
|
+
# Rust
|
|
46
|
+
"Cargo.toml": "Rust",
|
|
47
|
+
"Cargo.lock": "Rust",
|
|
48
|
+
# Java / Kotlin
|
|
49
|
+
"pom.xml": "Java",
|
|
50
|
+
"build.gradle": "Java / Kotlin",
|
|
51
|
+
"build.gradle.kts": "Kotlin",
|
|
52
|
+
"gradlew": "Java / Kotlin",
|
|
53
|
+
# C# / .NET
|
|
54
|
+
"global.json": "C# / .NET",
|
|
55
|
+
# PHP
|
|
56
|
+
"composer.json": "PHP",
|
|
57
|
+
"composer.lock": "PHP",
|
|
58
|
+
# Ruby
|
|
59
|
+
"Gemfile": "Ruby",
|
|
60
|
+
"Gemfile.lock": "Ruby",
|
|
61
|
+
# Elixir
|
|
62
|
+
"mix.exs": "Elixir",
|
|
63
|
+
"mix.lock": "Elixir",
|
|
64
|
+
# Dart / Flutter
|
|
65
|
+
"pubspec.yaml": "Dart / Flutter",
|
|
66
|
+
"pubspec.lock": "Dart / Flutter",
|
|
67
|
+
# C / C++
|
|
68
|
+
"CMakeLists.txt": "C / C++",
|
|
69
|
+
"Makefile": "C / C++",
|
|
70
|
+
"meson.build": "C / C++",
|
|
71
|
+
# Scala
|
|
72
|
+
"build.sbt": "Scala",
|
|
73
|
+
# Swift
|
|
74
|
+
"Package.swift": "Swift"
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# Extension mappings for fallback file census
|
|
78
|
+
EXTENSION_MAP: dict[str, str] = {
|
|
79
|
+
".py": "Python",
|
|
80
|
+
".ts": "TypeScript",
|
|
81
|
+
".tsx": "TypeScript",
|
|
82
|
+
".js": "JavaScript",
|
|
83
|
+
".jsx": "JavaScript",
|
|
84
|
+
".go": "Go",
|
|
85
|
+
".rs": "Rust",
|
|
86
|
+
".java": "Java",
|
|
87
|
+
".cs": "C#",
|
|
88
|
+
".php": "PHP",
|
|
89
|
+
".rb": "Ruby",
|
|
90
|
+
".kt": "Kotlin",
|
|
91
|
+
".kts": "Kotlin",
|
|
92
|
+
".swift": "Swift",
|
|
93
|
+
".dart": "Dart",
|
|
94
|
+
".ex": "Elixir",
|
|
95
|
+
".exs": "Elixir",
|
|
96
|
+
".scala": "Scala",
|
|
97
|
+
".c": "C",
|
|
98
|
+
".cpp": "C++",
|
|
99
|
+
".cc": "C++",
|
|
100
|
+
".h": "C / C++",
|
|
101
|
+
".hpp": "C++",
|
|
102
|
+
".sh": "Shell / Bash",
|
|
103
|
+
".sql": "SQL"
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def census_file_extensions(project_root: Path) -> dict[str, int]:
|
|
108
|
+
"""Tally source file extensions to infer primary languages when no manifest is present."""
|
|
109
|
+
census: dict[str, int] = {}
|
|
110
|
+
ignored_dirs = {".git", "node_modules", "vendor", ".venv", "venv", "dist", "build", ".torusguard", "target"}
|
|
111
|
+
|
|
112
|
+
for p in project_root.rglob("*"):
|
|
113
|
+
if p.is_file() and not any(part in ignored_dirs for part in p.parts):
|
|
114
|
+
ext = p.suffix.lower()
|
|
115
|
+
if ext in EXTENSION_MAP:
|
|
116
|
+
lang = EXTENSION_MAP[ext]
|
|
117
|
+
census[lang] = census.get(lang, 0) + 1
|
|
118
|
+
return census
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def add_to_list(profile, key, value):
|
|
123
|
+
if value and value not in profile[key] and value != "Unknown" and value != "None":
|
|
124
|
+
profile[key].append(value)
|
|
125
|
+
|
|
126
|
+
def detect_stack(project_root: Path) -> dict[str, Any]:
|
|
127
|
+
"""
|
|
128
|
+
Universally detect programming language, framework, ORM, and dependency manifests.
|
|
129
|
+
"""
|
|
130
|
+
profile: dict[str, Any] = {
|
|
131
|
+
"languages_found": [],
|
|
132
|
+
"frameworks_found": [],
|
|
133
|
+
"data_layers_found": [],
|
|
134
|
+
"language": "Unknown",
|
|
135
|
+
"framework": "None",
|
|
136
|
+
"data_layer": "None",
|
|
137
|
+
"ecosystem_family": "universal",
|
|
138
|
+
"dependency_files": [],
|
|
139
|
+
"detection_evidence": [],
|
|
140
|
+
"confidence": "Needs Review",
|
|
141
|
+
"recommended_rules": [
|
|
142
|
+
"TG-SEC-*",
|
|
143
|
+
"TG-INPUT-*",
|
|
144
|
+
"TG-AUTH-*"
|
|
145
|
+
],
|
|
146
|
+
"recommended_references": [
|
|
147
|
+
"polyglot-security-matrix.md"
|
|
148
|
+
]
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# 1. Discover all dependency manifest files present
|
|
152
|
+
for manifest_name, lang in MANIFEST_CATALOG.items():
|
|
153
|
+
if (project_root / manifest_name).is_file():
|
|
154
|
+
profile["dependency_files"].append(manifest_name)
|
|
155
|
+
|
|
156
|
+
# Check for C# csproj/sln files (recursive)
|
|
157
|
+
csproj_files = list(project_root.glob("**/*.csproj"))
|
|
158
|
+
sln_files = list(project_root.glob("**/*.sln"))
|
|
159
|
+
for f in (csproj_files + sln_files)[:10]:
|
|
160
|
+
rel = str(f.relative_to(project_root))
|
|
161
|
+
if rel not in profile["dependency_files"]:
|
|
162
|
+
profile["dependency_files"].append(rel)
|
|
163
|
+
|
|
164
|
+
# 2. Inspect Ecosystems in order of specificity
|
|
165
|
+
|
|
166
|
+
# -------------------------------------------------------------------------
|
|
167
|
+
# A. Python Ecosystem
|
|
168
|
+
# -------------------------------------------------------------------------
|
|
169
|
+
manage_py = project_root / "manage.py"
|
|
170
|
+
settings_py = list(project_root.glob("**/settings.py"))
|
|
171
|
+
has_py_manifest = any(f in profile["dependency_files"] for f in ["pyproject.toml", "requirements.txt", "Pipfile", "poetry.lock"])
|
|
172
|
+
|
|
173
|
+
if manage_py.is_file() or settings_py:
|
|
174
|
+
add_to_list(profile, "languages_found", "Python")
|
|
175
|
+
profile["ecosystem_family"] = "python"
|
|
176
|
+
add_to_list(profile, "frameworks_found", "Django")
|
|
177
|
+
add_to_list(profile, "data_layers_found", "Django ORM")
|
|
178
|
+
profile["confidence"] = "Confirmed"
|
|
179
|
+
evidence_file = "manage.py" if manage_py.is_file() else str(settings_py[0].relative_to(project_root))
|
|
180
|
+
profile["detection_evidence"].append({
|
|
181
|
+
"file": evidence_file,
|
|
182
|
+
"indicator": "Django management or settings configuration"
|
|
183
|
+
})
|
|
184
|
+
profile["recommended_rules"].extend(["TG-DB-004", "TG-RATE-001", "TG-PLATFORM-*"])
|
|
185
|
+
profile["recommended_references"].extend(["django-security.md", "python-security-overview.md"])
|
|
186
|
+
|
|
187
|
+
# Check for DRF
|
|
188
|
+
for py_file in project_root.glob("**/*.py"):
|
|
189
|
+
try:
|
|
190
|
+
content = py_file.read_text(encoding="utf-8", errors="ignore")
|
|
191
|
+
if "rest_framework" in content:
|
|
192
|
+
add_to_list(profile, "frameworks_found", "Django / DRF")
|
|
193
|
+
profile["recommended_references"].append("drf-security.md")
|
|
194
|
+
break
|
|
195
|
+
except Exception:
|
|
196
|
+
continue
|
|
197
|
+
|
|
198
|
+
elif has_py_manifest or list(project_root.glob("*.py")):
|
|
199
|
+
# Check FastAPI / Flask / SQLAlchemy
|
|
200
|
+
py_search_files = list(project_root.glob("*.py")) + list(project_root.glob("app/**/*.py")) + list(project_root.glob("src/**/*.py"))
|
|
201
|
+
for py_file in py_search_files[:25]:
|
|
202
|
+
try:
|
|
203
|
+
content = py_file.read_text(encoding="utf-8", errors="ignore")
|
|
204
|
+
if "FastAPI(" in content or "from fastapi" in content:
|
|
205
|
+
add_to_list(profile, "languages_found", "Python")
|
|
206
|
+
profile["ecosystem_family"] = "python"
|
|
207
|
+
add_to_list(profile, "frameworks_found", "FastAPI")
|
|
208
|
+
profile["confidence"] = "Confirmed"
|
|
209
|
+
profile["detection_evidence"].append({
|
|
210
|
+
"file": str(py_file.relative_to(project_root)),
|
|
211
|
+
"indicator": "FastAPI application instantiation"
|
|
212
|
+
})
|
|
213
|
+
profile["recommended_references"].append("fastapi-security.md")
|
|
214
|
+
elif "Flask(__name__)" in content or "from flask" in content:
|
|
215
|
+
add_to_list(profile, "languages_found", "Python")
|
|
216
|
+
profile["ecosystem_family"] = "python"
|
|
217
|
+
add_to_list(profile, "frameworks_found", "Flask")
|
|
218
|
+
profile["confidence"] = "Confirmed"
|
|
219
|
+
profile["detection_evidence"].append({
|
|
220
|
+
"file": str(py_file.relative_to(project_root)),
|
|
221
|
+
"indicator": "Flask application instantiation"
|
|
222
|
+
})
|
|
223
|
+
profile["recommended_references"].append("flask-security.md")
|
|
224
|
+
|
|
225
|
+
if "sqlalchemy" in content:
|
|
226
|
+
add_to_list(profile, "data_layers_found", "SQLAlchemy")
|
|
227
|
+
profile["recommended_references"].append("sqlalchemy-security.md")
|
|
228
|
+
elif "tortoise" in content:
|
|
229
|
+
add_to_list(profile, "data_layers_found", "Tortoise ORM")
|
|
230
|
+
except Exception:
|
|
231
|
+
continue
|
|
232
|
+
|
|
233
|
+
if has_py_manifest:
|
|
234
|
+
add_to_list(profile, "languages_found", "Python")
|
|
235
|
+
profile["ecosystem_family"] = "python"
|
|
236
|
+
profile["confidence"] = "High Confidence"
|
|
237
|
+
profile["detection_evidence"].append({
|
|
238
|
+
"file": profile["dependency_files"][0],
|
|
239
|
+
"indicator": "Python dependency manifest"
|
|
240
|
+
})
|
|
241
|
+
profile["recommended_references"].append("python-security-overview.md")
|
|
242
|
+
|
|
243
|
+
# -------------------------------------------------------------------------
|
|
244
|
+
# B. JavaScript / TypeScript Ecosystem
|
|
245
|
+
# -------------------------------------------------------------------------
|
|
246
|
+
pkg_json_path = project_root / "package.json"
|
|
247
|
+
has_backend_manifest = any((project_root / m).is_file() for m in [
|
|
248
|
+
"composer.json", "mix.exs", "Gemfile", "Cargo.toml", "go.mod", "pom.xml", "build.gradle", "build.gradle.kts"
|
|
249
|
+
]) or bool(csproj_files) or bool(sln_files)
|
|
250
|
+
|
|
251
|
+
if pkg_json_path.is_file():
|
|
252
|
+
is_ts = bool(list(project_root.glob("**/*.ts*")))
|
|
253
|
+
add_to_list(profile, "languages_found", "TypeScript" if is_ts else "JavaScript")
|
|
254
|
+
profile["ecosystem_family"] = "javascript"
|
|
255
|
+
try:
|
|
256
|
+
with open(pkg_json_path, "r", encoding="utf-8") as f:
|
|
257
|
+
pkg = json.load(f)
|
|
258
|
+
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
259
|
+
|
|
260
|
+
# Framework detection
|
|
261
|
+
if "next" in deps:
|
|
262
|
+
add_to_list(profile, "frameworks_found", "Next.js")
|
|
263
|
+
profile["confidence"] = "Confirmed"
|
|
264
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "next dependency"})
|
|
265
|
+
profile["recommended_references"].append("nextjs-security.md")
|
|
266
|
+
profile["recommended_rules"].extend(["TG-CLIENT-*", "TG-PLATFORM-001"])
|
|
267
|
+
elif "@nestjs/core" in deps:
|
|
268
|
+
add_to_list(profile, "frameworks_found", "NestJS")
|
|
269
|
+
profile["confidence"] = "Confirmed"
|
|
270
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "NestJS dependency"})
|
|
271
|
+
profile["recommended_references"].append("nestjs-security.md")
|
|
272
|
+
elif "@sveltejs/kit" in deps:
|
|
273
|
+
add_to_list(profile, "frameworks_found", "SvelteKit")
|
|
274
|
+
profile["confidence"] = "Confirmed"
|
|
275
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "SvelteKit dependency"})
|
|
276
|
+
elif "nuxt" in deps:
|
|
277
|
+
add_to_list(profile, "frameworks_found", "Nuxt")
|
|
278
|
+
profile["confidence"] = "Confirmed"
|
|
279
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "Nuxt dependency"})
|
|
280
|
+
elif "astro" in deps:
|
|
281
|
+
add_to_list(profile, "frameworks_found", "Astro")
|
|
282
|
+
profile["confidence"] = "Confirmed"
|
|
283
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "Astro dependency"})
|
|
284
|
+
elif "express" in deps:
|
|
285
|
+
add_to_list(profile, "frameworks_found", "Express")
|
|
286
|
+
profile["confidence"] = "Confirmed"
|
|
287
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "express dependency"})
|
|
288
|
+
profile["recommended_references"].append("express-security.md")
|
|
289
|
+
elif "react" in deps:
|
|
290
|
+
add_to_list(profile, "frameworks_found", "React / Vite")
|
|
291
|
+
profile["confidence"] = "Confirmed"
|
|
292
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "react dependency"})
|
|
293
|
+
profile["recommended_references"].append("react-vite-security.md")
|
|
294
|
+
|
|
295
|
+
# ORM / Data layer detection
|
|
296
|
+
if "@prisma/client" in deps or "prisma" in deps:
|
|
297
|
+
add_to_list(profile, "data_layers_found", "Prisma ORM")
|
|
298
|
+
elif "drizzle-orm" in deps:
|
|
299
|
+
add_to_list(profile, "data_layers_found", "Drizzle ORM")
|
|
300
|
+
elif "typeorm" in deps:
|
|
301
|
+
add_to_list(profile, "data_layers_found", "TypeORM")
|
|
302
|
+
elif "mongoose" in deps:
|
|
303
|
+
add_to_list(profile, "data_layers_found", "Mongoose")
|
|
304
|
+
elif "@supabase/supabase-js" in deps:
|
|
305
|
+
add_to_list(profile, "data_layers_found", "Supabase")
|
|
306
|
+
profile["recommended_references"].append("supabase-security.md")
|
|
307
|
+
elif "firebase" in deps or "firebase-admin" in deps:
|
|
308
|
+
add_to_list(profile, "data_layers_found", "Firebase")
|
|
309
|
+
profile["recommended_references"].append("firebase-security.md")
|
|
310
|
+
except Exception:
|
|
311
|
+
pass
|
|
312
|
+
|
|
313
|
+
# -------------------------------------------------------------------------
|
|
314
|
+
# C. Go Ecosystem
|
|
315
|
+
# -------------------------------------------------------------------------
|
|
316
|
+
go_mod = project_root / "go.mod"
|
|
317
|
+
if go_mod.is_file():
|
|
318
|
+
add_to_list(profile, "languages_found", "Go")
|
|
319
|
+
profile["ecosystem_family"] = "go"
|
|
320
|
+
profile["confidence"] = "Confirmed"
|
|
321
|
+
profile["detection_evidence"].append({"file": "go.mod", "indicator": "Go module manifest"})
|
|
322
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-INPUT-002", "TG-DB-004", "TG-SSRF-*"])
|
|
323
|
+
profile["recommended_references"].append("go-security.md")
|
|
324
|
+
|
|
325
|
+
try:
|
|
326
|
+
go_content = go_mod.read_text(encoding="utf-8", errors="ignore")
|
|
327
|
+
if "github.com/gin-gonic/gin" in go_content:
|
|
328
|
+
add_to_list(profile, "frameworks_found", "Gin")
|
|
329
|
+
elif "github.com/gofiber/fiber" in go_content:
|
|
330
|
+
add_to_list(profile, "frameworks_found", "Fiber")
|
|
331
|
+
elif "github.com/labstack/echo" in go_content:
|
|
332
|
+
add_to_list(profile, "frameworks_found", "Echo")
|
|
333
|
+
elif "github.com/go-chi/chi" in go_content:
|
|
334
|
+
add_to_list(profile, "frameworks_found", "Chi")
|
|
335
|
+
|
|
336
|
+
if "gorm.io/gorm" in go_content:
|
|
337
|
+
add_to_list(profile, "data_layers_found", "GORM")
|
|
338
|
+
elif "github.com/jmoiron/sqlx" in go_content:
|
|
339
|
+
add_to_list(profile, "data_layers_found", "SQLx")
|
|
340
|
+
elif "database/sql" in go_content or "github.com/lib/pq" in go_content or "github.com/go-sql-driver/mysql" in go_content:
|
|
341
|
+
add_to_list(profile, "data_layers_found", "database/sql")
|
|
342
|
+
except Exception:
|
|
343
|
+
pass
|
|
344
|
+
|
|
345
|
+
# -------------------------------------------------------------------------
|
|
346
|
+
# D. Rust Ecosystem
|
|
347
|
+
# -------------------------------------------------------------------------
|
|
348
|
+
cargo_toml = project_root / "Cargo.toml"
|
|
349
|
+
if cargo_toml.is_file():
|
|
350
|
+
add_to_list(profile, "languages_found", "Rust")
|
|
351
|
+
profile["ecosystem_family"] = "rust"
|
|
352
|
+
profile["confidence"] = "Confirmed"
|
|
353
|
+
profile["detection_evidence"].append({"file": "Cargo.toml", "indicator": "Cargo package manifest"})
|
|
354
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-DB-004", "TG-BIZ-001"])
|
|
355
|
+
profile["recommended_references"].append("rust-security.md")
|
|
356
|
+
|
|
357
|
+
try:
|
|
358
|
+
cargo_content = cargo_toml.read_text(encoding="utf-8", errors="ignore")
|
|
359
|
+
if "actix-web" in cargo_content:
|
|
360
|
+
add_to_list(profile, "frameworks_found", "Actix-web")
|
|
361
|
+
elif "axum" in cargo_content:
|
|
362
|
+
add_to_list(profile, "frameworks_found", "Axum")
|
|
363
|
+
elif "rocket" in cargo_content:
|
|
364
|
+
add_to_list(profile, "frameworks_found", "Rocket")
|
|
365
|
+
|
|
366
|
+
if "diesel" in cargo_content:
|
|
367
|
+
add_to_list(profile, "data_layers_found", "Diesel ORM")
|
|
368
|
+
elif "sqlx" in cargo_content:
|
|
369
|
+
add_to_list(profile, "data_layers_found", "SQLx")
|
|
370
|
+
elif "sea-orm" in cargo_content:
|
|
371
|
+
add_to_list(profile, "data_layers_found", "SeaORM")
|
|
372
|
+
except Exception:
|
|
373
|
+
pass
|
|
374
|
+
|
|
375
|
+
# -------------------------------------------------------------------------
|
|
376
|
+
# E. Java / Kotlin Ecosystem
|
|
377
|
+
# -------------------------------------------------------------------------
|
|
378
|
+
pom_files = [project_root / "pom.xml"] + list(project_root.glob("*/pom.xml"))[:10]
|
|
379
|
+
gradle_files = [project_root / "build.gradle", project_root / "build.gradle.kts"] + list(project_root.glob("*/build.gradle*"))[:10]
|
|
380
|
+
has_jvm_manifest = any(f.is_file() for f in pom_files + gradle_files)
|
|
381
|
+
if has_jvm_manifest:
|
|
382
|
+
java_files = list(project_root.glob("**/*.java"))
|
|
383
|
+
kt_files = list(project_root.glob("**/*.kt"))
|
|
384
|
+
is_kotlin = (project_root / "build.gradle.kts").is_file() or (len(kt_files) > len(java_files) and len(kt_files) > 0)
|
|
385
|
+
add_to_list(profile, "languages_found", "Kotlin" if is_kotlin else "Java")
|
|
386
|
+
profile["ecosystem_family"] = "jvm"
|
|
387
|
+
profile["confidence"] = "Confirmed"
|
|
388
|
+
ev_file = "build.gradle.kts" if (project_root / "build.gradle.kts").is_file() else ("build.gradle" if (project_root / "build.gradle").is_file() else "pom.xml")
|
|
389
|
+
profile["detection_evidence"].append({"file": ev_file, "indicator": f"{profile['language']} build configuration"})
|
|
390
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-INPUT-002", "TG-DB-004"])
|
|
391
|
+
profile["recommended_references"].append("java-security.md")
|
|
392
|
+
|
|
393
|
+
check_content = ""
|
|
394
|
+
for jf in (pom_files + gradle_files)[:15]:
|
|
395
|
+
if jf.is_file():
|
|
396
|
+
try:
|
|
397
|
+
check_content += jf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
398
|
+
except Exception:
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
if "quarkus" in check_content:
|
|
402
|
+
add_to_list(profile, "frameworks_found", "Quarkus")
|
|
403
|
+
elif "spring-boot" in check_content or "springframework" in check_content:
|
|
404
|
+
add_to_list(profile, "frameworks_found", "Spring Boot")
|
|
405
|
+
elif "ktor" in check_content:
|
|
406
|
+
add_to_list(profile, "frameworks_found", "Ktor")
|
|
407
|
+
elif "micronaut" in check_content:
|
|
408
|
+
add_to_list(profile, "frameworks_found", "Micronaut")
|
|
409
|
+
|
|
410
|
+
if "hibernate" in check_content or "spring-boot-starter-data-jpa" in check_content or "quarkus-hibernate" in check_content:
|
|
411
|
+
add_to_list(profile, "data_layers_found", "Hibernate / JPA")
|
|
412
|
+
elif "jooq" in check_content:
|
|
413
|
+
add_to_list(profile, "data_layers_found", "jOOQ")
|
|
414
|
+
|
|
415
|
+
# -------------------------------------------------------------------------
|
|
416
|
+
# F. C# / .NET Ecosystem
|
|
417
|
+
# -------------------------------------------------------------------------
|
|
418
|
+
if (csproj_files or sln_files):
|
|
419
|
+
add_to_list(profile, "languages_found", "C#")
|
|
420
|
+
profile["ecosystem_family"] = "dotnet"
|
|
421
|
+
profile["confidence"] = "Confirmed"
|
|
422
|
+
ev_file = str(csproj_files[0].relative_to(project_root)) if csproj_files else str(sln_files[0].relative_to(project_root))
|
|
423
|
+
profile["detection_evidence"].append({"file": ev_file, "indicator": ".NET project or solution manifest"})
|
|
424
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-DB-004"])
|
|
425
|
+
profile["recommended_references"].append("csharp-security.md")
|
|
426
|
+
|
|
427
|
+
csproj_content = ""
|
|
428
|
+
for cf in csproj_files[:10]:
|
|
429
|
+
try:
|
|
430
|
+
csproj_content += cf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
431
|
+
except Exception:
|
|
432
|
+
pass
|
|
433
|
+
|
|
434
|
+
if "Microsoft.NET.Sdk.Web" in csproj_content or "Microsoft.AspNetCore" in csproj_content or "Swashbuckle" in csproj_content:
|
|
435
|
+
add_to_list(profile, "frameworks_found", "ASP.NET Core")
|
|
436
|
+
if "Microsoft.EntityFrameworkCore" in csproj_content:
|
|
437
|
+
add_to_list(profile, "data_layers_found", "Entity Framework Core")
|
|
438
|
+
elif "Dapper" in csproj_content:
|
|
439
|
+
add_to_list(profile, "data_layers_found", "Dapper")
|
|
440
|
+
|
|
441
|
+
# -------------------------------------------------------------------------
|
|
442
|
+
# G. PHP Ecosystem
|
|
443
|
+
# -------------------------------------------------------------------------
|
|
444
|
+
composer_json = project_root / "composer.json"
|
|
445
|
+
if composer_json.is_file():
|
|
446
|
+
add_to_list(profile, "languages_found", "PHP")
|
|
447
|
+
profile["ecosystem_family"] = "php"
|
|
448
|
+
profile["confidence"] = "Confirmed"
|
|
449
|
+
profile["detection_evidence"].append({"file": "composer.json", "indicator": "PHP Composer manifest"})
|
|
450
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-INPUT-002", "TG-PLATFORM-*"])
|
|
451
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
452
|
+
|
|
453
|
+
try:
|
|
454
|
+
with open(composer_json, "r", encoding="utf-8") as f:
|
|
455
|
+
c_data = json.load(f)
|
|
456
|
+
reqs = {**c_data.get("require", {}), **c_data.get("require-dev", {})}
|
|
457
|
+
if "laravel/framework" in reqs:
|
|
458
|
+
add_to_list(profile, "frameworks_found", "Laravel")
|
|
459
|
+
add_to_list(profile, "data_layers_found", "Eloquent ORM")
|
|
460
|
+
elif "symfony/framework-bundle" in reqs or any("symfony/" in k for k in reqs):
|
|
461
|
+
add_to_list(profile, "frameworks_found", "Symfony")
|
|
462
|
+
add_to_list(profile, "data_layers_found", "Doctrine ORM")
|
|
463
|
+
except Exception:
|
|
464
|
+
pass
|
|
465
|
+
|
|
466
|
+
# -------------------------------------------------------------------------
|
|
467
|
+
# H. Ruby Ecosystem
|
|
468
|
+
# -------------------------------------------------------------------------
|
|
469
|
+
gemfile = project_root / "Gemfile"
|
|
470
|
+
if gemfile.is_file():
|
|
471
|
+
add_to_list(profile, "languages_found", "Ruby")
|
|
472
|
+
profile["ecosystem_family"] = "ruby"
|
|
473
|
+
profile["confidence"] = "Confirmed"
|
|
474
|
+
profile["detection_evidence"].append({"file": "Gemfile", "indicator": "Ruby Bundler manifest"})
|
|
475
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-DB-004"])
|
|
476
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
477
|
+
|
|
478
|
+
try:
|
|
479
|
+
g_content = gemfile.read_text(encoding="utf-8", errors="ignore")
|
|
480
|
+
if "rails" in g_content:
|
|
481
|
+
add_to_list(profile, "frameworks_found", "Ruby on Rails")
|
|
482
|
+
add_to_list(profile, "data_layers_found", "ActiveRecord")
|
|
483
|
+
elif "sinatra" in g_content:
|
|
484
|
+
add_to_list(profile, "frameworks_found", "Sinatra")
|
|
485
|
+
except Exception:
|
|
486
|
+
pass
|
|
487
|
+
|
|
488
|
+
# -------------------------------------------------------------------------
|
|
489
|
+
# I. Elixir Ecosystem
|
|
490
|
+
# -------------------------------------------------------------------------
|
|
491
|
+
mix_exs = project_root / "mix.exs"
|
|
492
|
+
if mix_exs.is_file():
|
|
493
|
+
add_to_list(profile, "languages_found", "Elixir")
|
|
494
|
+
profile["ecosystem_family"] = "elixir"
|
|
495
|
+
profile["confidence"] = "Confirmed"
|
|
496
|
+
profile["detection_evidence"].append({"file": "mix.exs", "indicator": "Elixir Mix manifest"})
|
|
497
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
498
|
+
try:
|
|
499
|
+
m_content = mix_exs.read_text(encoding="utf-8", errors="ignore")
|
|
500
|
+
if "phoenix" in m_content:
|
|
501
|
+
add_to_list(profile, "frameworks_found", "Phoenix")
|
|
502
|
+
add_to_list(profile, "data_layers_found", "Ecto")
|
|
503
|
+
except Exception:
|
|
504
|
+
pass
|
|
505
|
+
|
|
506
|
+
# -------------------------------------------------------------------------
|
|
507
|
+
# J. Dart / Flutter Ecosystem
|
|
508
|
+
# -------------------------------------------------------------------------
|
|
509
|
+
pubspec = project_root / "pubspec.yaml"
|
|
510
|
+
if pubspec.is_file():
|
|
511
|
+
add_to_list(profile, "languages_found", "Dart")
|
|
512
|
+
profile["ecosystem_family"] = "dart"
|
|
513
|
+
profile["confidence"] = "Confirmed"
|
|
514
|
+
profile["detection_evidence"].append({"file": "pubspec.yaml", "indicator": "Dart pubspec manifest"})
|
|
515
|
+
try:
|
|
516
|
+
p_content = pubspec.read_text(encoding="utf-8", errors="ignore")
|
|
517
|
+
if "flutter:" in p_content:
|
|
518
|
+
add_to_list(profile, "frameworks_found", "Flutter")
|
|
519
|
+
except Exception:
|
|
520
|
+
pass
|
|
521
|
+
|
|
522
|
+
# -------------------------------------------------------------------------
|
|
523
|
+
# K. Fallback: Extension Census (When No Manifest or Custom Structure)
|
|
524
|
+
# -------------------------------------------------------------------------
|
|
525
|
+
census = census_file_extensions(project_root)
|
|
526
|
+
profile["extension_census"] = census
|
|
527
|
+
|
|
528
|
+
if not profile.get("languages_found") and census:
|
|
529
|
+
top_lang = max(census.items(), key=lambda x: x[1])[0]
|
|
530
|
+
add_to_list(profile, "languages_found", top_lang)
|
|
531
|
+
profile["confidence"] = "Inferred from file census"
|
|
532
|
+
profile["detection_evidence"].append({
|
|
533
|
+
"file": "source files",
|
|
534
|
+
"indicator": f"{census[top_lang]} {top_lang} source file(s) found"
|
|
535
|
+
})
|
|
536
|
+
family_map = {
|
|
537
|
+
"Go": "go", "Rust": "rust", "Java": "jvm", "Kotlin": "jvm",
|
|
538
|
+
"C#": "dotnet", "PHP": "php", "Ruby": "ruby", "Python": "python",
|
|
539
|
+
"TypeScript": "javascript", "JavaScript": "javascript",
|
|
540
|
+
"C": "native", "C++": "native", "Swift": "apple", "Dart": "dart"
|
|
541
|
+
}
|
|
542
|
+
profile["ecosystem_family"] = family_map.get(top_lang, "universal")
|
|
543
|
+
|
|
544
|
+
# -------------------------------------------------------------------------
|
|
545
|
+
# L. Monorepo & Multi-Package Workspace Integration
|
|
546
|
+
# -------------------------------------------------------------------------
|
|
547
|
+
try:
|
|
548
|
+
from monorepo_detector import scan_workspace
|
|
549
|
+
ws_res = scan_workspace(str(project_root))
|
|
550
|
+
if ws_res.get("is_monorepo"):
|
|
551
|
+
profile["is_monorepo"] = True
|
|
552
|
+
profile["monorepo_type"] = ws_res.get("monorepo_type", "monorepo")
|
|
553
|
+
profile["sub_stacks"] = ws_res.get("packages", [])
|
|
554
|
+
languages_found = list(set(
|
|
555
|
+
p["language"] for p in ws_res.get("packages", [])
|
|
556
|
+
if p.get("language") and p["language"] != "unknown"
|
|
557
|
+
))
|
|
558
|
+
profile["sub_languages"] = languages_found
|
|
559
|
+
if len(languages_found) > 1:
|
|
560
|
+
profile["ecosystem_family"] = "polyglot_monorepo"
|
|
561
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-PLATFORM-*"])
|
|
562
|
+
except Exception:
|
|
563
|
+
profile["is_monorepo"] = False
|
|
564
|
+
profile["sub_stacks"] = []
|
|
565
|
+
|
|
566
|
+
# Compile final strings
|
|
567
|
+
if profile.get("languages_found"):
|
|
568
|
+
profile["language"] = " / ".join(profile["languages_found"])
|
|
569
|
+
else:
|
|
570
|
+
profile["language"] = "Unknown"
|
|
571
|
+
|
|
572
|
+
if profile.get("frameworks_found"):
|
|
573
|
+
profile["framework"] = " / ".join(profile["frameworks_found"])
|
|
574
|
+
else:
|
|
575
|
+
profile["framework"] = "None"
|
|
576
|
+
|
|
577
|
+
if profile.get("data_layers_found"):
|
|
578
|
+
profile["data_layer"] = " / ".join(profile["data_layers_found"])
|
|
579
|
+
else:
|
|
580
|
+
profile["data_layer"] = "None"
|
|
581
|
+
|
|
582
|
+
return profile
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def main():
|
|
586
|
+
parser = argparse.ArgumentParser(description="TorusGuard Universal Polyglot Stack Detector")
|
|
587
|
+
parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
|
|
588
|
+
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
589
|
+
args = parser.parse_args()
|
|
590
|
+
|
|
591
|
+
project_root = Path(args.path).resolve()
|
|
592
|
+
profile = detect_stack(project_root)
|
|
593
|
+
|
|
594
|
+
if args.json:
|
|
595
|
+
print(json.dumps(profile, indent=2))
|
|
596
|
+
else:
|
|
597
|
+
evidence = profile["detection_evidence"][0]["file"] if profile["detection_evidence"] else "heuristic"
|
|
598
|
+
print("## Detected Stack (Universal Polyglot)")
|
|
599
|
+
print(f"- Language: {profile['language']}")
|
|
600
|
+
print(f"- Ecosystem Family: {profile.get('ecosystem_family', 'universal')}")
|
|
601
|
+
print(f"- Framework: {profile['framework']}")
|
|
602
|
+
print(f"- Data layer: {profile['data_layer']}")
|
|
603
|
+
print(f"- Dependency files: {', '.join(profile['dependency_files']) if profile['dependency_files'] else 'None'}")
|
|
604
|
+
print(f"- Detection confidence: {profile['confidence']} ({evidence})")
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
if __name__ == "__main__":
|
|
608
|
+
main()
|