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.
@@ -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": "f5f9406b01b56070fea86e524c04b16255998d88e67e3be9e2517fb52aba82a8",
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": "1384b74d57df0bd7f50930128e28aba5245a72795ac876aa1065d8ae83a03045",
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": "595008bf365b77729118b0312ec636fb72668ee57f458087ed555a00e1d3db6b",
161
- "skills/torusguard/bootstrap.py": "20cf16a30fc26cfa0e585708c103ab481751eebec569b61907f1d0b83bad7d99",
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
- for r in runs:
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.0{RESET}", border_color=CYAN))
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 candidate remediation bundles found to apply.{RESET}")
127
- print(f" Run {CYAN}npx torusguard harden{RESET} first to formulate candidate patches.\n")
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
- if not bundles_dir.is_dir():
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 {YELLOW}ℹ Zero candidate bundles available in {run_folder.name}.{RESET}\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.0{RESET}", border_color=CYAN))
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
- print(f" {GREEN}╔═ Next Governed Action ═════════════════════════════════════════════════════╗{RESET}")
325
- print(format_box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="║", border_color=GREEN))
326
- print(format_box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="║", border_color=GREEN))
327
- print(format_box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="║", border_color=GREEN))
328
- print(f" {GREEN}╚═════════════════════════════════════════════════════════════════════════════╝{RESET}\n")
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.1
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.reconfigure(encoding="utf-8", errors="replace")
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.reconfigure(encoding="utf-8", errors="replace")
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}v0.9.5{RESET} {CYAN}│{RESET}
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(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
- """)
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
- 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}"
178
+ print(card_line(f"{GREEN}✔{RESET} {BOLD}Auto-aligned stack:{RESET} {WHITE}{fw}{RESET} {DIM}({lang}){RESET}"))
95
179
  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
- """)
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
- lines = ""
186
+ print(card_border_top("Step 3/3 ─── AI IDE Command Registration"))
187
+ print(card_line(""))
120
188
  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
- """)
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(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
- """)
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.0.0')}{RESET}
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 .agents/workflows are created
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 11 individual slash commands
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
- [![npm version](https://img.shields.io/badge/npm-v1.3.1-cb3837.svg?style=flat-square&logo=npm)](https://www.npmjs.com/package/torusguard)
9
- [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-v1.3.1-181717.svg?style=flat-square&logo=github)](https://github.com/githubmofo/TorusGuard/pkgs/npm/torusguard)
10
- [![Release](https://img.shields.io/badge/Release-v1.3.1-blue.svg?style=flat-square)](https://github.com/githubmofo/TorusGuard/releases/latest)
8
+ [![npm version](https://img.shields.io/badge/npm-v1.3.2-cb3837.svg?style=flat-square&logo=npm)](https://www.npmjs.com/package/torusguard)
9
+ [![GitHub Packages](https://img.shields.io/badge/GitHub%20Packages-v1.3.2-181717.svg?style=flat-square&logo=github)](https://github.com/githubmofo/TorusGuard/pkgs/npm/torusguard)
10
+ [![Release](https://img.shields.io/badge/Release-v1.3.2-blue.svg?style=flat-square)](https://github.com/githubmofo/TorusGuard/releases/latest)
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=flat-square)](LICENSE)
12
12
  [![Python 3.10+](https://img.shields.io/badge/Python-3.10%2B-blue.svg?style=flat-square&logo=python&logoColor=white)](https://python.org)
13
13
  [![Node.js 18+](https://img.shields.io/badge/Node.js-18%2B-339933.svg?style=flat-square&logo=node.js&logoColor=white)](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.1${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.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.1${RESET} ${CYAN}│${RESET}
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(diffScript) ? diffScript : fallbackDiffScript;
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(rulesScript) ? rulesScript : fallbackRulesScript;
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(auditScript) ? auditScript : fallbackAuditScript;
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(htmlScript) ? htmlScript : fallbackHtmlScript;
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(sarifScript) ? sarifScript : fallbackSarifScript;
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(scriptPath) ? scriptPath : fallbackScript;
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(scriptPath) ? scriptPath : fallbackScript;
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(scriptPath) ? scriptPath : fallbackScript;
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(scriptPath) ? scriptPath : fallbackScript;
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(scriptPath) ? scriptPath : fallbackScript;
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,6 +1,6 @@
1
1
  {
2
2
  "name": "torusguard",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "Security guardrails, governed remediation, and authorized runtime validation for AI-built web applications.",
5
5
  "main": "bin/torusguard.js",
6
6
  "bin": {
@@ -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.1
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": "f5f9406b01b56070fea86e524c04b16255998d88e67e3be9e2517fb52aba82a8",
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": "1384b74d57df0bd7f50930128e28aba5245a72795ac876aa1065d8ae83a03045",
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": "595008bf365b77729118b0312ec636fb72668ee57f458087ed555a00e1d3db6b",
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
- for r in runs:
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.0{RESET}", border_color=CYAN))
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 candidate remediation bundles found to apply.{RESET}")
127
- print(f" Run {CYAN}npx torusguard harden{RESET} first to formulate candidate patches.\n")
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
- if not bundles_dir.is_dir():
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 {YELLOW}ℹ Zero candidate bundles available in {run_folder.name}.{RESET}\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.0{RESET}", border_color=CYAN))
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
- print(f" {GREEN}╔═ Next Governed Action ═════════════════════════════════════════════════════╗{RESET}")
325
- print(format_box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="║", border_color=GREEN))
326
- print(format_box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="║", border_color=GREEN))
327
- print(format_box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="║", border_color=GREEN))
328
- print(f" {GREEN}╚═════════════════════════════════════════════════════════════════════════════╝{RESET}\n")
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.1
4
+ version: 1.3.2
5
5
  ---
6
6
 
7
7
  # TorusGuard Master Security Router