torusguard 0.9.5 → 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 +98 -79
- package/.torusguard/config/torusguard.json +1 -1
- 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/schemas/memory-context.schema.json +95 -0
- package/.torusguard/schemas/memory-event.schema.json +70 -0
- package/.torusguard/schemas/memory-pattern.schema.json +65 -0
- package/.torusguard/scripts/diff_guard.py +202 -15
- package/.torusguard/scripts/finding_scorer.py +199 -86
- package/.torusguard/scripts/html_reporter.py +628 -0
- package/.torusguard/scripts/manifest_builder.py +7 -5
- package/.torusguard/scripts/memory_engine.py +1334 -0
- package/.torusguard/scripts/monorepo_detector.py +123 -10
- package/.torusguard/scripts/rules_sync.py +321 -0
- package/.torusguard/scripts/run_manager.py +171 -89
- package/.torusguard/scripts/stack_detect.py +458 -26
- package/.torusguard/skills/torusguard/SKILL.md +4 -3
- package/.torusguard/skills/torusguard/bootstrap.py +344 -56
- 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/.torusguard/skills/torusguard-audit/SKILL.md +25 -25
- package/.torusguard/skills/torusguard-harden/SKILL.md +3 -3
- package/.torusguard/skills/torusguard-recheck/SKILL.md +3 -3
- package/.torusguard/workflows/memory.md +53 -0
- package/README.md +307 -401
- package/bin/torusguard.js +204 -6
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +4 -3
- package/skills/torusguard/bootstrap.py +68 -4
- package/skills/torusguard/payload/.manifest.json +98 -79
- package/skills/torusguard/payload/config/torusguard.json +1 -1
- 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/schemas/memory-context.schema.json +95 -0
- package/skills/torusguard/payload/schemas/memory-event.schema.json +70 -0
- package/skills/torusguard/payload/schemas/memory-pattern.schema.json +65 -0
- package/skills/torusguard/payload/scripts/diff_guard.py +202 -15
- package/skills/torusguard/payload/scripts/finding_scorer.py +199 -86
- package/skills/torusguard/payload/scripts/html_reporter.py +628 -0
- package/skills/torusguard/payload/scripts/manifest_builder.py +7 -5
- package/skills/torusguard/payload/scripts/memory_engine.py +1334 -0
- 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/run_manager.py +171 -89
- package/skills/torusguard/payload/scripts/stack_detect.py +458 -26
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +4 -3
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +344 -56
- 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/skills/torusguard/payload/skills/torusguard-audit/SKILL.md +25 -25
- package/skills/torusguard/payload/skills/torusguard-harden/SKILL.md +3 -3
- package/skills/torusguard/payload/skills/torusguard-recheck/SKILL.md +3 -3
- package/skills/torusguard/payload/workflows/memory.md +53 -0
- package/skills/torusguard-audit/SKILL.md +25 -25
- package/skills/torusguard-harden/SKILL.md +3 -3
- package/skills/torusguard-recheck/SKILL.md +3 -3
- package/.torusguard/scripts/__pycache__/diff_guard.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
- package/skills/torusguard/payload/scripts/__pycache__/diff_guard.cpython-311.pyc +0 -0
- package/skills/torusguard/payload/scripts/__pycache__/monorepo_detector.cpython-311.pyc +0 -0
|
@@ -1,21 +1,132 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""
|
|
3
|
-
TorusGuard Stack Detector
|
|
4
|
-
Inspects project directory to detect programming language,
|
|
5
|
-
and dependency manifests
|
|
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).
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
import sys
|
|
9
10
|
import json
|
|
10
11
|
import argparse
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import
|
|
13
|
+
from typing import Any
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
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
|
+
def detect_stack(project_root: Path) -> dict[str, Any]:
|
|
122
|
+
"""
|
|
123
|
+
Universally detect programming language, framework, ORM, and dependency manifests.
|
|
124
|
+
"""
|
|
125
|
+
profile: dict[str, Any] = {
|
|
16
126
|
"language": "Unknown",
|
|
17
127
|
"framework": "None",
|
|
18
128
|
"data_layer": "None",
|
|
129
|
+
"ecosystem_family": "universal",
|
|
19
130
|
"dependency_files": [],
|
|
20
131
|
"detection_evidence": [],
|
|
21
132
|
"confidence": "Needs Review",
|
|
@@ -24,31 +135,47 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
24
135
|
"TG-INPUT-*",
|
|
25
136
|
"TG-AUTH-*"
|
|
26
137
|
],
|
|
27
|
-
"recommended_references": [
|
|
138
|
+
"recommended_references": [
|
|
139
|
+
"polyglot-security-matrix.md"
|
|
140
|
+
]
|
|
28
141
|
}
|
|
29
142
|
|
|
30
|
-
# 1.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
143
|
+
# 1. Discover all dependency manifest files present
|
|
144
|
+
for manifest_name, lang in MANIFEST_CATALOG.items():
|
|
145
|
+
if (project_root / manifest_name).is_file():
|
|
146
|
+
profile["dependency_files"].append(manifest_name)
|
|
147
|
+
|
|
148
|
+
# Check for C# csproj/sln files (recursive)
|
|
149
|
+
csproj_files = list(project_root.glob("**/*.csproj"))
|
|
150
|
+
sln_files = list(project_root.glob("**/*.sln"))
|
|
151
|
+
for f in (csproj_files + sln_files)[:10]:
|
|
152
|
+
rel = str(f.relative_to(project_root))
|
|
153
|
+
if rel not in profile["dependency_files"]:
|
|
154
|
+
profile["dependency_files"].append(rel)
|
|
155
|
+
|
|
156
|
+
# 2. Inspect Ecosystems in order of specificity
|
|
35
157
|
|
|
36
|
-
#
|
|
158
|
+
# -------------------------------------------------------------------------
|
|
159
|
+
# A. Python Ecosystem
|
|
160
|
+
# -------------------------------------------------------------------------
|
|
37
161
|
manage_py = project_root / "manage.py"
|
|
38
162
|
settings_py = list(project_root.glob("**/settings.py"))
|
|
39
|
-
|
|
163
|
+
has_py_manifest = any(f in profile["dependency_files"] for f in ["pyproject.toml", "requirements.txt", "Pipfile", "poetry.lock"])
|
|
164
|
+
|
|
40
165
|
if manage_py.is_file() or settings_py:
|
|
41
166
|
profile["language"] = "Python"
|
|
167
|
+
profile["ecosystem_family"] = "python"
|
|
42
168
|
profile["framework"] = "Django"
|
|
43
169
|
profile["data_layer"] = "Django ORM"
|
|
44
170
|
profile["confidence"] = "Confirmed"
|
|
171
|
+
evidence_file = "manage.py" if manage_py.is_file() else str(settings_py[0].relative_to(project_root))
|
|
45
172
|
profile["detection_evidence"].append({
|
|
46
|
-
"file":
|
|
173
|
+
"file": evidence_file,
|
|
47
174
|
"indicator": "Django management or settings configuration"
|
|
48
175
|
})
|
|
49
176
|
profile["recommended_rules"].extend(["TG-DB-004", "TG-RATE-001", "TG-PLATFORM-*"])
|
|
50
177
|
profile["recommended_references"].extend(["django-security.md", "python-security-overview.md"])
|
|
51
|
-
|
|
178
|
+
|
|
52
179
|
# Check for DRF
|
|
53
180
|
for py_file in project_root.glob("**/*.py"):
|
|
54
181
|
try:
|
|
@@ -60,13 +187,15 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
60
187
|
except Exception:
|
|
61
188
|
continue
|
|
62
189
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
190
|
+
elif has_py_manifest or list(project_root.glob("*.py")):
|
|
191
|
+
# Check FastAPI / Flask / SQLAlchemy
|
|
192
|
+
py_search_files = list(project_root.glob("*.py")) + list(project_root.glob("app/**/*.py")) + list(project_root.glob("src/**/*.py"))
|
|
193
|
+
for py_file in py_search_files[:25]:
|
|
66
194
|
try:
|
|
67
195
|
content = py_file.read_text(encoding="utf-8", errors="ignore")
|
|
68
196
|
if "FastAPI(" in content or "from fastapi" in content:
|
|
69
197
|
profile["language"] = "Python"
|
|
198
|
+
profile["ecosystem_family"] = "python"
|
|
70
199
|
profile["framework"] = "FastAPI"
|
|
71
200
|
profile["confidence"] = "Confirmed"
|
|
72
201
|
profile["detection_evidence"].append({
|
|
@@ -76,6 +205,7 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
76
205
|
profile["recommended_references"].append("fastapi-security.md")
|
|
77
206
|
elif "Flask(__name__)" in content or "from flask" in content:
|
|
78
207
|
profile["language"] = "Python"
|
|
208
|
+
profile["ecosystem_family"] = "python"
|
|
79
209
|
profile["framework"] = "Flask"
|
|
80
210
|
profile["confidence"] = "Confirmed"
|
|
81
211
|
profile["detection_evidence"].append({
|
|
@@ -87,24 +217,62 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
87
217
|
if "sqlalchemy" in content:
|
|
88
218
|
profile["data_layer"] = "SQLAlchemy"
|
|
89
219
|
profile["recommended_references"].append("sqlalchemy-security.md")
|
|
220
|
+
elif "tortoise" in content:
|
|
221
|
+
profile["data_layer"] = "Tortoise ORM"
|
|
90
222
|
except Exception:
|
|
91
223
|
continue
|
|
92
224
|
|
|
93
|
-
|
|
225
|
+
if profile["language"] == "Unknown" and has_py_manifest:
|
|
226
|
+
profile["language"] = "Python"
|
|
227
|
+
profile["ecosystem_family"] = "python"
|
|
228
|
+
profile["confidence"] = "High Confidence"
|
|
229
|
+
profile["detection_evidence"].append({
|
|
230
|
+
"file": profile["dependency_files"][0],
|
|
231
|
+
"indicator": "Python dependency manifest"
|
|
232
|
+
})
|
|
233
|
+
profile["recommended_references"].append("python-security-overview.md")
|
|
234
|
+
|
|
235
|
+
# -------------------------------------------------------------------------
|
|
236
|
+
# B. JavaScript / TypeScript Ecosystem
|
|
237
|
+
# -------------------------------------------------------------------------
|
|
94
238
|
pkg_json_path = project_root / "package.json"
|
|
95
|
-
|
|
96
|
-
|
|
239
|
+
has_backend_manifest = any((project_root / m).is_file() for m in [
|
|
240
|
+
"composer.json", "mix.exs", "Gemfile", "Cargo.toml", "go.mod", "pom.xml", "build.gradle", "build.gradle.kts"
|
|
241
|
+
]) or bool(csproj_files) or bool(sln_files)
|
|
242
|
+
|
|
243
|
+
if pkg_json_path.is_file() and profile["language"] == "Unknown" and not has_backend_manifest:
|
|
244
|
+
is_ts = bool(list(project_root.glob("**/*.ts*")))
|
|
245
|
+
profile["language"] = "TypeScript" if is_ts else "JavaScript"
|
|
246
|
+
profile["ecosystem_family"] = "javascript"
|
|
97
247
|
try:
|
|
98
248
|
with open(pkg_json_path, "r", encoding="utf-8") as f:
|
|
99
249
|
pkg = json.load(f)
|
|
100
250
|
deps = {**pkg.get("dependencies", {}), **pkg.get("devDependencies", {})}
|
|
101
|
-
|
|
251
|
+
|
|
252
|
+
# Framework detection
|
|
102
253
|
if "next" in deps:
|
|
103
254
|
profile["framework"] = "Next.js"
|
|
104
255
|
profile["confidence"] = "Confirmed"
|
|
105
256
|
profile["detection_evidence"].append({"file": "package.json", "indicator": "next dependency"})
|
|
106
257
|
profile["recommended_references"].append("nextjs-security.md")
|
|
107
258
|
profile["recommended_rules"].extend(["TG-CLIENT-*", "TG-PLATFORM-001"])
|
|
259
|
+
elif "@nestjs/core" in deps:
|
|
260
|
+
profile["framework"] = "NestJS"
|
|
261
|
+
profile["confidence"] = "Confirmed"
|
|
262
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "NestJS dependency"})
|
|
263
|
+
profile["recommended_references"].append("nestjs-security.md")
|
|
264
|
+
elif "@sveltejs/kit" in deps:
|
|
265
|
+
profile["framework"] = "SvelteKit"
|
|
266
|
+
profile["confidence"] = "Confirmed"
|
|
267
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "SvelteKit dependency"})
|
|
268
|
+
elif "nuxt" in deps:
|
|
269
|
+
profile["framework"] = "Nuxt"
|
|
270
|
+
profile["confidence"] = "Confirmed"
|
|
271
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "Nuxt dependency"})
|
|
272
|
+
elif "astro" in deps:
|
|
273
|
+
profile["framework"] = "Astro"
|
|
274
|
+
profile["confidence"] = "Confirmed"
|
|
275
|
+
profile["detection_evidence"].append({"file": "package.json", "indicator": "Astro dependency"})
|
|
108
276
|
elif "express" in deps:
|
|
109
277
|
profile["framework"] = "Express"
|
|
110
278
|
profile["confidence"] = "Confirmed"
|
|
@@ -116,7 +284,16 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
116
284
|
profile["detection_evidence"].append({"file": "package.json", "indicator": "react dependency"})
|
|
117
285
|
profile["recommended_references"].append("react-vite-security.md")
|
|
118
286
|
|
|
119
|
-
|
|
287
|
+
# ORM / Data layer detection
|
|
288
|
+
if "@prisma/client" in deps or "prisma" in deps:
|
|
289
|
+
profile["data_layer"] = "Prisma ORM"
|
|
290
|
+
elif "drizzle-orm" in deps:
|
|
291
|
+
profile["data_layer"] = "Drizzle ORM"
|
|
292
|
+
elif "typeorm" in deps:
|
|
293
|
+
profile["data_layer"] = "TypeORM"
|
|
294
|
+
elif "mongoose" in deps:
|
|
295
|
+
profile["data_layer"] = "Mongoose"
|
|
296
|
+
elif "@supabase/supabase-js" in deps:
|
|
120
297
|
profile["data_layer"] = "Supabase"
|
|
121
298
|
profile["recommended_references"].append("supabase-security.md")
|
|
122
299
|
elif "firebase" in deps or "firebase-admin" in deps:
|
|
@@ -125,11 +302,264 @@ def detect_stack(project_root: Path) -> Dict[str, Any]:
|
|
|
125
302
|
except Exception:
|
|
126
303
|
pass
|
|
127
304
|
|
|
305
|
+
# -------------------------------------------------------------------------
|
|
306
|
+
# C. Go Ecosystem
|
|
307
|
+
# -------------------------------------------------------------------------
|
|
308
|
+
go_mod = project_root / "go.mod"
|
|
309
|
+
if go_mod.is_file():
|
|
310
|
+
profile["language"] = "Go"
|
|
311
|
+
profile["ecosystem_family"] = "go"
|
|
312
|
+
profile["confidence"] = "Confirmed"
|
|
313
|
+
profile["detection_evidence"].append({"file": "go.mod", "indicator": "Go module manifest"})
|
|
314
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-INPUT-002", "TG-DB-004", "TG-SSRF-*"])
|
|
315
|
+
profile["recommended_references"].append("go-security.md")
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
go_content = go_mod.read_text(encoding="utf-8", errors="ignore")
|
|
319
|
+
if "github.com/gin-gonic/gin" in go_content:
|
|
320
|
+
profile["framework"] = "Gin"
|
|
321
|
+
elif "github.com/gofiber/fiber" in go_content:
|
|
322
|
+
profile["framework"] = "Fiber"
|
|
323
|
+
elif "github.com/labstack/echo" in go_content:
|
|
324
|
+
profile["framework"] = "Echo"
|
|
325
|
+
elif "github.com/go-chi/chi" in go_content:
|
|
326
|
+
profile["framework"] = "Chi"
|
|
327
|
+
|
|
328
|
+
if "gorm.io/gorm" in go_content:
|
|
329
|
+
profile["data_layer"] = "GORM"
|
|
330
|
+
elif "github.com/jmoiron/sqlx" in go_content:
|
|
331
|
+
profile["data_layer"] = "SQLx"
|
|
332
|
+
elif "database/sql" in go_content or "github.com/lib/pq" in go_content or "github.com/go-sql-driver/mysql" in go_content:
|
|
333
|
+
profile["data_layer"] = "database/sql"
|
|
334
|
+
except Exception:
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
# -------------------------------------------------------------------------
|
|
338
|
+
# D. Rust Ecosystem
|
|
339
|
+
# -------------------------------------------------------------------------
|
|
340
|
+
cargo_toml = project_root / "Cargo.toml"
|
|
341
|
+
if cargo_toml.is_file() and profile["language"] == "Unknown":
|
|
342
|
+
profile["language"] = "Rust"
|
|
343
|
+
profile["ecosystem_family"] = "rust"
|
|
344
|
+
profile["confidence"] = "Confirmed"
|
|
345
|
+
profile["detection_evidence"].append({"file": "Cargo.toml", "indicator": "Cargo package manifest"})
|
|
346
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-DB-004", "TG-BIZ-001"])
|
|
347
|
+
profile["recommended_references"].append("rust-security.md")
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
cargo_content = cargo_toml.read_text(encoding="utf-8", errors="ignore")
|
|
351
|
+
if "actix-web" in cargo_content:
|
|
352
|
+
profile["framework"] = "Actix-web"
|
|
353
|
+
elif "axum" in cargo_content:
|
|
354
|
+
profile["framework"] = "Axum"
|
|
355
|
+
elif "rocket" in cargo_content:
|
|
356
|
+
profile["framework"] = "Rocket"
|
|
357
|
+
|
|
358
|
+
if "diesel" in cargo_content:
|
|
359
|
+
profile["data_layer"] = "Diesel ORM"
|
|
360
|
+
elif "sqlx" in cargo_content:
|
|
361
|
+
profile["data_layer"] = "SQLx"
|
|
362
|
+
elif "sea-orm" in cargo_content:
|
|
363
|
+
profile["data_layer"] = "SeaORM"
|
|
364
|
+
except Exception:
|
|
365
|
+
pass
|
|
366
|
+
|
|
367
|
+
# -------------------------------------------------------------------------
|
|
368
|
+
# E. Java / Kotlin Ecosystem
|
|
369
|
+
# -------------------------------------------------------------------------
|
|
370
|
+
pom_files = [project_root / "pom.xml"] + list(project_root.glob("*/pom.xml"))[:10]
|
|
371
|
+
gradle_files = [project_root / "build.gradle", project_root / "build.gradle.kts"] + list(project_root.glob("*/build.gradle*"))[:10]
|
|
372
|
+
has_jvm_manifest = any(f.is_file() for f in pom_files + gradle_files)
|
|
373
|
+
if has_jvm_manifest and profile["language"] == "Unknown":
|
|
374
|
+
java_files = list(project_root.glob("**/*.java"))
|
|
375
|
+
kt_files = list(project_root.glob("**/*.kt"))
|
|
376
|
+
is_kotlin = (project_root / "build.gradle.kts").is_file() or (len(kt_files) > len(java_files) and len(kt_files) > 0)
|
|
377
|
+
profile["language"] = "Kotlin" if is_kotlin else "Java"
|
|
378
|
+
profile["ecosystem_family"] = "jvm"
|
|
379
|
+
profile["confidence"] = "Confirmed"
|
|
380
|
+
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")
|
|
381
|
+
profile["detection_evidence"].append({"file": ev_file, "indicator": f"{profile['language']} build configuration"})
|
|
382
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-INPUT-002", "TG-DB-004"])
|
|
383
|
+
profile["recommended_references"].append("java-security.md")
|
|
384
|
+
|
|
385
|
+
check_content = ""
|
|
386
|
+
for jf in (pom_files + gradle_files)[:15]:
|
|
387
|
+
if jf.is_file():
|
|
388
|
+
try:
|
|
389
|
+
check_content += jf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
390
|
+
except Exception:
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
if "quarkus" in check_content:
|
|
394
|
+
profile["framework"] = "Quarkus"
|
|
395
|
+
elif "spring-boot" in check_content or "springframework" in check_content:
|
|
396
|
+
profile["framework"] = "Spring Boot"
|
|
397
|
+
elif "ktor" in check_content:
|
|
398
|
+
profile["framework"] = "Ktor"
|
|
399
|
+
elif "micronaut" in check_content:
|
|
400
|
+
profile["framework"] = "Micronaut"
|
|
401
|
+
|
|
402
|
+
if "hibernate" in check_content or "spring-boot-starter-data-jpa" in check_content or "quarkus-hibernate" in check_content:
|
|
403
|
+
profile["data_layer"] = "Hibernate / JPA"
|
|
404
|
+
elif "jooq" in check_content:
|
|
405
|
+
profile["data_layer"] = "jOOQ"
|
|
406
|
+
|
|
407
|
+
# -------------------------------------------------------------------------
|
|
408
|
+
# F. C# / .NET Ecosystem
|
|
409
|
+
# -------------------------------------------------------------------------
|
|
410
|
+
if (csproj_files or sln_files) and profile["language"] == "Unknown":
|
|
411
|
+
profile["language"] = "C#"
|
|
412
|
+
profile["ecosystem_family"] = "dotnet"
|
|
413
|
+
profile["confidence"] = "Confirmed"
|
|
414
|
+
ev_file = str(csproj_files[0].relative_to(project_root)) if csproj_files else str(sln_files[0].relative_to(project_root))
|
|
415
|
+
profile["detection_evidence"].append({"file": ev_file, "indicator": ".NET project or solution manifest"})
|
|
416
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-DB-004"])
|
|
417
|
+
profile["recommended_references"].append("csharp-security.md")
|
|
418
|
+
|
|
419
|
+
csproj_content = ""
|
|
420
|
+
for cf in csproj_files[:10]:
|
|
421
|
+
try:
|
|
422
|
+
csproj_content += cf.read_text(encoding="utf-8", errors="ignore") + "\n"
|
|
423
|
+
except Exception:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
if "Microsoft.NET.Sdk.Web" in csproj_content or "Microsoft.AspNetCore" in csproj_content or "Swashbuckle" in csproj_content:
|
|
427
|
+
profile["framework"] = "ASP.NET Core"
|
|
428
|
+
if "Microsoft.EntityFrameworkCore" in csproj_content:
|
|
429
|
+
profile["data_layer"] = "Entity Framework Core"
|
|
430
|
+
elif "Dapper" in csproj_content:
|
|
431
|
+
profile["data_layer"] = "Dapper"
|
|
432
|
+
|
|
433
|
+
# -------------------------------------------------------------------------
|
|
434
|
+
# G. PHP Ecosystem
|
|
435
|
+
# -------------------------------------------------------------------------
|
|
436
|
+
composer_json = project_root / "composer.json"
|
|
437
|
+
if composer_json.is_file() and profile["language"] == "Unknown":
|
|
438
|
+
profile["language"] = "PHP"
|
|
439
|
+
profile["ecosystem_family"] = "php"
|
|
440
|
+
profile["confidence"] = "Confirmed"
|
|
441
|
+
profile["detection_evidence"].append({"file": "composer.json", "indicator": "PHP Composer manifest"})
|
|
442
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-INPUT-001", "TG-INPUT-002", "TG-PLATFORM-*"])
|
|
443
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
444
|
+
|
|
445
|
+
try:
|
|
446
|
+
with open(composer_json, "r", encoding="utf-8") as f:
|
|
447
|
+
c_data = json.load(f)
|
|
448
|
+
reqs = {**c_data.get("require", {}), **c_data.get("require-dev", {})}
|
|
449
|
+
if "laravel/framework" in reqs:
|
|
450
|
+
profile["framework"] = "Laravel"
|
|
451
|
+
profile["data_layer"] = "Eloquent ORM"
|
|
452
|
+
elif "symfony/framework-bundle" in reqs or any("symfony/" in k for k in reqs):
|
|
453
|
+
profile["framework"] = "Symfony"
|
|
454
|
+
profile["data_layer"] = "Doctrine ORM"
|
|
455
|
+
except Exception:
|
|
456
|
+
pass
|
|
457
|
+
|
|
458
|
+
# -------------------------------------------------------------------------
|
|
459
|
+
# H. Ruby Ecosystem
|
|
460
|
+
# -------------------------------------------------------------------------
|
|
461
|
+
gemfile = project_root / "Gemfile"
|
|
462
|
+
if gemfile.is_file() and profile["language"] == "Unknown":
|
|
463
|
+
profile["language"] = "Ruby"
|
|
464
|
+
profile["ecosystem_family"] = "ruby"
|
|
465
|
+
profile["confidence"] = "Confirmed"
|
|
466
|
+
profile["detection_evidence"].append({"file": "Gemfile", "indicator": "Ruby Bundler manifest"})
|
|
467
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-INPUT-001", "TG-DB-004"])
|
|
468
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
g_content = gemfile.read_text(encoding="utf-8", errors="ignore")
|
|
472
|
+
if "rails" in g_content:
|
|
473
|
+
profile["framework"] = "Ruby on Rails"
|
|
474
|
+
profile["data_layer"] = "ActiveRecord"
|
|
475
|
+
elif "sinatra" in g_content:
|
|
476
|
+
profile["framework"] = "Sinatra"
|
|
477
|
+
except Exception:
|
|
478
|
+
pass
|
|
479
|
+
|
|
480
|
+
# -------------------------------------------------------------------------
|
|
481
|
+
# I. Elixir Ecosystem
|
|
482
|
+
# -------------------------------------------------------------------------
|
|
483
|
+
mix_exs = project_root / "mix.exs"
|
|
484
|
+
if mix_exs.is_file() and profile["language"] == "Unknown":
|
|
485
|
+
profile["language"] = "Elixir"
|
|
486
|
+
profile["ecosystem_family"] = "elixir"
|
|
487
|
+
profile["confidence"] = "Confirmed"
|
|
488
|
+
profile["detection_evidence"].append({"file": "mix.exs", "indicator": "Elixir Mix manifest"})
|
|
489
|
+
profile["recommended_references"].append("polyglot-security-matrix.md")
|
|
490
|
+
try:
|
|
491
|
+
m_content = mix_exs.read_text(encoding="utf-8", errors="ignore")
|
|
492
|
+
if "phoenix" in m_content:
|
|
493
|
+
profile["framework"] = "Phoenix"
|
|
494
|
+
profile["data_layer"] = "Ecto"
|
|
495
|
+
except Exception:
|
|
496
|
+
pass
|
|
497
|
+
|
|
498
|
+
# -------------------------------------------------------------------------
|
|
499
|
+
# J. Dart / Flutter Ecosystem
|
|
500
|
+
# -------------------------------------------------------------------------
|
|
501
|
+
pubspec = project_root / "pubspec.yaml"
|
|
502
|
+
if pubspec.is_file() and profile["language"] == "Unknown":
|
|
503
|
+
profile["language"] = "Dart"
|
|
504
|
+
profile["ecosystem_family"] = "dart"
|
|
505
|
+
profile["confidence"] = "Confirmed"
|
|
506
|
+
profile["detection_evidence"].append({"file": "pubspec.yaml", "indicator": "Dart pubspec manifest"})
|
|
507
|
+
try:
|
|
508
|
+
p_content = pubspec.read_text(encoding="utf-8", errors="ignore")
|
|
509
|
+
if "flutter:" in p_content:
|
|
510
|
+
profile["framework"] = "Flutter"
|
|
511
|
+
except Exception:
|
|
512
|
+
pass
|
|
513
|
+
|
|
514
|
+
# -------------------------------------------------------------------------
|
|
515
|
+
# K. Fallback: Extension Census (When No Manifest or Custom Structure)
|
|
516
|
+
# -------------------------------------------------------------------------
|
|
517
|
+
census = census_file_extensions(project_root)
|
|
518
|
+
profile["extension_census"] = census
|
|
519
|
+
|
|
520
|
+
if profile["language"] == "Unknown" and census:
|
|
521
|
+
top_lang = max(census.items(), key=lambda x: x[1])[0]
|
|
522
|
+
profile["language"] = top_lang
|
|
523
|
+
profile["confidence"] = "Inferred from file census"
|
|
524
|
+
profile["detection_evidence"].append({
|
|
525
|
+
"file": "source files",
|
|
526
|
+
"indicator": f"{census[top_lang]} {top_lang} source file(s) found"
|
|
527
|
+
})
|
|
528
|
+
family_map = {
|
|
529
|
+
"Go": "go", "Rust": "rust", "Java": "jvm", "Kotlin": "jvm",
|
|
530
|
+
"C#": "dotnet", "PHP": "php", "Ruby": "ruby", "Python": "python",
|
|
531
|
+
"TypeScript": "javascript", "JavaScript": "javascript",
|
|
532
|
+
"C": "native", "C++": "native", "Swift": "apple", "Dart": "dart"
|
|
533
|
+
}
|
|
534
|
+
profile["ecosystem_family"] = family_map.get(top_lang, "universal")
|
|
535
|
+
|
|
536
|
+
# -------------------------------------------------------------------------
|
|
537
|
+
# L. Monorepo & Multi-Package Workspace Integration
|
|
538
|
+
# -------------------------------------------------------------------------
|
|
539
|
+
try:
|
|
540
|
+
from monorepo_detector import scan_workspace
|
|
541
|
+
ws_res = scan_workspace(str(project_root))
|
|
542
|
+
if ws_res.get("is_monorepo"):
|
|
543
|
+
profile["is_monorepo"] = True
|
|
544
|
+
profile["monorepo_type"] = ws_res.get("monorepo_type", "monorepo")
|
|
545
|
+
profile["sub_stacks"] = ws_res.get("packages", [])
|
|
546
|
+
languages_found = list(set(
|
|
547
|
+
p["language"] for p in ws_res.get("packages", [])
|
|
548
|
+
if p.get("language") and p["language"] != "unknown"
|
|
549
|
+
))
|
|
550
|
+
profile["sub_languages"] = languages_found
|
|
551
|
+
if len(languages_found) > 1:
|
|
552
|
+
profile["ecosystem_family"] = "polyglot_monorepo"
|
|
553
|
+
profile["recommended_rules"].extend(["TG-SEC-*", "TG-AUTH-*", "TG-PLATFORM-*"])
|
|
554
|
+
except Exception:
|
|
555
|
+
profile["is_monorepo"] = False
|
|
556
|
+
profile["sub_stacks"] = []
|
|
557
|
+
|
|
128
558
|
return profile
|
|
129
559
|
|
|
130
560
|
|
|
131
561
|
def main():
|
|
132
|
-
parser = argparse.ArgumentParser(description="TorusGuard Stack Detector")
|
|
562
|
+
parser = argparse.ArgumentParser(description="TorusGuard Universal Polyglot Stack Detector")
|
|
133
563
|
parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
|
|
134
564
|
parser.add_argument("--json", action="store_true", help="Output raw JSON")
|
|
135
565
|
args = parser.parse_args()
|
|
@@ -141,12 +571,14 @@ def main():
|
|
|
141
571
|
print(json.dumps(profile, indent=2))
|
|
142
572
|
else:
|
|
143
573
|
evidence = profile["detection_evidence"][0]["file"] if profile["detection_evidence"] else "heuristic"
|
|
144
|
-
print(
|
|
574
|
+
print("## Detected Stack (Universal Polyglot)")
|
|
145
575
|
print(f"- Language: {profile['language']}")
|
|
576
|
+
print(f"- Ecosystem Family: {profile.get('ecosystem_family', 'universal')}")
|
|
146
577
|
print(f"- Framework: {profile['framework']}")
|
|
147
578
|
print(f"- Data layer: {profile['data_layer']}")
|
|
148
579
|
print(f"- Dependency files: {', '.join(profile['dependency_files']) if profile['dependency_files'] else 'None'}")
|
|
149
580
|
print(f"- Detection confidence: {profile['confidence']} ({evidence})")
|
|
150
581
|
|
|
582
|
+
|
|
151
583
|
if __name__ == "__main__":
|
|
152
584
|
main()
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard
|
|
3
|
-
description: "Autonomous security engine: 71 rules across 11 families, governed remediation (Ponytail Protocol), SARIF v2.1.0 exports, and
|
|
4
|
-
version: 0.
|
|
3
|
+
description: "Autonomous security engine: 71 rules across 11 families, governed remediation (Ponytail Protocol), SARIF v2.1.0 exports, and persistent security memory context for Python & TypeScript web applications."
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# TorusGuard Master Security Router
|
|
@@ -11,7 +11,7 @@ version: 0.9.5
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
## ⚡ Workspace Bootstrap Check
|
|
14
|
-
1. **If `.torusguard/` exists on disk:** Full governance active. Route
|
|
14
|
+
1. **If `.torusguard/` exists on disk:** Full governance active. Load `.torusguard/memory/context.json` into prompt context for local-first intelligence. Route commands to specialist workflows below.
|
|
15
15
|
2. **If `.torusguard/` is absent (Autonomous AI Mode):** Operates standalone in any AI agent (Kimi, Antigravity, VS Code, Cursor). Apply in-memory security invariants:
|
|
16
16
|
- Server-side auth & tenant scoping (`.filter(tenant=...)`).
|
|
17
17
|
- Zero client-side secrets (`SUPABASE_SERVICE_ROLE_KEY`, live API keys).
|
|
@@ -36,6 +36,7 @@ Load **only** the designated sub-skill on demand to preserve the 1,000–1,500 t
|
|
|
36
36
|
| `/torusguard recheck` | `skills/torusguard-recheck/SKILL.md` | `reviewer` |
|
|
37
37
|
| `/torusguard report` | `skills/torusguard-report/SKILL.md` | `reviewer` |
|
|
38
38
|
| `/torusguard status` | `skills/torusguard-status/SKILL.md` | `reviewer` |
|
|
39
|
+
| `/torusguard memory` | `skills/torusguard/SKILL.md` | `reviewer` |
|
|
39
40
|
| `/torusguard full` | `skills/torusguard-full/SKILL.md` | *All* |
|
|
40
41
|
|
|
41
42
|
*Note: Never load all skills simultaneously. Lazy-load strictly on demand.*
|