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.
- package/.torusguard/.manifest.json +4 -4
- package/.torusguard/runs/report-latest.html +1 -1
- package/.torusguard/scripts/apply_runner.py +20 -13
- package/.torusguard/scripts/harden_runner.py +31 -9
- package/.torusguard/scripts/html_reporter.py +8 -3
- package/.torusguard/scripts/stack_detect.py +608 -584
- 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/scripts/html_reporter.py +8 -3
- package/skills/torusguard/payload/scripts/stack_detect.py +608 -584
- package/skills/torusguard/payload/skills/torusguard/SKILL.md +1 -1
- package/skills/torusguard-apply/SKILL.md +8 -6
- package/skills/torusguard-audit/SKILL.md +5 -0
- package/skills/torusguard-authorize/SKILL.md +4 -3
- package/skills/torusguard-exploit-check/SKILL.md +5 -4
- package/skills/torusguard-harden/SKILL.md +11 -7
- package/skills/torusguard-init/SKILL.md +6 -5
- package/skills/torusguard-recheck/SKILL.md +8 -7
- package/skills/torusguard-report/SKILL.md +6 -5
- package/skills/torusguard-status/SKILL.md +7 -6
- package/skills/torusguard-verify/SKILL.md +7 -6
- package/skills/torusguard-web-validate/SKILL.md +7 -6
|
@@ -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",
|
|
@@ -231,7 +231,7 @@
|
|
|
231
231
|
<header>
|
|
232
232
|
<div class="logo-group">
|
|
233
233
|
<h1><span class="shield-icon">🛡</span> TorusGuard Security Posture</h1>
|
|
234
|
-
<div class="project-meta">Workspace: <strong>TorusGuard</strong> · Stack: <strong>TypeScript</strong> · 2026-09-08
|
|
234
|
+
<div class="project-meta">Workspace: <strong>TorusGuard</strong> · Stack: <strong>TypeScript</strong> · 2026-09-08 14:07:32 IST</div>
|
|
235
235
|
</div>
|
|
236
236
|
<div class="status-badge">✓ 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
|
-
|
|
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",
|
|
@@ -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
|
-
|
|
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", "
|
|
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 = "
|
|
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")
|