torusguard 1.3.1 → 1.3.3

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",
@@ -231,7 +231,7 @@
231
231
  <header>
232
232
  <div class="logo-group">
233
233
  <h1><span class="shield-icon">&#128737;</span> TorusGuard Security Posture</h1>
234
- <div class="project-meta">Workspace: <strong>TorusGuard</strong> &middot; Stack: <strong>TypeScript</strong> &middot; 2026-09-08 11:54:16 IST</div>
234
+ <div class="project-meta">Workspace: <strong>TorusGuard</strong> &middot; Stack: <strong>TypeScript</strong> &middot; 2026-09-08 14:07:32 IST</div>
235
235
  </div>
236
236
  <div class="status-badge">&check; Local Governance Active</div>
237
237
  </header>
@@ -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",
@@ -131,7 +131,12 @@ def generate_html_dashboard(telemetry: dict[str, Any]) -> str:
131
131
  stack = [detected["language"]]
132
132
  if detected.get("framework") and detected["framework"] != "None":
133
133
  stack.append(detected["framework"])
134
- stack_label = ", ".join(stack) if stack else "Universal Polyglot Application"
134
+ if stack:
135
+ stack_label = ", ".join(stack)
136
+ else:
137
+ # Avoid hardcoding "Universal Polyglot Application" if stack is empty
138
+ detected_lang = telemetry.get("detected_stack", {}).get("language", "Unknown")
139
+ stack_label = detected_lang if detected_lang != "Unknown" else "Polyglot Application"
135
140
 
136
141
  recipes = [p for p in patterns if p.get("pattern_type") == "golden_fix_recipe"]
137
142
  regressions = [p for p in patterns if p.get("pattern_type") == "regression_watch"]
@@ -208,7 +213,7 @@ def generate_html_dashboard(telemetry: dict[str, Any]) -> str:
208
213
  regression_rows_html = "<tr><td colspan='3' class='empty-cell'>Zero active security regressions or suppressions. Posture clean.</td></tr>"
209
214
 
210
215
  stack_info = telemetry.get("detected_stack", {})
211
- primary_lang = stack_info.get("language", "Universal")
216
+ primary_lang = stack_info.get("language", "Unknown")
212
217
  if primary_lang in ("Unknown", "None", "", None):
213
218
  root_path = Path(telemetry.get("root_dir", "."))
214
219
  if (root_path / "tsconfig.json").is_file() or list(root_path.glob("*.ts")) or list((root_path / "src").glob("*.ts*")):
@@ -220,7 +225,7 @@ def generate_html_dashboard(telemetry: dict[str, Any]) -> str:
220
225
  elif (root_path / "go.mod").is_file():
221
226
  primary_lang = "Go"
222
227
  else:
223
- primary_lang = "Universal Polyglot"
228
+ primary_lang = "Unknown"
224
229
 
225
230
  primary_fw = stack_info.get("framework", "None")
226
231
  primary_orm = stack_info.get("data_layer", "None")