torusguard 1.3.1 → 1.3.2
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 +4 -4
- package/.torusguard/scripts/apply_runner.py +20 -13
- package/.torusguard/scripts/harden_runner.py +31 -9
- package/.torusguard/skills/torusguard/SKILL.md +1 -1
- package/.torusguard/skills/torusguard/bootstrap.py +149 -70
- package/README.md +3 -3
- package/bin/torusguard.js +12 -12
- package/package.json +1 -1
- package/skills/torusguard/SKILL.md +1 -1
- package/skills/torusguard/payload/.manifest.json +3 -3
- package/skills/torusguard/payload/scripts/apply_runner.py +20 -13
- package/skills/torusguard/payload/scripts/harden_runner.py +31 -9
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +1 -1
|
@@ -141,11 +141,11 @@
|
|
|
141
141
|
"schemas/remediation.schema.json": "544904239f871dec9552973e01e09dc1f40dbb8330f20d98c319f3b24031d630",
|
|
142
142
|
"schemas/retest.schema.json": "97c0407ca4365a524020c430912f686f698cc0de90043194621071e9fc681cc7",
|
|
143
143
|
"schemas/runtime-evidence.schema.json": "929d2b094c1cd1f7f9db07be1eba5994e8002477eab3fd24140e1e973fd18cc1",
|
|
144
|
-
"scripts/apply_runner.py": "
|
|
144
|
+
"scripts/apply_runner.py": "2218a02538e821d88bb95211ae907705bea9b0533e43504912d5e3655f69de0c",
|
|
145
145
|
"scripts/audit_runner.py": "71553ca6c2d57df6f7323d41abc0216ecedb821fcf0eb5fd502290bd6dec6ae4",
|
|
146
146
|
"scripts/diff_guard.py": "0dc178cdde1ac0ae1a1ea4b2eb2ce1756b2126f6156909cf55d85249f115fcbd",
|
|
147
147
|
"scripts/finding_scorer.py": "885c7cbb7a8def2ceda0266e5a7f893938e41debc7501f7400a17a606b22dc23",
|
|
148
|
-
"scripts/harden_runner.py": "
|
|
148
|
+
"scripts/harden_runner.py": "cf813ec241db73de94fe76d4929dcfc6dd7826e50c84986c50fa28175717ed93",
|
|
149
149
|
"scripts/html_reporter.py": "f2bc401e9dfebda2c0e078897247210b2c8b83cba24add00e618821f9cae9eda",
|
|
150
150
|
"scripts/manifest_builder.py": "c0137927789a9e40a08243e630ac3c6990beb1b87807934e98906c8ad0b99a97",
|
|
151
151
|
"scripts/memory_engine.py": "b715d0ca030e95eb06ad6eda99027535df8a7e37d64897c023be6a291cf13dc6",
|
|
@@ -157,8 +157,8 @@
|
|
|
157
157
|
"scripts/safety_gate.py": "3379b7534b2590cf563717a448983ad8ed7e8f174f0ad65683d33e8a10f51558",
|
|
158
158
|
"scripts/sarif_exporter.py": "35c66637413afc4b72d5bd426638447c77231d2a8594dce5addceffd4803a20d",
|
|
159
159
|
"scripts/stack_detect.py": "7848006e3cd6e828f7dbe189a8c9728abd58e021ff02efbc4230ef8ca3444596",
|
|
160
|
-
"skills/torusguard/SKILL.md": "
|
|
161
|
-
"skills/torusguard/bootstrap.py": "
|
|
160
|
+
"skills/torusguard/SKILL.md": "4b5cb127bea1f7e78e3452637c3e13117f8ece46de65ec791f2de3ef750e5bd5",
|
|
161
|
+
"skills/torusguard/bootstrap.py": "3d946f388cb4befa739c9d3b1404255d9a8ea5770ebbb79e5a1b9839589e9bc7",
|
|
162
162
|
"skills/torusguard/references/auth-and-sessions.md": "c3e4b9600cb53fc28c993922dc880accbec5783ac90d5ee98d63b52762d19525",
|
|
163
163
|
"skills/torusguard/references/business-logic-and-abuse.md": "58e87df2864d468af70c1ff39f5d540c687fa846b671457f85046ab2065fe99c",
|
|
164
164
|
"skills/torusguard/references/cache-and-sensitive-responses.md": "81b4a7a08e11dafa4ec58d868c4a35f1f891d0dcdcfbcefaef81e1b9ba94f5a1",
|
|
@@ -68,10 +68,7 @@ def find_latest_audit_run(runs_dir: Path) -> Optional[Path]:
|
|
|
68
68
|
if not runs_dir.is_dir():
|
|
69
69
|
return None
|
|
70
70
|
runs = sorted(runs_dir.glob("run-*-audit"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
71
|
-
|
|
72
|
-
if (r / "bundles").is_dir() and list((r / "bundles").iterdir()):
|
|
73
|
-
return r
|
|
74
|
-
return None
|
|
71
|
+
return runs[0] if runs else None
|
|
75
72
|
|
|
76
73
|
|
|
77
74
|
def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> None:
|
|
@@ -102,7 +99,7 @@ def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> Non
|
|
|
102
99
|
restored_files.append(orig_rel)
|
|
103
100
|
|
|
104
101
|
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
105
|
-
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD ROLLBACK RESTORATION v1.3.
|
|
102
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD ROLLBACK RESTORATION v1.3.1{RESET}", border_color=CYAN))
|
|
106
103
|
print(format_box_line(f"{DIM}Restored original files from pre-apply snapshot{RESET}", border_color=CYAN))
|
|
107
104
|
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
108
105
|
|
|
@@ -123,18 +120,28 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
|
|
|
123
120
|
run_folder = find_latest_audit_run(runs_dir)
|
|
124
121
|
|
|
125
122
|
if not run_folder or not run_folder.is_dir():
|
|
126
|
-
print(f"\n {RED}✖ No
|
|
127
|
-
print(f" Run {CYAN}npx torusguard
|
|
123
|
+
print(f"\n {RED}✖ No audit runs found.{RESET}")
|
|
124
|
+
print(f" Run {CYAN}npx torusguard audit{RESET} followed by {CYAN}npx torusguard harden{RESET} first.\n")
|
|
128
125
|
sys.exit(1)
|
|
129
126
|
|
|
130
127
|
bundles_dir = run_folder / "bundles"
|
|
131
|
-
|
|
132
|
-
print(f"\n {RED}✖ Missing bundles directory in run folder: {run_folder}{RESET}\n")
|
|
133
|
-
sys.exit(1)
|
|
134
|
-
|
|
135
|
-
bundle_meta_files = list(bundles_dir.glob("*/metadata.json"))
|
|
128
|
+
bundle_meta_files = list(bundles_dir.glob("*/metadata.json")) if bundles_dir.is_dir() else []
|
|
136
129
|
if not bundle_meta_files:
|
|
137
|
-
print(f"\n {
|
|
130
|
+
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
131
|
+
print(f" {CYAN}│ │{RESET}")
|
|
132
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED PATCH APPLIER v1.3.1{RESET}", border_color=CYAN))
|
|
133
|
+
print(format_box_line(f"{DIM}Human-Gate Authorization, Snapshots & Golden Recipe Distillation{RESET}", border_color=CYAN))
|
|
134
|
+
print(f" {CYAN}│ │{RESET}")
|
|
135
|
+
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
136
|
+
|
|
137
|
+
print(f" {YELLOW}ℹ Zero candidate remediation bundles found in run: {run_folder.name}{RESET}")
|
|
138
|
+
print(f" {GRAY}The findings in this run require manual review or AI-assisted remediation.{RESET}\n")
|
|
139
|
+
print(f" {CYAN}┌─ Recommended Next Steps ────────────────────────────────────────────────┐{RESET}")
|
|
140
|
+
print(format_box_line("1. In AI Chat (Antigravity/Cursor/Claude): Run /torusguard-harden"))
|
|
141
|
+
print(format_box_line(" to formulate custom architectural patches with your AI agent."))
|
|
142
|
+
print(format_box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
|
|
143
|
+
print(format_box_line("3. View visual posture report: npx torusguard report --html"))
|
|
144
|
+
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
138
145
|
sys.exit(0)
|
|
139
146
|
|
|
140
147
|
bundles = []
|
|
@@ -132,7 +132,7 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
|
|
|
132
132
|
applied_rule = True
|
|
133
133
|
fix_explanation = f"Replaced hardcoded credential with os.environ.get('{env_key}')"
|
|
134
134
|
|
|
135
|
-
# Strategy 2: Dangerous React HTML rendering (TG-INPUT-003)
|
|
135
|
+
# Strategy 2: Dangerous React HTML rendering & DOM innerHTML (TG-INPUT-003)
|
|
136
136
|
elif rule_id == "TG-INPUT-003":
|
|
137
137
|
if "dangerouslySetInnerHTML" in target_line:
|
|
138
138
|
# <div dangerouslySetInnerHTML={{ __html: bio }} /> -> <div>{bio}</div>
|
|
@@ -142,6 +142,21 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
|
|
|
142
142
|
new_lines[line_number - 1] = replacement
|
|
143
143
|
applied_rule = True
|
|
144
144
|
fix_explanation = "Replaced raw dangerouslySetInnerHTML injection with safe React text interpolation"
|
|
145
|
+
elif ".innerHTML" in target_line:
|
|
146
|
+
# Pattern A: el.innerHTML = '' or "" -> el.textContent = ""
|
|
147
|
+
if re.search(r'\.innerHTML\s*=\s*["\']["\']', target_line):
|
|
148
|
+
fixed_line = re.sub(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*["\']["\']', r'\1.textContent = ""', target_line)
|
|
149
|
+
if fixed_line != target_line:
|
|
150
|
+
new_lines[line_number - 1] = fixed_line
|
|
151
|
+
applied_rule = True
|
|
152
|
+
fix_explanation = "Replaced unsafe innerHTML DOM reset with safe textContent assignment"
|
|
153
|
+
# Pattern B: el.innerHTML = someVar -> el.textContent = someVar
|
|
154
|
+
elif re.search(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*([a-zA-Z0-9_$.]+)\s*;?$', target_line):
|
|
155
|
+
fixed_line = re.sub(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*([a-zA-Z0-9_$.]+)', r'\1.textContent = \2', target_line)
|
|
156
|
+
if fixed_line != target_line:
|
|
157
|
+
new_lines[line_number - 1] = fixed_line
|
|
158
|
+
applied_rule = True
|
|
159
|
+
fix_explanation = "Replaced raw DOM innerHTML assignment with XSS-safe textContent"
|
|
145
160
|
|
|
146
161
|
# Strategy 3: Raw SQL Concatenation (TG-INPUT-002)
|
|
147
162
|
elif rule_id == "TG-INPUT-002":
|
|
@@ -298,7 +313,7 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
|
|
|
298
313
|
# Print 75-column Terminal Card
|
|
299
314
|
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
300
315
|
print(f" {CYAN}│ │{RESET}")
|
|
301
|
-
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE v1.3.
|
|
316
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE v1.3.1{RESET}", border_color=CYAN))
|
|
302
317
|
print(format_box_line(f"{DIM}Autonomous Minimal Patch Formulation & Ponytail Packaging{RESET}", border_color=CYAN))
|
|
303
318
|
print(f" {CYAN}│ │{RESET}")
|
|
304
319
|
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
@@ -318,14 +333,21 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
|
|
|
318
333
|
if len(bundles) > 5:
|
|
319
334
|
print(format_box_line(f" ... and {len(bundles) - 5} more patches cataloged in remediation.md"))
|
|
320
335
|
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
321
|
-
else:
|
|
322
|
-
print(f" {YELLOW}ℹ No automatic patch templates matched current findings.{RESET}\n")
|
|
323
336
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
print(f" {GREEN}╔═ Next Governed Action ═════════════════════════════════════════════════════╗{RESET}")
|
|
338
|
+
print(format_box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="║", border_color=GREEN))
|
|
339
|
+
print(format_box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="║", border_color=GREEN))
|
|
340
|
+
print(format_box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="║", border_color=GREEN))
|
|
341
|
+
print(f" {GREEN}╚═════════════════════════════════════════════════════════════════════════════╝{RESET}\n")
|
|
342
|
+
else:
|
|
343
|
+
print(f" {YELLOW}ℹ No automatic patch templates matched current findings.{RESET}")
|
|
344
|
+
print(f" {GRAY}Findings require architectural refactoring or AI-assisted guidance.{RESET}\n")
|
|
345
|
+
print(f" {CYAN}┌─ Recommended Next Steps ────────────────────────────────────────────────┐{RESET}")
|
|
346
|
+
print(format_box_line("1. In AI Chat (Antigravity/Cursor/Claude): Run /torusguard-harden"))
|
|
347
|
+
print(format_box_line(" to synthesize custom architectural fixes with your AI agent."))
|
|
348
|
+
print(format_box_line("2. View visual posture report: npx torusguard report --html"))
|
|
349
|
+
print(format_box_line(f"3. Inspect finding details: .torusguard/runs/{run_folder.name}/findings.md"))
|
|
350
|
+
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
329
351
|
|
|
330
352
|
return {
|
|
331
353
|
"status": "success",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard
|
|
3
3
|
description: "Universal autonomous security engine: 71 rules across 11 families, polyglot stack detection across 16+ languages, governed remediation (Ponytail Protocol <=35 add, <=25 del), SARIF v2.1.0 exports, single-file HTML dashboards, and persistent security memory context for modern web applications."
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# TorusGuard Master Security Router
|
|
@@ -15,12 +15,12 @@ from pathlib import Path
|
|
|
15
15
|
# Ensure UTF-8 stdout/stderr on Windows consoles
|
|
16
16
|
if sys.stdout and hasattr(sys.stdout, "reconfigure"):
|
|
17
17
|
try:
|
|
18
|
-
sys.stdout
|
|
18
|
+
getattr(sys.stdout, "reconfigure")(encoding="utf-8", errors="replace")
|
|
19
19
|
except Exception:
|
|
20
20
|
pass
|
|
21
21
|
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
|
|
22
22
|
try:
|
|
23
|
-
sys.stderr
|
|
23
|
+
getattr(sys.stderr, "reconfigure")(encoding="utf-8", errors="replace")
|
|
24
24
|
except Exception:
|
|
25
25
|
pass
|
|
26
26
|
|
|
@@ -57,12 +57,85 @@ def find_project_root(start_dir=None):
|
|
|
57
57
|
return current
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
import re
|
|
61
|
+
import unicodedata
|
|
62
|
+
|
|
63
|
+
ANSI_REGEX = re.compile(r'\033\[[0-9;]*m')
|
|
64
|
+
|
|
65
|
+
def get_visual_width(text: str) -> int:
|
|
66
|
+
"""Calculate the printable display width of a string (ANSI and emoji aware)."""
|
|
67
|
+
clean = ANSI_REGEX.sub('', text)
|
|
68
|
+
width = 0
|
|
69
|
+
for ch in clean:
|
|
70
|
+
ea = unicodedata.east_asian_width(ch)
|
|
71
|
+
if ea in ('W', 'F'):
|
|
72
|
+
width += 2
|
|
73
|
+
elif ord(ch) >= 0x1F300:
|
|
74
|
+
width += 2
|
|
75
|
+
else:
|
|
76
|
+
width += 1
|
|
77
|
+
return width
|
|
78
|
+
|
|
79
|
+
def truncate_visual(text: str, max_w: int = 67) -> str:
|
|
80
|
+
"""Truncate text visually without breaking ANSI escape codes."""
|
|
81
|
+
if get_visual_width(text) <= max_w:
|
|
82
|
+
return text
|
|
83
|
+
out, curr_w, in_ansi, ansi_buf = [], 0, False, ''
|
|
84
|
+
for ch in text:
|
|
85
|
+
if ch == '\033':
|
|
86
|
+
in_ansi = True
|
|
87
|
+
ansi_buf = ch
|
|
88
|
+
continue
|
|
89
|
+
if in_ansi:
|
|
90
|
+
ansi_buf += ch
|
|
91
|
+
if ch == 'm':
|
|
92
|
+
in_ansi = False
|
|
93
|
+
out.append(ansi_buf)
|
|
94
|
+
continue
|
|
95
|
+
ea = unicodedata.east_asian_width(ch)
|
|
96
|
+
cw = 2 if ea in ('W', 'F') or ord(ch) >= 0x1F300 else 1
|
|
97
|
+
if curr_w + cw > max_w - 3:
|
|
98
|
+
out.append('...')
|
|
99
|
+
break
|
|
100
|
+
out.append(ch)
|
|
101
|
+
curr_w += cw
|
|
102
|
+
return ''.join(out)
|
|
103
|
+
|
|
104
|
+
def card_line(content: str, max_w: int = 67, border: str = "│", border_color: str = CYAN) -> str:
|
|
105
|
+
"""Pad content so the card is exactly 75 visual columns total width."""
|
|
106
|
+
trunc = truncate_visual(content, max_w)
|
|
107
|
+
vis = get_visual_width(trunc)
|
|
108
|
+
pad = " " * max(0, max_w - vis)
|
|
109
|
+
return f" {border_color}{border}{RESET} {trunc}{pad} {border_color}{border}{RESET}"
|
|
110
|
+
|
|
111
|
+
def card_border_top(title: str = "", border_color: str = CYAN, double: bool = False) -> str:
|
|
112
|
+
left = "╔" if double else "┌"
|
|
113
|
+
right = "╗" if double else "┐"
|
|
114
|
+
h = "═" if double else "─"
|
|
115
|
+
if title:
|
|
116
|
+
vis = get_visual_width(title)
|
|
117
|
+
rem = max(0, 71 - 3 - vis - 1)
|
|
118
|
+
return f" {border_color}{left}{h} {BOLD}{WHITE}{title}{RESET}{border_color} {h * rem}{right}{RESET}"
|
|
119
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
120
|
+
|
|
121
|
+
def card_border_bottom(border_color: str = CYAN, double: bool = False) -> str:
|
|
122
|
+
left = "╚" if double else "└"
|
|
123
|
+
right = "╝" if double else "┘"
|
|
124
|
+
h = "═" if double else "─"
|
|
125
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
126
|
+
|
|
127
|
+
def card_divider(border_color: str = CYAN, double: bool = False) -> str:
|
|
128
|
+
left = "╠" if double else "├"
|
|
129
|
+
right = "╣" if double else "┤"
|
|
130
|
+
h = "═" if double else "─"
|
|
131
|
+
return f" {border_color}{left}{h * 71}{right}{RESET}"
|
|
132
|
+
|
|
60
133
|
def print_header():
|
|
61
134
|
"""Print the branded TorusGuard header card."""
|
|
62
135
|
print(f"""
|
|
63
136
|
{CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}
|
|
64
137
|
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
65
|
-
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}
|
|
138
|
+
{CYAN}│{RESET} {BOLD}{WHITE}🛡️ T O R U S G U A R D{RESET} {GRAY}v1.3.0{RESET} {CYAN}│{RESET}
|
|
66
139
|
{CYAN}│{RESET} {DIM}Autonomous Security Engine for AI-Built Applications{RESET} {CYAN}│{RESET}
|
|
67
140
|
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
68
141
|
{CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}
|
|
@@ -71,81 +144,71 @@ def print_header():
|
|
|
71
144
|
|
|
72
145
|
def print_step_1_assets(file_count):
|
|
73
146
|
"""Print the enhanced Step 1 asset unpacking card."""
|
|
74
|
-
print(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
""")
|
|
147
|
+
print(card_border_top("Step 1/3 ─── Unpacking Governance Assets"))
|
|
148
|
+
print(card_line(""))
|
|
149
|
+
print(card_line(f"{GREEN}✔{RESET} Canonical security rules {DIM}71 rules across 11 families{RESET}"))
|
|
150
|
+
print(card_line(f"{GREEN}✔{RESET} JSON validation schemas {DIM}8 formal schemas{RESET}"))
|
|
151
|
+
print(card_line(f"{GREEN}✔{RESET} Specialist agent specifications {DIM}5 isolated agents{RESET}"))
|
|
152
|
+
print(card_line(f"{GREEN}✔{RESET} Workflow templates {DIM}11 slash commands{RESET}"))
|
|
153
|
+
print(card_line(f"{GREEN}✔{RESET} Framework reference guides {DIM}10 security guides{RESET}"))
|
|
154
|
+
print(card_line(""))
|
|
155
|
+
print(card_line(f"{BOLD}{WHITE}{file_count} files unpacked{RESET}"))
|
|
156
|
+
print(card_border_bottom())
|
|
85
157
|
|
|
86
158
|
|
|
87
159
|
def print_step_2_profile(detected_stack=None):
|
|
88
160
|
"""Print the security profile & coverage card (in-process stack alignment)."""
|
|
161
|
+
print(card_border_top("Step 2/3 ─── Security Profile & Coverage"))
|
|
162
|
+
print(card_line(""))
|
|
163
|
+
print(card_line(f"{BOLD}Rule Families:{RESET}"))
|
|
164
|
+
print(card_line(f" {YELLOW}TG-SEC{RESET} Secrets & Credentials {YELLOW}TG-DB{RESET} Database Safety"))
|
|
165
|
+
print(card_line(f" {YELLOW}TG-INPUT{RESET} Input Validation {YELLOW}TG-AUTH{RESET} Authentication"))
|
|
166
|
+
print(card_line(f" {YELLOW}TG-CLIENT{RESET} Client Bundle Leaks {YELLOW}TG-DIFF{RESET} Diff Inspection"))
|
|
167
|
+
print(card_line(f" {YELLOW}TG-AGENT{RESET} AI Agent Security {YELLOW}TG-EDGE{RESET} Serverless"))
|
|
168
|
+
print(card_line(f" {YELLOW}TG-SUPPLY{RESET} Supply Chain & CI/CD {YELLOW}TG-SSRF{RESET} Outbound Net"))
|
|
169
|
+
print(card_line(f" {YELLOW}TG-BIZ{RESET} Business Logic"))
|
|
170
|
+
print(card_line(""))
|
|
171
|
+
print(card_line(f"{BOLD}Supported Stacks:{RESET}"))
|
|
172
|
+
print(card_line(f" {GREEN}Python{RESET} Django · FastAPI · Flask · DRF · SQLAlchemy"))
|
|
173
|
+
print(card_line(f" {GREEN}Node.js{RESET} Next.js · Express · React · Supabase · Firebase"))
|
|
174
|
+
print(card_line(""))
|
|
89
175
|
if detected_stack and detected_stack.get("framework") and detected_stack.get("framework") != "None":
|
|
90
176
|
fw = str(detected_stack.get("framework"))
|
|
91
177
|
lang = str(detected_stack.get("language", ""))
|
|
92
|
-
|
|
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}"
|
|
178
|
+
print(card_line(f"{GREEN}✔{RESET} {BOLD}Auto-aligned stack:{RESET} {WHITE}{fw}{RESET} {DIM}({lang}){RESET}"))
|
|
95
179
|
else:
|
|
96
|
-
|
|
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
|
-
""")
|
|
180
|
+
print(card_line(f"{DIM}ℹ Stack auto-detected on first /torusguard-audit run{RESET}"))
|
|
181
|
+
print(card_border_bottom())
|
|
115
182
|
|
|
116
183
|
|
|
117
184
|
def print_step_3_bridges(registered_ides):
|
|
118
185
|
"""Print the IDE command registration card."""
|
|
119
|
-
|
|
186
|
+
print(card_border_top("Step 3/3 ─── AI IDE Command Registration"))
|
|
187
|
+
print(card_line(""))
|
|
120
188
|
for r in registered_ides:
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
print(
|
|
124
|
-
{CYAN}│{RESET} {CYAN}│{RESET}
|
|
125
|
-
{lines} {CYAN}│{RESET} {CYAN}│{RESET}
|
|
126
|
-
{CYAN}└───────────────────────────────────────────────────────────────────────┘{RESET}
|
|
127
|
-
""")
|
|
189
|
+
print(card_line(f"{GREEN}✔{RESET} {r}"))
|
|
190
|
+
print(card_line(""))
|
|
191
|
+
print(card_border_bottom())
|
|
128
192
|
|
|
129
193
|
|
|
130
194
|
def print_success_card():
|
|
131
195
|
"""Print the final success card with next steps."""
|
|
132
|
-
print(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
""")
|
|
196
|
+
print(card_border_top("", border_color=GREEN, double=True))
|
|
197
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
198
|
+
print(card_line(f"{GREEN}{BOLD}✅ WORKSPACE INITIALIZED SUCCESSFULLY{RESET}", border="║", border_color=GREEN))
|
|
199
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
200
|
+
print(card_divider(border_color=GREEN, double=True))
|
|
201
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
202
|
+
print(card_line(f"{BOLD}Next Steps:{RESET}", border="║", border_color=GREEN))
|
|
203
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
204
|
+
print(card_line(f" {CYAN}1.{RESET} In AI Chat {WHITE}/torusguard-audit{RESET} or {WHITE}/torusguard{RESET}", border="║", border_color=GREEN))
|
|
205
|
+
print(card_line(f" {CYAN}2.{RESET} In Terminal {WHITE}npx torusguard status{RESET}", border="║", border_color=GREEN))
|
|
206
|
+
print(card_line(f" {CYAN}3.{RESET} In CI/CD {WHITE}npx torusguard audit{RESET}", border="║", border_color=GREEN))
|
|
207
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
208
|
+
print(card_line(f"{DIM}Docs{RESET} {CYAN}https://github.com/githubmofo/TorusGuard{RESET}", border="║", border_color=GREEN))
|
|
209
|
+
print(card_line(f"{DIM}NPM{RESET} {CYAN}https://npmjs.com/package/torusguard{RESET}", border="║", border_color=GREEN))
|
|
210
|
+
print(card_line("", border="║", border_color=GREEN))
|
|
211
|
+
print(card_border_bottom(border_color=GREEN, double=True))
|
|
149
212
|
|
|
150
213
|
|
|
151
214
|
def print_already_initialized(target_root, cfg):
|
|
@@ -157,7 +220,7 @@ def print_already_initialized(target_root, cfg):
|
|
|
157
220
|
|
|
158
221
|
{BOLD}▸ Project Root:{RESET} {GREEN}{target_root}{RESET}
|
|
159
222
|
{BOLD}▸ Workspace:{RESET} {GREEN}.torusguard/{RESET} {DIM}(Already Initialized){RESET}
|
|
160
|
-
{BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.
|
|
223
|
+
{BOLD}▸ Version:{RESET} {CYAN}{cfg.get('version', '1.3.0')}{RESET}
|
|
161
224
|
{BOLD}▸ Severity Floor:{RESET} {YELLOW}{cfg.get('severity_threshold', 'medium')}{RESET}
|
|
162
225
|
|
|
163
226
|
{DIM}To refresh templates or re-scaffold, run:{RESET}
|
|
@@ -358,23 +421,39 @@ Parse the requested action from `$ARGUMENTS` (e.g. `audit`, `verify`, `web-valid
|
|
|
358
421
|
3. Follow the phase execution steps defined in the workflow.
|
|
359
422
|
"""
|
|
360
423
|
|
|
361
|
-
# Ensure .agent/workflows and .
|
|
424
|
+
# Ensure .agents/workflows, .agents/skills, .agent/workflows, and .agent/skills are created
|
|
362
425
|
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
426
|
|
|
427
|
+
# 1. Antigravity / Gemini (.agents/)
|
|
428
|
+
agents_wf = target_root / ".agents" / "workflows"
|
|
429
|
+
agents_wf.mkdir(parents=True, exist_ok=True)
|
|
430
|
+
(agents_wf / "torusguard.md").write_text(tg_workflow_content, encoding="utf-8")
|
|
431
|
+
dest_workflow_dirs.append(agents_wf)
|
|
432
|
+
registered_ides.append("Antigravity / Gemini .agents/workflows/torusguard.md")
|
|
433
|
+
|
|
434
|
+
# 2. Tribunal Agent Kit (.agent/)
|
|
370
435
|
agent_wf = target_root / ".agent" / "workflows"
|
|
371
436
|
agent_wf.mkdir(parents=True, exist_ok=True)
|
|
372
437
|
(agent_wf / "torusguard.md").write_text(tg_workflow_content, encoding="utf-8")
|
|
373
438
|
dest_workflow_dirs.append(agent_wf)
|
|
374
439
|
registered_ides.append("Tribunal Agent Kit .agent/workflows/torusguard.md")
|
|
375
440
|
|
|
441
|
+
# Unpack Skills into .agents/skills and .agent/skills for full IDE resolution
|
|
442
|
+
src_skills = torusguard_target / "skills"
|
|
443
|
+
if src_skills.is_dir():
|
|
444
|
+
for dest_base in [target_root / ".agents" / "skills", target_root / ".agent" / "skills"]:
|
|
445
|
+
dest_base.mkdir(parents=True, exist_ok=True)
|
|
446
|
+
for skill_dir in src_skills.iterdir():
|
|
447
|
+
if skill_dir.is_dir():
|
|
448
|
+
target_skill_dir = dest_base / skill_dir.name
|
|
449
|
+
if not target_skill_dir.exists():
|
|
450
|
+
try:
|
|
451
|
+
shutil.copytree(skill_dir, target_skill_dir)
|
|
452
|
+
except Exception:
|
|
453
|
+
pass
|
|
454
|
+
|
|
376
455
|
# When full_commands is requested (e.g. via NPM package npx torusguard init),
|
|
377
|
-
# unlock and register all
|
|
456
|
+
# unlock and register all individual slash commands
|
|
378
457
|
if full_commands:
|
|
379
458
|
src_wf = torusguard_target / "workflows"
|
|
380
459
|
if src_wf.is_dir():
|
package/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
|
|
6
6
|
**Autonomous Security Guardrails, Governed Remediation, and Authorized Runtime Validation for AI-Built Web Applications.**
|
|
7
7
|
|
|
8
|
-
[](https://www.npmjs.com/package/torusguard)
|
|
9
|
+
[](https://github.com/githubmofo/TorusGuard/pkgs/npm/torusguard)
|
|
10
|
+
[](https://github.com/githubmofo/TorusGuard/releases/latest)
|
|
11
11
|
[](LICENSE)
|
|
12
12
|
[](https://python.org)
|
|
13
13
|
[](https://nodejs.org)
|
package/bin/torusguard.js
CHANGED
|
@@ -49,7 +49,7 @@ function printHelp() {
|
|
|
49
49
|
console.log(`
|
|
50
50
|
${CYAN}╭─────────────────────────────────────────────────────────────────────────╮${RESET}
|
|
51
51
|
${CYAN}│${RESET} ${CYAN}│${RESET}
|
|
52
|
-
${CYAN}│${RESET} ${BOLD}${WHITE}🛡️ T O R U S G U A R D C L I${RESET} ${GRAY}v1.3.
|
|
52
|
+
${CYAN}│${RESET} ${BOLD}${WHITE}🛡️ T O R U S G U A R D C L I${RESET} ${GRAY}v1.3.2${RESET} ${CYAN}│${RESET}
|
|
53
53
|
${CYAN}│${RESET} ${DIM}Autonomous Security Engine for AI-Built Applications${RESET} ${CYAN}│${RESET}
|
|
54
54
|
${CYAN}│${RESET} ${CYAN}│${RESET}
|
|
55
55
|
${CYAN}╰─────────────────────────────────────────────────────────────────────────╯${RESET}
|
|
@@ -142,7 +142,7 @@ if (command === 'status') {
|
|
|
142
142
|
console.log(`
|
|
143
143
|
${CYAN}╭─────────────────────────────────────────────────────────────────────────╮${RESET}
|
|
144
144
|
${CYAN}│${RESET} ${CYAN}│${RESET}
|
|
145
|
-
${CYAN}│${RESET} ${BOLD}${WHITE}🛡️ TORUSGUARD SECURITY POSTURE${RESET} ${GRAY}v1.3.
|
|
145
|
+
${CYAN}│${RESET} ${BOLD}${WHITE}🛡️ TORUSGUARD SECURITY POSTURE${RESET} ${GRAY}v1.3.2${RESET} ${CYAN}│${RESET}
|
|
146
146
|
${CYAN}│${RESET} ${CYAN}│${RESET}
|
|
147
147
|
${CYAN}╰─────────────────────────────────────────────────────────────────────────╯${RESET}
|
|
148
148
|
|
|
@@ -292,7 +292,7 @@ if (command === 'memory') {
|
|
|
292
292
|
if (command === 'diff-guard') {
|
|
293
293
|
const diffScript = path.join(cwd, '.torusguard', 'scripts', 'diff_guard.py');
|
|
294
294
|
const fallbackDiffScript = path.join(rootDir, '.torusguard', 'scripts', 'diff_guard.py');
|
|
295
|
-
const actualDiffScript = fs.existsSync(
|
|
295
|
+
const actualDiffScript = fs.existsSync(fallbackDiffScript) ? fallbackDiffScript : diffScript;
|
|
296
296
|
|
|
297
297
|
const proc = spawnSync(pythonCmd, [actualDiffScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
298
298
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -303,7 +303,7 @@ if (command === 'rules') {
|
|
|
303
303
|
const sub = args[1] || 'sync';
|
|
304
304
|
const rulesScript = path.join(cwd, '.torusguard', 'scripts', 'rules_sync.py');
|
|
305
305
|
const fallbackRulesScript = path.join(rootDir, '.torusguard', 'scripts', 'rules_sync.py');
|
|
306
|
-
const actualRulesScript = fs.existsSync(
|
|
306
|
+
const actualRulesScript = fs.existsSync(fallbackRulesScript) ? fallbackRulesScript : rulesScript;
|
|
307
307
|
|
|
308
308
|
let pyArgs = [actualRulesScript];
|
|
309
309
|
if (sub === 'sync') {
|
|
@@ -330,7 +330,7 @@ if (command === 'rules') {
|
|
|
330
330
|
if (command === 'audit') {
|
|
331
331
|
const auditScript = path.join(cwd, '.torusguard', 'scripts', 'audit_runner.py');
|
|
332
332
|
const fallbackAuditScript = path.join(rootDir, '.torusguard', 'scripts', 'audit_runner.py');
|
|
333
|
-
const actualAuditScript = fs.existsSync(
|
|
333
|
+
const actualAuditScript = fs.existsSync(fallbackAuditScript) ? fallbackAuditScript : auditScript;
|
|
334
334
|
|
|
335
335
|
const proc = spawnSync(pythonCmd, [actualAuditScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
336
336
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -341,7 +341,7 @@ if (command === 'report') {
|
|
|
341
341
|
if (args.includes('--html') || args[1] === 'html') {
|
|
342
342
|
const htmlScript = path.join(cwd, '.torusguard', 'scripts', 'html_reporter.py');
|
|
343
343
|
const fallbackHtmlScript = path.join(rootDir, '.torusguard', 'scripts', 'html_reporter.py');
|
|
344
|
-
const actualHtmlScript = fs.existsSync(
|
|
344
|
+
const actualHtmlScript = fs.existsSync(fallbackHtmlScript) ? fallbackHtmlScript : htmlScript;
|
|
345
345
|
|
|
346
346
|
let pyArgs = [actualHtmlScript];
|
|
347
347
|
const outIdx = args.indexOf('--out');
|
|
@@ -361,7 +361,7 @@ if (command === 'report') {
|
|
|
361
361
|
} else {
|
|
362
362
|
const sarifScript = path.join(cwd, '.torusguard', 'scripts', 'sarif_exporter.py');
|
|
363
363
|
const fallbackSarifScript = path.join(rootDir, '.torusguard', 'scripts', 'sarif_exporter.py');
|
|
364
|
-
const actualSarifScript = fs.existsSync(
|
|
364
|
+
const actualSarifScript = fs.existsSync(fallbackSarifScript) ? fallbackSarifScript : sarifScript;
|
|
365
365
|
|
|
366
366
|
const proc = spawnSync(pythonCmd, [actualSarifScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
367
367
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -372,7 +372,7 @@ if (command === 'report') {
|
|
|
372
372
|
if (command === 'harden') {
|
|
373
373
|
const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'harden_runner.py');
|
|
374
374
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'harden_runner.py');
|
|
375
|
-
const actualScript = fs.existsSync(
|
|
375
|
+
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
376
376
|
|
|
377
377
|
const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
378
378
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -382,7 +382,7 @@ if (command === 'harden') {
|
|
|
382
382
|
if (command === 'apply') {
|
|
383
383
|
const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
|
|
384
384
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
385
|
-
const actualScript = fs.existsSync(
|
|
385
|
+
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
386
386
|
|
|
387
387
|
const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
388
388
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -392,7 +392,7 @@ if (command === 'apply') {
|
|
|
392
392
|
if (command === 'rollback') {
|
|
393
393
|
const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'apply_runner.py');
|
|
394
394
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'apply_runner.py');
|
|
395
|
-
const actualScript = fs.existsSync(
|
|
395
|
+
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
396
396
|
|
|
397
397
|
const proc = spawnSync(pythonCmd, [actualScript, '--rollback', ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
398
398
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -402,7 +402,7 @@ if (command === 'rollback') {
|
|
|
402
402
|
if (command === 'recheck' || command === 'verify') {
|
|
403
403
|
const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recheck_runner.py');
|
|
404
404
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recheck_runner.py');
|
|
405
|
-
const actualScript = fs.existsSync(
|
|
405
|
+
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
406
406
|
|
|
407
407
|
const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
408
408
|
process.exit(proc.status !== null ? proc.status : 0);
|
|
@@ -412,7 +412,7 @@ if (command === 'recheck' || command === 'verify') {
|
|
|
412
412
|
if (command === 'recipes') {
|
|
413
413
|
const scriptPath = path.join(cwd, '.torusguard', 'scripts', 'recipes_runner.py');
|
|
414
414
|
const fallbackScript = path.join(rootDir, '.torusguard', 'scripts', 'recipes_runner.py');
|
|
415
|
-
const actualScript = fs.existsSync(
|
|
415
|
+
const actualScript = fs.existsSync(fallbackScript) ? fallbackScript : scriptPath;
|
|
416
416
|
|
|
417
417
|
const proc = spawnSync(pythonCmd, [actualScript, ...args.slice(1)], { stdio: 'inherit', cwd });
|
|
418
418
|
process.exit(proc.status !== null ? proc.status : 0);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard
|
|
3
3
|
description: "Universal autonomous security engine: 71 rules across 11 families, polyglot stack detection across 16+ languages, governed remediation (Ponytail Protocol <=35 add, <=25 del), SARIF v2.1.0 exports, single-file HTML dashboards, and persistent security memory context for modern web applications."
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# TorusGuard Master Security Router
|
|
@@ -141,11 +141,11 @@
|
|
|
141
141
|
"schemas/remediation.schema.json": "544904239f871dec9552973e01e09dc1f40dbb8330f20d98c319f3b24031d630",
|
|
142
142
|
"schemas/retest.schema.json": "97c0407ca4365a524020c430912f686f698cc0de90043194621071e9fc681cc7",
|
|
143
143
|
"schemas/runtime-evidence.schema.json": "929d2b094c1cd1f7f9db07be1eba5994e8002477eab3fd24140e1e973fd18cc1",
|
|
144
|
-
"scripts/apply_runner.py": "
|
|
144
|
+
"scripts/apply_runner.py": "2218a02538e821d88bb95211ae907705bea9b0533e43504912d5e3655f69de0c",
|
|
145
145
|
"scripts/audit_runner.py": "71553ca6c2d57df6f7323d41abc0216ecedb821fcf0eb5fd502290bd6dec6ae4",
|
|
146
146
|
"scripts/diff_guard.py": "0dc178cdde1ac0ae1a1ea4b2eb2ce1756b2126f6156909cf55d85249f115fcbd",
|
|
147
147
|
"scripts/finding_scorer.py": "885c7cbb7a8def2ceda0266e5a7f893938e41debc7501f7400a17a606b22dc23",
|
|
148
|
-
"scripts/harden_runner.py": "
|
|
148
|
+
"scripts/harden_runner.py": "cf813ec241db73de94fe76d4929dcfc6dd7826e50c84986c50fa28175717ed93",
|
|
149
149
|
"scripts/html_reporter.py": "f2bc401e9dfebda2c0e078897247210b2c8b83cba24add00e618821f9cae9eda",
|
|
150
150
|
"scripts/manifest_builder.py": "c0137927789a9e40a08243e630ac3c6990beb1b87807934e98906c8ad0b99a97",
|
|
151
151
|
"scripts/memory_engine.py": "b715d0ca030e95eb06ad6eda99027535df8a7e37d64897c023be6a291cf13dc6",
|
|
@@ -157,7 +157,7 @@
|
|
|
157
157
|
"scripts/safety_gate.py": "3379b7534b2590cf563717a448983ad8ed7e8f174f0ad65683d33e8a10f51558",
|
|
158
158
|
"scripts/sarif_exporter.py": "35c66637413afc4b72d5bd426638447c77231d2a8594dce5addceffd4803a20d",
|
|
159
159
|
"scripts/stack_detect.py": "7848006e3cd6e828f7dbe189a8c9728abd58e021ff02efbc4230ef8ca3444596",
|
|
160
|
-
"skills/torusguard/SKILL.md": "
|
|
160
|
+
"skills/torusguard/SKILL.md": "4b5cb127bea1f7e78e3452637c3e13117f8ece46de65ec791f2de3ef750e5bd5",
|
|
161
161
|
"skills/torusguard/bootstrap.py": "3d946f388cb4befa739c9d3b1404255d9a8ea5770ebbb79e5a1b9839589e9bc7",
|
|
162
162
|
"skills/torusguard/references/auth-and-sessions.md": "c3e4b9600cb53fc28c993922dc880accbec5783ac90d5ee98d63b52762d19525",
|
|
163
163
|
"skills/torusguard/references/business-logic-and-abuse.md": "58e87df2864d468af70c1ff39f5d540c687fa846b671457f85046ab2065fe99c",
|
|
@@ -68,10 +68,7 @@ def find_latest_audit_run(runs_dir: Path) -> Optional[Path]:
|
|
|
68
68
|
if not runs_dir.is_dir():
|
|
69
69
|
return None
|
|
70
70
|
runs = sorted(runs_dir.glob("run-*-audit"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
71
|
-
|
|
72
|
-
if (r / "bundles").is_dir() and list((r / "bundles").iterdir()):
|
|
73
|
-
return r
|
|
74
|
-
return None
|
|
71
|
+
return runs[0] if runs else None
|
|
75
72
|
|
|
76
73
|
|
|
77
74
|
def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> None:
|
|
@@ -102,7 +99,7 @@ def execute_rollback(target_root: Path, run_id_arg: Optional[str] = None) -> Non
|
|
|
102
99
|
restored_files.append(orig_rel)
|
|
103
100
|
|
|
104
101
|
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
105
|
-
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD ROLLBACK RESTORATION v1.3.
|
|
102
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD ROLLBACK RESTORATION v1.3.1{RESET}", border_color=CYAN))
|
|
106
103
|
print(format_box_line(f"{DIM}Restored original files from pre-apply snapshot{RESET}", border_color=CYAN))
|
|
107
104
|
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
108
105
|
|
|
@@ -123,18 +120,28 @@ def execute_apply(target_root: Path, run_id_arg: Optional[str] = None, auto_appr
|
|
|
123
120
|
run_folder = find_latest_audit_run(runs_dir)
|
|
124
121
|
|
|
125
122
|
if not run_folder or not run_folder.is_dir():
|
|
126
|
-
print(f"\n {RED}✖ No
|
|
127
|
-
print(f" Run {CYAN}npx torusguard
|
|
123
|
+
print(f"\n {RED}✖ No audit runs found.{RESET}")
|
|
124
|
+
print(f" Run {CYAN}npx torusguard audit{RESET} followed by {CYAN}npx torusguard harden{RESET} first.\n")
|
|
128
125
|
sys.exit(1)
|
|
129
126
|
|
|
130
127
|
bundles_dir = run_folder / "bundles"
|
|
131
|
-
|
|
132
|
-
print(f"\n {RED}✖ Missing bundles directory in run folder: {run_folder}{RESET}\n")
|
|
133
|
-
sys.exit(1)
|
|
134
|
-
|
|
135
|
-
bundle_meta_files = list(bundles_dir.glob("*/metadata.json"))
|
|
128
|
+
bundle_meta_files = list(bundles_dir.glob("*/metadata.json")) if bundles_dir.is_dir() else []
|
|
136
129
|
if not bundle_meta_files:
|
|
137
|
-
print(f"\n {
|
|
130
|
+
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
131
|
+
print(f" {CYAN}│ │{RESET}")
|
|
132
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED PATCH APPLIER v1.3.1{RESET}", border_color=CYAN))
|
|
133
|
+
print(format_box_line(f"{DIM}Human-Gate Authorization, Snapshots & Golden Recipe Distillation{RESET}", border_color=CYAN))
|
|
134
|
+
print(f" {CYAN}│ │{RESET}")
|
|
135
|
+
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
136
|
+
|
|
137
|
+
print(f" {YELLOW}ℹ Zero candidate remediation bundles found in run: {run_folder.name}{RESET}")
|
|
138
|
+
print(f" {GRAY}The findings in this run require manual review or AI-assisted remediation.{RESET}\n")
|
|
139
|
+
print(f" {CYAN}┌─ Recommended Next Steps ────────────────────────────────────────────────┐{RESET}")
|
|
140
|
+
print(format_box_line("1. In AI Chat (Antigravity/Cursor/Claude): Run /torusguard-harden"))
|
|
141
|
+
print(format_box_line(" to formulate custom architectural patches with your AI agent."))
|
|
142
|
+
print(format_box_line(f"2. Inspect findings: .torusguard/runs/{run_folder.name}/findings.md"))
|
|
143
|
+
print(format_box_line("3. View visual posture report: npx torusguard report --html"))
|
|
144
|
+
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
138
145
|
sys.exit(0)
|
|
139
146
|
|
|
140
147
|
bundles = []
|
|
@@ -132,7 +132,7 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
|
|
|
132
132
|
applied_rule = True
|
|
133
133
|
fix_explanation = f"Replaced hardcoded credential with os.environ.get('{env_key}')"
|
|
134
134
|
|
|
135
|
-
# Strategy 2: Dangerous React HTML rendering (TG-INPUT-003)
|
|
135
|
+
# Strategy 2: Dangerous React HTML rendering & DOM innerHTML (TG-INPUT-003)
|
|
136
136
|
elif rule_id == "TG-INPUT-003":
|
|
137
137
|
if "dangerouslySetInnerHTML" in target_line:
|
|
138
138
|
# <div dangerouslySetInnerHTML={{ __html: bio }} /> -> <div>{bio}</div>
|
|
@@ -142,6 +142,21 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
|
|
|
142
142
|
new_lines[line_number - 1] = replacement
|
|
143
143
|
applied_rule = True
|
|
144
144
|
fix_explanation = "Replaced raw dangerouslySetInnerHTML injection with safe React text interpolation"
|
|
145
|
+
elif ".innerHTML" in target_line:
|
|
146
|
+
# Pattern A: el.innerHTML = '' or "" -> el.textContent = ""
|
|
147
|
+
if re.search(r'\.innerHTML\s*=\s*["\']["\']', target_line):
|
|
148
|
+
fixed_line = re.sub(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*["\']["\']', r'\1.textContent = ""', target_line)
|
|
149
|
+
if fixed_line != target_line:
|
|
150
|
+
new_lines[line_number - 1] = fixed_line
|
|
151
|
+
applied_rule = True
|
|
152
|
+
fix_explanation = "Replaced unsafe innerHTML DOM reset with safe textContent assignment"
|
|
153
|
+
# Pattern B: el.innerHTML = someVar -> el.textContent = someVar
|
|
154
|
+
elif re.search(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*([a-zA-Z0-9_$.]+)\s*;?$', target_line):
|
|
155
|
+
fixed_line = re.sub(r'([a-zA-Z0-9_$]+)\.innerHTML\s*=\s*([a-zA-Z0-9_$.]+)', r'\1.textContent = \2', target_line)
|
|
156
|
+
if fixed_line != target_line:
|
|
157
|
+
new_lines[line_number - 1] = fixed_line
|
|
158
|
+
applied_rule = True
|
|
159
|
+
fix_explanation = "Replaced raw DOM innerHTML assignment with XSS-safe textContent"
|
|
145
160
|
|
|
146
161
|
# Strategy 3: Raw SQL Concatenation (TG-INPUT-002)
|
|
147
162
|
elif rule_id == "TG-INPUT-002":
|
|
@@ -298,7 +313,7 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
|
|
|
298
313
|
# Print 75-column Terminal Card
|
|
299
314
|
print(f"\n {CYAN}╭─────────────────────────────────────────────────────────────────────────╮{RESET}")
|
|
300
315
|
print(f" {CYAN}│ │{RESET}")
|
|
301
|
-
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE v1.3.
|
|
316
|
+
print(format_box_line(f"{BOLD}🛡️ TORUSGUARD GOVERNED REMEDIATION ENGINE v1.3.1{RESET}", border_color=CYAN))
|
|
302
317
|
print(format_box_line(f"{DIM}Autonomous Minimal Patch Formulation & Ponytail Packaging{RESET}", border_color=CYAN))
|
|
303
318
|
print(f" {CYAN}│ │{RESET}")
|
|
304
319
|
print(f" {CYAN}╰─────────────────────────────────────────────────────────────────────────╯{RESET}\n")
|
|
@@ -318,14 +333,21 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
|
|
|
318
333
|
if len(bundles) > 5:
|
|
319
334
|
print(format_box_line(f" ... and {len(bundles) - 5} more patches cataloged in remediation.md"))
|
|
320
335
|
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
321
|
-
else:
|
|
322
|
-
print(f" {YELLOW}ℹ No automatic patch templates matched current findings.{RESET}\n")
|
|
323
336
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
337
|
+
print(f" {GREEN}╔═ Next Governed Action ═════════════════════════════════════════════════════╗{RESET}")
|
|
338
|
+
print(format_box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="║", border_color=GREEN))
|
|
339
|
+
print(format_box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="║", border_color=GREEN))
|
|
340
|
+
print(format_box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="║", border_color=GREEN))
|
|
341
|
+
print(f" {GREEN}╚═════════════════════════════════════════════════════════════════════════════╝{RESET}\n")
|
|
342
|
+
else:
|
|
343
|
+
print(f" {YELLOW}ℹ No automatic patch templates matched current findings.{RESET}")
|
|
344
|
+
print(f" {GRAY}Findings require architectural refactoring or AI-assisted guidance.{RESET}\n")
|
|
345
|
+
print(f" {CYAN}┌─ Recommended Next Steps ────────────────────────────────────────────────┐{RESET}")
|
|
346
|
+
print(format_box_line("1. In AI Chat (Antigravity/Cursor/Claude): Run /torusguard-harden"))
|
|
347
|
+
print(format_box_line(" to synthesize custom architectural fixes with your AI agent."))
|
|
348
|
+
print(format_box_line("2. View visual posture report: npx torusguard report --html"))
|
|
349
|
+
print(format_box_line(f"3. Inspect finding details: .torusguard/runs/{run_folder.name}/findings.md"))
|
|
350
|
+
print(f" {CYAN}└─────────────────────────────────────────────────────────────────────────┘{RESET}\n")
|
|
329
351
|
|
|
330
352
|
return {
|
|
331
353
|
"status": "success",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: torusguard
|
|
3
3
|
description: "Universal autonomous security engine: 71 rules across 11 families, polyglot stack detection across 16+ languages, governed remediation (Ponytail Protocol <=35 add, <=25 del), SARIF v2.1.0 exports, single-file HTML dashboards, and persistent security memory context for modern web applications."
|
|
4
|
-
version: 1.3.
|
|
4
|
+
version: 1.3.2
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# TorusGuard Master Security Router
|