torusguard 0.9.4 → 1.0.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 +15 -10
- package/.torusguard/config/torusguard.json +1 -1
- 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/__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/diff_guard.py +71 -11
- package/.torusguard/scripts/finding_scorer.py +171 -86
- package/.torusguard/scripts/manifest_builder.py +2 -0
- package/.torusguard/scripts/memory_engine.py +912 -0
- package/.torusguard/scripts/run_manager.py +171 -89
- package/.torusguard/skills/torusguard/SKILL.md +12 -7
- package/.torusguard/skills/torusguard/bootstrap.py +344 -56
- 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 +12 -2
- package/bin/torusguard.js +173 -43
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +4 -3
- package/skills/torusguard/__pycache__/bootstrap.cpython-311.pyc +0 -0
- package/skills/torusguard/bootstrap.py +244 -80
- package/skills/torusguard/payload/.manifest.json +15 -10
- package/skills/torusguard/payload/config/torusguard.json +1 -1
- 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 +71 -11
- package/skills/torusguard/payload/scripts/finding_scorer.py +171 -86
- package/skills/torusguard/payload/scripts/manifest_builder.py +2 -0
- package/skills/torusguard/payload/scripts/memory_engine.py +912 -0
- package/skills/torusguard/payload/scripts/run_manager.py +171 -89
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +12 -7
- package/skills/torusguard/payload/skills/torusguard/bootstrap.py +344 -56
- 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
|
@@ -10,9 +10,33 @@ import sys
|
|
|
10
10
|
import shutil
|
|
11
11
|
import json
|
|
12
12
|
import argparse
|
|
13
|
-
import subprocess
|
|
14
13
|
from pathlib import Path
|
|
15
14
|
|
|
15
|
+
# Ensure UTF-8 stdout/stderr on Windows consoles
|
|
16
|
+
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
|
+
try:
|
|
18
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
19
|
+
except Exception:
|
|
20
|
+
pass
|
|
21
|
+
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
|
+
try:
|
|
23
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
24
|
+
except Exception:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
# ─── ANSI Color Helpers ───────────────────────────────────────────────────────
|
|
28
|
+
BOLD = "\033[1m"
|
|
29
|
+
DIM = "\033[2m"
|
|
30
|
+
RESET = "\033[0m"
|
|
31
|
+
CYAN = "\033[36m"
|
|
32
|
+
GREEN = "\033[32m"
|
|
33
|
+
YELLOW = "\033[33m"
|
|
34
|
+
WHITE = "\033[97m"
|
|
35
|
+
GRAY = "\033[90m"
|
|
36
|
+
RED = "\033[31m"
|
|
37
|
+
BG_GREEN = "\033[42m"
|
|
38
|
+
BG_CYAN = "\033[46m"
|
|
39
|
+
|
|
16
40
|
|
|
17
41
|
def find_project_root(start_dir=None):
|
|
18
42
|
"""Detect project root directory by searching for standard repo root markers."""
|
|
@@ -33,36 +57,141 @@ def find_project_root(start_dir=None):
|
|
|
33
57
|
return current
|
|
34
58
|
|
|
35
59
|
|
|
36
|
-
def
|
|
60
|
+
def print_header():
|
|
61
|
+
"""Print the branded TorusGuard header card."""
|
|
62
|
+
print(f"""
|
|
63
|
+
{CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
|
|
64
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
65
|
+
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}v0.9.5{RESET} {CYAN}│{RESET}
|
|
66
|
+
{CYAN}│{RESET} {DIM}Autonomous Security Engine for AI-Built Applications{RESET} {CYAN}│{RESET}
|
|
67
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
68
|
+
{CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
|
|
69
|
+
""")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def print_step_1_assets(file_count):
|
|
73
|
+
"""Print the enhanced Step 1 asset unpacking card."""
|
|
74
|
+
print(f""" {CYAN}┌─ {BOLD}Step 1/3{RESET} {CYAN}──────────────────────── {WHITE}Unpacking Governance Assets{RESET} {CYAN}──────┐{RESET}
|
|
75
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
76
|
+
{CYAN}│{RESET} {GREEN}✔{RESET} Canonical security rules {DIM}71 rules across 11 families{RESET} {CYAN}│{RESET}
|
|
77
|
+
{CYAN}│{RESET} {GREEN}✔{RESET} JSON validation schemas {DIM}8 formal schemas{RESET} {CYAN}│{RESET}
|
|
78
|
+
{CYAN}│{RESET} {GREEN}✔{RESET} Specialist agent specifications {DIM}5 isolated agents{RESET} {CYAN}│{RESET}
|
|
79
|
+
{CYAN}│{RESET} {GREEN}✔{RESET} Workflow templates {DIM}11 slash commands{RESET} {CYAN}│{RESET}
|
|
80
|
+
{CYAN}│{RESET} {GREEN}✔{RESET} Framework reference guides {DIM}10 security guides{RESET} {CYAN}│{RESET}
|
|
81
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
82
|
+
{CYAN}│{RESET} {BOLD}{WHITE}{file_count} files unpacked{RESET} {CYAN}│{RESET}
|
|
83
|
+
{CYAN}└───────────────────────────────────────────────────────────────────────┘{RESET}
|
|
84
|
+
""")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def print_step_2_profile(detected_stack=None):
|
|
88
|
+
"""Print the security profile & coverage card (in-process stack alignment)."""
|
|
89
|
+
if detected_stack and detected_stack.get("framework") and detected_stack.get("framework") != "None":
|
|
90
|
+
fw = str(detected_stack.get("framework"))
|
|
91
|
+
lang = str(detected_stack.get("language", ""))
|
|
92
|
+
label = f"Auto-aligned stack: {fw} ({lang})"
|
|
93
|
+
pad = " " * max(0, 66 - len(label))
|
|
94
|
+
status_line = f" {CYAN}│{RESET} {GREEN}✔{RESET} {BOLD}Auto-aligned stack:{RESET} {WHITE}{fw}{RESET} {DIM}({lang}){RESET}{pad}{CYAN}│{RESET}"
|
|
95
|
+
else:
|
|
96
|
+
status_line = f" {CYAN}│{RESET} {DIM}ℹ Stack auto-detected on first /torusguard-audit run{RESET} {CYAN}│{RESET}"
|
|
97
|
+
|
|
98
|
+
print(f""" {CYAN}┌─ {BOLD}Step 2/3{RESET} {CYAN}──────────────────── {WHITE}Security Profile & Coverage{RESET} {CYAN}────────┐{RESET}
|
|
99
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
100
|
+
{CYAN}│{RESET} {BOLD}Rule Families:{RESET} {CYAN}│{RESET}
|
|
101
|
+
{CYAN}│{RESET} {YELLOW}TG-SEC{RESET} Secrets & Credentials {YELLOW}TG-DB{RESET} Database Safety {CYAN}│{RESET}
|
|
102
|
+
{CYAN}│{RESET} {YELLOW}TG-INPUT{RESET} Input Validation {YELLOW}TG-AUTH{RESET} Authentication {CYAN}│{RESET}
|
|
103
|
+
{CYAN}│{RESET} {YELLOW}TG-CLIENT{RESET} Client Bundle Leaks {YELLOW}TG-DIFF{RESET} Diff Inspection {CYAN}│{RESET}
|
|
104
|
+
{CYAN}│{RESET} {YELLOW}TG-AGENT{RESET} AI Agent Security {YELLOW}TG-EDGE{RESET} Serverless {CYAN}│{RESET}
|
|
105
|
+
{CYAN}│{RESET} {YELLOW}TG-SUPPLY{RESET} Supply Chain & CI/CD {YELLOW}TG-SSRF{RESET} Outbound Net {CYAN}│{RESET}
|
|
106
|
+
{CYAN}│{RESET} {YELLOW}TG-BIZ{RESET} Business Logic {CYAN}│{RESET}
|
|
107
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
108
|
+
{CYAN}│{RESET} {BOLD}Supported Stacks:{RESET} {CYAN}│{RESET}
|
|
109
|
+
{CYAN}│{RESET} {GREEN}Python{RESET} Django {DIM}·{RESET} FastAPI {DIM}·{RESET} Flask {DIM}·{RESET} DRF {DIM}·{RESET} SQLAlchemy {CYAN}│{RESET}
|
|
110
|
+
{CYAN}│{RESET} {GREEN}Node.js{RESET} Next.js {DIM}·{RESET} Express {DIM}·{RESET} React {DIM}·{RESET} Supabase {DIM}·{RESET} Firebase {CYAN}│{RESET}
|
|
111
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
112
|
+
{status_line}
|
|
113
|
+
{CYAN}└───────────────────────────────────────────────────────────────────────┘{RESET}
|
|
114
|
+
""")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def print_step_3_bridges(registered_ides):
|
|
118
|
+
"""Print the IDE command registration card."""
|
|
119
|
+
lines = ""
|
|
120
|
+
for r in registered_ides:
|
|
121
|
+
lines += f" {CYAN}│{RESET} {GREEN}✔{RESET} {r:<67} {CYAN}│{RESET}\n"
|
|
122
|
+
|
|
123
|
+
print(f""" {CYAN}┌─ {BOLD}Step 3/3{RESET} {CYAN}─────────────────── {WHITE}AI IDE Command Registration{RESET} {CYAN}─────────┐{RESET}
|
|
124
|
+
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
125
|
+
{lines} {CYAN}│{RESET} {CYAN}│{RESET}
|
|
126
|
+
{CYAN}└───────────────────────────────────────────────────────────────────────┘{RESET}
|
|
127
|
+
""")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def print_success_card():
|
|
131
|
+
"""Print the final success card with next steps."""
|
|
132
|
+
print(f""" {GREEN}╔═══════════════════════════════════════════════════════════════════════╗{RESET}
|
|
133
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
134
|
+
{GREEN}║{RESET} {GREEN}{BOLD}✅ WORKSPACE INITIALIZED SUCCESSFULLY{RESET} {GREEN}║{RESET}
|
|
135
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
136
|
+
{GREEN}╠═══════════════════════════════════════════════════════════════════════╣{RESET}
|
|
137
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
138
|
+
{GREEN}║{RESET} {BOLD}Next Steps:{RESET} {GREEN}║{RESET}
|
|
139
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
140
|
+
{GREEN}║{RESET} {CYAN}1.{RESET} In AI Chat {WHITE}/torusguard-audit{RESET} or {WHITE}/torusguard{RESET} {GREEN}║{RESET}
|
|
141
|
+
{GREEN}║{RESET} {CYAN}2.{RESET} In Terminal {WHITE}npx torusguard status{RESET} {GREEN}║{RESET}
|
|
142
|
+
{GREEN}║{RESET} {CYAN}3.{RESET} In CI/CD {WHITE}npx torusguard audit{RESET} {GREEN}║{RESET}
|
|
143
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
144
|
+
{GREEN}║{RESET} {DIM}Docs{RESET} {CYAN}https://github.com/githubmofo/TorusGuard{RESET} {GREEN}║{RESET}
|
|
145
|
+
{GREEN}║{RESET} {DIM}NPM{RESET} {CYAN}https://npmjs.com/package/torusguard{RESET} {GREEN}║{RESET}
|
|
146
|
+
{GREEN}║{RESET} {GREEN}║{RESET}
|
|
147
|
+
{GREEN}╚═══════════════════════════════════════════════════════════════════════╝{RESET}
|
|
148
|
+
""")
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def print_already_initialized(target_root, cfg):
|
|
152
|
+
"""Print the already-initialized status card."""
|
|
153
|
+
print(f"""
|
|
154
|
+
{CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
|
|
155
|
+
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ TORUSGUARD WORKSPACE{RESET} {GREEN}[Active]{RESET} {CYAN}│{RESET}
|
|
156
|
+
{CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
|
|
157
|
+
|
|
158
|
+
{BOLD}▸ Project Root:{RESET} {GREEN}{target_root}{RESET}
|
|
159
|
+
{BOLD}▸ Workspace:{RESET} {GREEN}.torusguard/{RESET} {DIM}(Already Initialized){RESET}
|
|
160
|
+
{BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.0.0')}{RESET}
|
|
161
|
+
{BOLD}▸ Severity Floor:{RESET} {YELLOW}{cfg.get('severity_threshold', 'medium')}{RESET}
|
|
162
|
+
|
|
163
|
+
{DIM}To refresh templates or re-scaffold, run:{RESET}
|
|
164
|
+
{CYAN}npx torusguard init --force{RESET}
|
|
165
|
+
""")
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def scaffold_workspace(target_root=None, force=False, full_commands=False):
|
|
37
169
|
"""Scaffold the .torusguard workspace into the target project root."""
|
|
38
170
|
target_root = Path(target_root or find_project_root()).resolve()
|
|
39
171
|
torusguard_target = target_root / ".torusguard"
|
|
40
172
|
script_dir = Path(__file__).resolve().parent
|
|
41
173
|
payload_dir = script_dir / "payload"
|
|
42
174
|
|
|
43
|
-
print("================================================================================")
|
|
44
|
-
print("TORUSGUARD WORKSPACE BOOTSTRAPPER")
|
|
45
|
-
print("================================================================================")
|
|
46
|
-
print(f"Target Directory: {target_root}")
|
|
47
|
-
print(f"Destination: {torusguard_target}")
|
|
48
|
-
|
|
49
175
|
if torusguard_target.exists() and not force:
|
|
50
|
-
print(f"\n[INFO] Workspace already exists at {torusguard_target}")
|
|
51
176
|
config_file = torusguard_target / "config" / "torusguard.json"
|
|
177
|
+
cfg = {}
|
|
52
178
|
if config_file.exists():
|
|
53
179
|
try:
|
|
54
180
|
with open(config_file, "r", encoding="utf-8") as f:
|
|
55
181
|
cfg = json.load(f)
|
|
56
|
-
print(f"Current Version: {cfg.get('version', 'unknown')}")
|
|
57
|
-
print(f"Severity Minimum: {cfg.get('severity_threshold', 'medium')}")
|
|
58
182
|
except Exception:
|
|
59
183
|
pass
|
|
60
|
-
|
|
184
|
+
print_already_initialized(target_root, cfg)
|
|
61
185
|
return True
|
|
62
186
|
|
|
63
|
-
#
|
|
187
|
+
# ── Header ──────────────────────────────────────────────────────────────
|
|
188
|
+
print_header()
|
|
189
|
+
print(f" {BOLD}▸ Target Project:{RESET} {WHITE}{target_root}{RESET}")
|
|
190
|
+
print(f" {BOLD}▸ Destination:{RESET} {WHITE}{torusguard_target}{RESET}")
|
|
191
|
+
|
|
192
|
+
# ── Step 1: Unpack Payload ──────────────────────────────────────────────
|
|
193
|
+
file_count = 93
|
|
64
194
|
if payload_dir.is_dir():
|
|
65
|
-
print("\n1. Unpacking bundled offline workspace payload...")
|
|
66
195
|
try:
|
|
67
196
|
if torusguard_target.exists() and force:
|
|
68
197
|
shutil.rmtree(torusguard_target)
|
|
@@ -71,22 +200,25 @@ def scaffold_workspace(target_root=None, force=False):
|
|
|
71
200
|
torusguard_target,
|
|
72
201
|
ignore=shutil.ignore_patterns("runs", "*.pyc", "__pycache__", ".git*")
|
|
73
202
|
)
|
|
74
|
-
|
|
203
|
+
# Count actual files
|
|
204
|
+
file_count = sum(1 for _ in torusguard_target.rglob("*") if _.is_file())
|
|
75
205
|
except Exception as e:
|
|
76
|
-
print(f"
|
|
206
|
+
print(f"\n {RED}✖ Failed to unpack payload: {e}{RESET}")
|
|
77
207
|
return False
|
|
78
208
|
else:
|
|
79
209
|
# Fallback: create base structure
|
|
80
|
-
print("\n1. Bundled payload not found. Creating core skeleton...")
|
|
81
210
|
subdirs = [
|
|
82
211
|
"config", "agents", "workflows", "scripts", "templates",
|
|
83
212
|
"schemas", "references", "rules/active", "runs"
|
|
84
213
|
]
|
|
85
214
|
for s in subdirs:
|
|
86
215
|
(torusguard_target / s).mkdir(parents=True, exist_ok=True)
|
|
87
|
-
|
|
216
|
+
file_count = 0
|
|
217
|
+
|
|
218
|
+
print()
|
|
219
|
+
print_step_1_assets(file_count)
|
|
88
220
|
|
|
89
|
-
#
|
|
221
|
+
# ── Ensure runs and rules/active directories exist ──────────────────────
|
|
90
222
|
runs_dir = torusguard_target / "runs"
|
|
91
223
|
runs_dir.mkdir(parents=True, exist_ok=True)
|
|
92
224
|
gitkeep_runs = runs_dir / ".gitkeep"
|
|
@@ -99,52 +231,207 @@ def scaffold_workspace(target_root=None, force=False):
|
|
|
99
231
|
if not gitkeep_rules.exists():
|
|
100
232
|
gitkeep_rules.write_text("", encoding="utf-8")
|
|
101
233
|
|
|
102
|
-
#
|
|
103
|
-
|
|
234
|
+
# ── Ensure memory subsystem is initialized (v1.0.0) ────────────────────
|
|
235
|
+
memory_dir = torusguard_target / "memory"
|
|
236
|
+
memory_dir.mkdir(parents=True, exist_ok=True)
|
|
237
|
+
events_dir = memory_dir / "events"
|
|
238
|
+
events_dir.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
|
|
240
|
+
gitkeep_events = events_dir / ".gitkeep"
|
|
241
|
+
if not gitkeep_events.exists():
|
|
242
|
+
gitkeep_events.write_text("", encoding="utf-8")
|
|
243
|
+
|
|
244
|
+
mem_gitignore = memory_dir / ".gitignore"
|
|
245
|
+
if not mem_gitignore.exists():
|
|
246
|
+
mem_gitignore.write_text("*\n", encoding="utf-8")
|
|
247
|
+
|
|
248
|
+
decay_file = memory_dir / "decay.json"
|
|
249
|
+
if not decay_file.exists():
|
|
250
|
+
decay_file.write_text(json.dumps({
|
|
251
|
+
"default_ttl_days": 90,
|
|
252
|
+
"decay_rate": 0.15,
|
|
253
|
+
"last_decay_run": None
|
|
254
|
+
}, indent=2), encoding="utf-8")
|
|
255
|
+
|
|
256
|
+
patterns_file = memory_dir / "patterns.json"
|
|
257
|
+
if not patterns_file.exists():
|
|
258
|
+
patterns_file.write_text("[]", encoding="utf-8")
|
|
259
|
+
|
|
260
|
+
profile_file = memory_dir / "profile.json"
|
|
261
|
+
if not profile_file.exists():
|
|
262
|
+
profile_file.write_text(json.dumps({
|
|
263
|
+
"stack": [],
|
|
264
|
+
"total_events": 0,
|
|
265
|
+
"active_patterns_count": 0,
|
|
266
|
+
"fix_rate_percentage": None,
|
|
267
|
+
"top_vulnerabilities": [],
|
|
268
|
+
"last_updated": None
|
|
269
|
+
}, indent=2), encoding="utf-8")
|
|
270
|
+
|
|
271
|
+
context_file = memory_dir / "context.json"
|
|
272
|
+
if not context_file.exists():
|
|
273
|
+
context_file.write_text(json.dumps({
|
|
274
|
+
"version": "1.0.0",
|
|
275
|
+
"generated_at": None,
|
|
276
|
+
"token_estimate": 0,
|
|
277
|
+
"max_token_budget": 2000,
|
|
278
|
+
"project_profile": {
|
|
279
|
+
"stack": [],
|
|
280
|
+
"total_events": 0,
|
|
281
|
+
"active_patterns_count": 0,
|
|
282
|
+
"fix_rate_percentage": None,
|
|
283
|
+
"top_vulnerabilities": []
|
|
284
|
+
},
|
|
285
|
+
"cards": []
|
|
286
|
+
}, indent=2), encoding="utf-8")
|
|
287
|
+
|
|
288
|
+
# Ensure target project root .gitignore excludes .torusguard/memory/
|
|
289
|
+
root_gitignore = target_root / ".gitignore"
|
|
290
|
+
if root_gitignore.exists():
|
|
291
|
+
try:
|
|
292
|
+
gi_content = root_gitignore.read_text(encoding="utf-8")
|
|
293
|
+
if ".torusguard/memory" not in gi_content:
|
|
294
|
+
root_gitignore.write_text(gi_content.rstrip() + "\n\n# TorusGuard security memory (local only)\n.torusguard/memory/\n", encoding="utf-8")
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
|
|
298
|
+
# ── Step 2: Security Profile & Coverage (In-Process, Zero Subprocess) ──
|
|
299
|
+
# Execute stack detection in-process without spawning child processes (Socket.dev safe)
|
|
300
|
+
detected_stack = None
|
|
104
301
|
stack_detect_script = torusguard_target / "scripts" / "stack_detect.py"
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if stack_detect_script.exists():
|
|
302
|
+
if stack_detect_script.is_file():
|
|
108
303
|
try:
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
print(f" [DETECTED] Framework: {detected_stack.get('framework')}")
|
|
119
|
-
print(f" [DETECTED] Data Layer: {detected_stack.get('data_layer')}")
|
|
120
|
-
except Exception as e:
|
|
121
|
-
print(f" [WARN] Stack detection encountered error: {e}")
|
|
304
|
+
import importlib.util
|
|
305
|
+
spec = importlib.util.spec_from_file_location("stack_detect", str(stack_detect_script))
|
|
306
|
+
if spec and spec.loader:
|
|
307
|
+
sd_mod = importlib.util.module_from_spec(spec)
|
|
308
|
+
spec.loader.exec_module(sd_mod)
|
|
309
|
+
if hasattr(sd_mod, "detect_stack"):
|
|
310
|
+
detected_stack = sd_mod.detect_stack(target_root)
|
|
311
|
+
except Exception:
|
|
312
|
+
detected_stack = None
|
|
122
313
|
|
|
123
|
-
# 4. Update torusguard.json configuration
|
|
124
314
|
config_file = torusguard_target / "config" / "torusguard.json"
|
|
125
315
|
if config_file.exists():
|
|
126
316
|
try:
|
|
127
317
|
with open(config_file, "r", encoding="utf-8") as f:
|
|
128
318
|
cfg = json.load(f)
|
|
129
|
-
|
|
130
|
-
"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
319
|
+
if detected_stack and detected_stack.get("framework") != "None":
|
|
320
|
+
cfg["detected_stack"] = {
|
|
321
|
+
"language": detected_stack.get("language"),
|
|
322
|
+
"framework": detected_stack.get("framework"),
|
|
323
|
+
"data_layer": detected_stack.get("data_layer")
|
|
324
|
+
}
|
|
325
|
+
else:
|
|
326
|
+
cfg["detected_stack"] = {
|
|
327
|
+
"language": "Unknown",
|
|
328
|
+
"framework": "None",
|
|
329
|
+
"data_layer": "None"
|
|
330
|
+
}
|
|
134
331
|
with open(config_file, "w", encoding="utf-8") as f:
|
|
135
332
|
json.dump(cfg, f, indent=2)
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
333
|
+
except Exception:
|
|
334
|
+
pass
|
|
335
|
+
|
|
336
|
+
print_step_2_profile(detected_stack)
|
|
337
|
+
|
|
338
|
+
# ── Step 3: Register Slash Commands for AI IDEs ─────────────────────────
|
|
339
|
+
registered_ides = []
|
|
340
|
+
tg_workflow_content = """---
|
|
341
|
+
description: TorusGuard Autonomous Security Command Engine — run static security audits, authorized runtime web validation, governed remediation, and SARIF exports.
|
|
342
|
+
version: 1.0.0
|
|
343
|
+
tools: Read, Grep, Glob, Bash, Edit, Write
|
|
344
|
+
agent: auditor
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
# /torusguard — Autonomous Application Security Guardrails
|
|
348
|
+
|
|
349
|
+
$ARGUMENTS
|
|
350
|
+
|
|
351
|
+
## Objective
|
|
352
|
+
Execute TorusGuard security workflows across Python and TypeScript codebases.
|
|
353
|
+
|
|
354
|
+
## Execution
|
|
355
|
+
Parse the requested action from `$ARGUMENTS` (e.g. `audit`, `verify`, `web-validate`, `exploit-check`, `harden`, `apply`, `recheck`, `report`, `status`, `init`):
|
|
356
|
+
1. **If action is omitted or 'status':** View `.torusguard/workflows/status.md` and report posture.
|
|
357
|
+
2. **If action is specified:** Load the dedicated workflow from `.torusguard/workflows/<action>.md` and matching skill from `.torusguard/skills/torusguard-<action>/SKILL.md`.
|
|
358
|
+
3. Follow the phase execution steps defined in the workflow.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
# Ensure .agent/workflows and .agents/workflows are created
|
|
362
|
+
dest_workflow_dirs = []
|
|
363
|
+
if (target_root / ".agents").exists():
|
|
364
|
+
agents_wf = target_root / ".agents" / "workflows"
|
|
365
|
+
agents_wf.mkdir(parents=True, exist_ok=True)
|
|
366
|
+
(agents_wf / "torusguard.md").write_text(tg_workflow_content, encoding="utf-8")
|
|
367
|
+
dest_workflow_dirs.append(agents_wf)
|
|
368
|
+
registered_ides.append("Antigravity / Gemini .agents/workflows/torusguard.md")
|
|
369
|
+
|
|
370
|
+
agent_wf = target_root / ".agent" / "workflows"
|
|
371
|
+
agent_wf.mkdir(parents=True, exist_ok=True)
|
|
372
|
+
(agent_wf / "torusguard.md").write_text(tg_workflow_content, encoding="utf-8")
|
|
373
|
+
dest_workflow_dirs.append(agent_wf)
|
|
374
|
+
registered_ides.append("Tribunal Agent Kit .agent/workflows/torusguard.md")
|
|
375
|
+
|
|
376
|
+
# When full_commands is requested (e.g. via NPM package npx torusguard init),
|
|
377
|
+
# unlock and register all 11 individual slash commands
|
|
378
|
+
if full_commands:
|
|
379
|
+
src_wf = torusguard_target / "workflows"
|
|
380
|
+
if src_wf.is_dir():
|
|
381
|
+
count = 0
|
|
382
|
+
for wf_file in src_wf.glob("*.md"):
|
|
383
|
+
if wf_file.name == "torusguard.md":
|
|
384
|
+
continue
|
|
385
|
+
cmd_filename = f"torusguard-{wf_file.name}"
|
|
386
|
+
for d in dest_workflow_dirs:
|
|
387
|
+
shutil.copy2(wf_file, d / cmd_filename)
|
|
388
|
+
count += 1
|
|
389
|
+
registered_ides.append(f"{count} Slash Commands /torusguard-audit, -harden, -apply, ...")
|
|
390
|
+
|
|
391
|
+
# Claude Code (.claude/commands)
|
|
392
|
+
claude_dir = target_root / ".claude"
|
|
393
|
+
if claude_dir.exists():
|
|
394
|
+
claude_commands = claude_dir / "commands"
|
|
395
|
+
claude_commands.mkdir(parents=True, exist_ok=True)
|
|
396
|
+
(claude_commands / "torusguard.md").write_text(tg_workflow_content, encoding="utf-8")
|
|
397
|
+
registered_ides.append("Claude Code .claude/commands/torusguard.md")
|
|
398
|
+
|
|
399
|
+
# Cursor (.cursor/rules)
|
|
400
|
+
cursor_dir = target_root / ".cursor"
|
|
401
|
+
if cursor_dir.exists():
|
|
402
|
+
cursor_rules = cursor_dir / "rules"
|
|
403
|
+
cursor_rules.mkdir(parents=True, exist_ok=True)
|
|
404
|
+
(cursor_rules / "torusguard.mdc").write_text(tg_workflow_content, encoding="utf-8")
|
|
405
|
+
registered_ides.append("Cursor .cursor/rules/torusguard.mdc")
|
|
406
|
+
|
|
407
|
+
# Windsurf (.windsurfrules)
|
|
408
|
+
if (target_root / ".windsurfrules").exists() or (target_root / ".windsurf").exists():
|
|
409
|
+
windsurf_rules = target_root / ".windsurfrules"
|
|
410
|
+
current_content = windsurf_rules.read_text(encoding="utf-8") if windsurf_rules.exists() else ""
|
|
411
|
+
if "TorusGuard" not in current_content:
|
|
412
|
+
windsurf_rules.write_text(current_content + "\n\n" + tg_workflow_content, encoding="utf-8")
|
|
413
|
+
registered_ides.append("Windsurf .windsurfrules")
|
|
414
|
+
|
|
415
|
+
# VS Code / GitHub Copilot (.github/copilot-instructions.md)
|
|
416
|
+
github_dir = target_root / ".github"
|
|
417
|
+
if github_dir.exists():
|
|
418
|
+
copilot_file = github_dir / "copilot-instructions.md"
|
|
419
|
+
current_content = copilot_file.read_text(encoding="utf-8") if copilot_file.exists() else ""
|
|
420
|
+
if "TorusGuard" not in current_content:
|
|
421
|
+
copilot_file.write_text(current_content + "\n\n# TorusGuard Security Instructions\n" + tg_workflow_content, encoding="utf-8")
|
|
422
|
+
registered_ides.append("VS Code / Copilot .github/copilot-instructions.md")
|
|
423
|
+
|
|
424
|
+
# Universal AGENTS.md (Kimi, Codex, Open Agent Standard)
|
|
425
|
+
agents_md = target_root / "AGENTS.md"
|
|
426
|
+
current_agents = agents_md.read_text(encoding="utf-8") if agents_md.exists() else ""
|
|
427
|
+
if "TorusGuard" not in current_agents:
|
|
428
|
+
agents_md.write_text(current_agents + ("\n\n" if current_agents else "") + "# TorusGuard Security Guardrails\n\nRun `/torusguard audit` to scan the codebase or refer to `.torusguard/` for security guidelines.\n", encoding="utf-8")
|
|
429
|
+
registered_ides.append("Universal AGENTS.md Kimi / Open Agent Standard")
|
|
430
|
+
|
|
431
|
+
print_step_3_bridges(registered_ides)
|
|
432
|
+
|
|
433
|
+
# ── Success Card ────────────────────────────────────────────────────────
|
|
434
|
+
print_success_card()
|
|
148
435
|
return True
|
|
149
436
|
|
|
150
437
|
|
|
@@ -152,7 +439,8 @@ if __name__ == "__main__":
|
|
|
152
439
|
parser = argparse.ArgumentParser(description="TorusGuard Autonomous Workspace Bootstrapper")
|
|
153
440
|
parser.add_argument("--target", type=str, help="Target project root directory")
|
|
154
441
|
parser.add_argument("--force", action="store_true", help="Force overwrite existing workspace")
|
|
442
|
+
parser.add_argument("--full-commands", action="store_true", help="Unlock and register all 11 individual slash commands")
|
|
155
443
|
args = parser.parse_args()
|
|
156
444
|
|
|
157
|
-
success = scaffold_workspace(target_root=args.target, force=args.force)
|
|
445
|
+
success = scaffold_workspace(target_root=args.target, force=args.force, full_commands=args.full_commands)
|
|
158
446
|
sys.exit(0 if success else 1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard-audit
|
|
3
|
-
description: Static
|
|
4
|
-
version: 0.
|
|
3
|
+
description: Static AST scanning, memory pattern boosting, and 0–100 confidence scoring.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
workflow: .torusguard/workflows/audit.md
|
|
6
6
|
tools: Read, Grep, Glob, Write
|
|
7
7
|
scripts-binding:
|
|
@@ -9,36 +9,37 @@ scripts-binding:
|
|
|
9
9
|
- .torusguard/scripts/run_manager.py
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
# TorusGuard Audit — Static Code Analysis &
|
|
12
|
+
# TorusGuard Audit — Static Code Analysis & Memory Scanning
|
|
13
13
|
|
|
14
14
|
## Objective
|
|
15
|
-
Execute static analysis
|
|
15
|
+
Execute static AST analysis, query memory context, compute 0–100 confidence scores with memory boosts, and record findings to runs and memory.
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
## Execution Steps
|
|
20
20
|
|
|
21
|
-
1. **
|
|
22
|
-
2. **
|
|
21
|
+
1. **Run Setup:** Initialize `.torusguard/runs/run-YYYYMMDD-HHMMSS-audit/` via `run_manager.py`.
|
|
22
|
+
2. **Context & Rules:** Load `.torusguard/memory/context.json` for patterns and false positives. Load rules from `.torusguard/rules/active/`.
|
|
23
23
|
3. **AST Scan Sinks:** Match files against active rules:
|
|
24
|
-
- `TG-SEC-*`:
|
|
25
|
-
- `TG-INPUT-*`:
|
|
26
|
-
- `TG-DB-*`: Unscoped
|
|
27
|
-
- `TG-AUTH-*`:
|
|
28
|
-
- `TG-CLIENT-*`:
|
|
29
|
-
4. **Stable Fingerprinting:** Compute SHA-256 hash of
|
|
30
|
-
5. **Root-Cause Clustering:** Group findings by
|
|
31
|
-
6. **
|
|
32
|
-
7. **Write
|
|
24
|
+
- `TG-SEC-*`: Secrets and API keys.
|
|
25
|
+
- `TG-INPUT-*`: SQL injection, unvalidated input.
|
|
26
|
+
- `TG-DB-*`: Unscoped tenant queries (`Model.get(id=id)`).
|
|
27
|
+
- `TG-AUTH-*`: Insecure cookies, open CORS.
|
|
28
|
+
- `TG-CLIENT-*`: Client bundle secret leaks.
|
|
29
|
+
4. **Stable Fingerprinting:** Compute SHA-256 hash of surrounding AST context.
|
|
30
|
+
5. **Root-Cause Clustering:** Group findings by causal sink (`cluster-tenant-isolation`).
|
|
31
|
+
6. **Scoring:** Calculate 0–100 score via `finding_scorer.py` (applies memory boost/suppression).
|
|
32
|
+
7. **Write Artifacts:** Save `findings.json` and sync findings to memory engine.
|
|
33
33
|
|
|
34
34
|
---
|
|
35
35
|
|
|
36
36
|
## Confidence Scoring Rubric
|
|
37
|
-
- **1. Evidence (35):** AST flow (35), regex
|
|
38
|
-
- **2. Reproduction (25):**
|
|
37
|
+
- **1. Evidence (35):** AST flow (35), regex sink (20), heuristic (10).
|
|
38
|
+
- **2. Reproduction (25):** Automated test (25), simulated (15), hypothesis (0).
|
|
39
39
|
- **3. Corroboration (15):** >=3 files (15), 2 files (10), single (5).
|
|
40
|
-
- **4. Environment (15):** Explicit route (15),
|
|
41
|
-
- **5. Review Status (10):** Human
|
|
40
|
+
- **4. Environment (15):** Explicit route (15), ambiguous (8), unknown (0).
|
|
41
|
+
- **5. Review Status (10):** Human confirmed (10), agent verified (5), auto (0).
|
|
42
|
+
- **Memory Modifier:** False positive (-30), regression (+15), recurring (+10).
|
|
42
43
|
- **Bands:** 90–100 (`Confirmed`), 70–89 (`High`), 50–69 (`Medium`), <50 (`Review`).
|
|
43
44
|
|
|
44
45
|
---
|
|
@@ -46,17 +47,16 @@ Execute static analysis across ASTs, generate line-shift invariant fingerprints,
|
|
|
46
47
|
## Finding Card Format
|
|
47
48
|
```markdown
|
|
48
49
|
### [TG-DB-004] Missing Tenant Scoping in /invoices/
|
|
49
|
-
- **Severity**: High | **Confidence**: 90/100 (Confirmed)
|
|
50
|
+
- **Severity**: High | **Confidence**: 90/100 (Confirmed) | **Memory Boost**: +10
|
|
50
51
|
- **File**: `views.py:42` | **Cluster**: `cluster-tenant-isolation`
|
|
51
|
-
- **
|
|
52
|
-
- **Remediation**: Scope by `tenant=request.user.tenant`.
|
|
52
|
+
- **Remediation**: Apply `.filter(tenant=request.user.tenant)`.
|
|
53
53
|
```
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
57
57
|
## Safety Constraints
|
|
58
|
-
- Read-only
|
|
59
|
-
- Exclude build
|
|
58
|
+
- Read-only analysis; zero code modifications during audit.
|
|
59
|
+
- Exclude build directories (`node_modules/`, `.venv/`, `dist/`).
|
|
60
60
|
- Redact secrets before saving to `findings.md`.
|
|
61
61
|
|
|
62
62
|
---
|
|
@@ -65,6 +65,6 @@ Execute static analysis across ASTs, generate line-shift invariant fingerprints,
|
|
|
65
65
|
```markdown
|
|
66
66
|
🛡️ [TorusGuard] Static Code Audit Completed
|
|
67
67
|
- Run ID: run-YYYYMMDD-HHMMSS-audit | Files: <Count> | Findings: <Count>
|
|
68
|
-
- Clusters: <Count> identified |
|
|
68
|
+
- Clusters: <Count> identified | Memory Patterns Applied: <Count>
|
|
69
69
|
Next: Run `/torusguard harden` to formulate surgical fixes.
|
|
70
70
|
```
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard-harden
|
|
3
3
|
description: Package surgical remediation bundles conforming to the Ponytail Protocol (<= 35 additions, <= 25 deletions).
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
workflow: .torusguard/workflows/harden.md
|
|
6
6
|
tools: Read, Grep, Glob, Write
|
|
7
7
|
scripts-binding:
|
|
@@ -38,8 +38,8 @@ Formulate minimal, surgical code fixes bound by the Ponytail Protocol ($\le 35$
|
|
|
38
38
|
|
|
39
39
|
1. **Select Finding:** Choose prioritized verified flaw from active run's `findings.md`.
|
|
40
40
|
2. **Read AST Context:** View target file surrounding lines (±15) using `view_file`.
|
|
41
|
-
3. **Formulate Minimal Fix:**
|
|
42
|
-
4. **Validate Line Churn:** Assert additions $\le 35$ and deletions $\le 25
|
|
41
|
+
3. **Formulate Minimal Fix:** Consult `.torusguard/memory/context.json` for verified idioms; parameterize SQL, add tenant filters.
|
|
42
|
+
4. **Validate Line Churn:** Assert additions $\le 35$ and deletions $\le 25$ via `diff_guard.py`.
|
|
43
43
|
5. **Package Bundle:** Write 4 artifacts (`patch.diff`, `plan.md`, `verification.md`, `rollback.md`).
|
|
44
44
|
6. **Flag Sensitive Paths:** Mark changes touching auth or billing with `Requires Sensitive-Path Sign-Off`.
|
|
45
45
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard-recheck
|
|
3
3
|
description: Execute targeted differential AST re-scan against modified files, verify fix closure, and assert zero regressions.
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 1.0.0
|
|
5
5
|
workflow: .torusguard/workflows/recheck.md
|
|
6
6
|
tools: Read, Grep, Glob, Write
|
|
7
7
|
scripts-binding:
|
|
@@ -19,10 +19,10 @@ Re-scan only files modified by remediation patches, confirming that targeted vul
|
|
|
19
19
|
## Execution Steps
|
|
20
20
|
|
|
21
21
|
1. **Identify Modified Files:** Read `apply-log.json` from active run directory.
|
|
22
|
-
2. **Execute Differential Scan:** Re-run active `TG-*` rules exclusively against
|
|
22
|
+
2. **Execute Differential Scan:** Re-run active `TG-*` rules exclusively against modified files.
|
|
23
23
|
3. **Evaluate Finding Status:** Transition finding state according to transition rules below.
|
|
24
24
|
4. **Assert Regression-Free:** Ensure no new security warnings triggered on altered lines.
|
|
25
|
-
5. **Update State:** Record
|
|
25
|
+
5. **Update State & Memory:** Record status in `findings.json` and sync verified results to memory engine.
|
|
26
26
|
6. **Emit Recheck Report:** Save summary in `.torusguard/runs/<run_id>/recheck-report.md`.
|
|
27
27
|
|
|
28
28
|
---
|