torusguard 1.3.6 โ†’ 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/.torusguard/.manifest.json +9 -8
  2. package/.torusguard/runs/report-latest.html +3529 -364
  3. package/.torusguard/runs/run-20260918-105940-audit/manifest.json +2 -1
  4. package/.torusguard/runs/run-20260918-105940-audit/remediation.md +7 -0
  5. package/.torusguard/runs/run-20260919-145701-audit/findings.json +1 -0
  6. package/.torusguard/runs/run-20260919-145701-audit/findings.md +8 -0
  7. package/.torusguard/runs/run-20260919-145701-audit/manifest.json +12 -0
  8. package/.torusguard/runs/run-20260919-145701-audit/summary.md +5 -0
  9. package/.torusguard/runs/run-20260919-151729-audit/findings.json +1 -0
  10. package/.torusguard/runs/run-20260919-151729-audit/findings.md +8 -0
  11. package/.torusguard/runs/run-20260919-151729-audit/manifest.json +13 -0
  12. package/.torusguard/runs/run-20260919-151729-audit/remediation.md +7 -0
  13. package/.torusguard/runs/run-20260919-151729-audit/summary.md +5 -0
  14. package/.torusguard/runs/run-20260919-152340-audit/findings.json +1 -0
  15. package/.torusguard/runs/run-20260919-152340-audit/findings.md +8 -0
  16. package/.torusguard/runs/run-20260919-152340-audit/manifest.json +12 -0
  17. package/.torusguard/runs/run-20260919-152340-audit/summary.md +5 -0
  18. package/.torusguard/runs/run-20260919-152345-audit/findings.json +1 -0
  19. package/.torusguard/runs/run-20260919-152345-audit/findings.md +8 -0
  20. package/.torusguard/runs/run-20260919-152345-audit/manifest.json +12 -0
  21. package/.torusguard/runs/run-20260919-152345-audit/summary.md +5 -0
  22. package/.torusguard/runs/run-20260919-153500-audit/findings.json +1 -0
  23. package/.torusguard/runs/run-20260919-153500-audit/findings.md +8 -0
  24. package/.torusguard/runs/run-20260919-153500-audit/manifest.json +12 -0
  25. package/.torusguard/runs/run-20260919-153500-audit/summary.md +5 -0
  26. package/.torusguard/scripts/__pycache__/apply_runner.cpython-314.pyc +0 -0
  27. package/.torusguard/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  28. package/.torusguard/scripts/__pycache__/harden_runner.cpython-314.pyc +0 -0
  29. package/.torusguard/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  30. package/.torusguard/scripts/__pycache__/manifest_builder.cpython-314.pyc +0 -0
  31. package/.torusguard/scripts/__pycache__/recipes_runner.cpython-314.pyc +0 -0
  32. package/.torusguard/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  33. package/.torusguard/scripts/apply_runner.py +101 -8
  34. package/.torusguard/scripts/audit_runner.py +116 -11
  35. package/.torusguard/scripts/harden_runner.py +122 -68
  36. package/.torusguard/scripts/html_reporter.py +1170 -253
  37. package/.torusguard/scripts/manifest_builder.py +2 -0
  38. package/.torusguard/scripts/recheck_runner.py +2 -1
  39. package/.torusguard/scripts/recipes_runner.py +91 -8
  40. package/.torusguard/scripts/report_sync.py +134 -7
  41. package/.torusguard/scripts/status_runner.py +267 -0
  42. package/README.md +108 -80
  43. package/bin/torusguard.js +190 -20
  44. package/package.json +6 -2
  45. package/skills/torusguard/__pycache__/bootstrap.cpython-314.pyc +0 -0
  46. package/skills/torusguard/bootstrap.py +45 -6
  47. package/skills/torusguard/payload/.manifest.json +29 -14
  48. package/skills/torusguard/payload/scripts/__pycache__/audit_runner.cpython-314.pyc +0 -0
  49. package/skills/torusguard/payload/scripts/__pycache__/html_reporter.cpython-314.pyc +0 -0
  50. package/skills/torusguard/payload/scripts/__pycache__/report_sync.cpython-314.pyc +0 -0
  51. package/skills/torusguard/payload/scripts/__pycache__/sarif_exporter.cpython-314.pyc +0 -0
  52. package/skills/torusguard/payload/scripts/__pycache__/term_ui.cpython-314.pyc +0 -0
  53. package/skills/torusguard/payload/scripts/apply_runner.py +101 -8
  54. package/skills/torusguard/payload/scripts/audit_runner.py +116 -11
  55. package/skills/torusguard/payload/scripts/harden_runner.py +122 -68
  56. package/skills/torusguard/payload/scripts/html_reporter.py +1170 -253
  57. package/skills/torusguard/payload/scripts/manifest_builder.py +2 -0
  58. package/skills/torusguard/payload/scripts/recheck_runner.py +2 -1
  59. package/skills/torusguard/payload/scripts/recipes_runner.py +91 -8
  60. package/skills/torusguard/payload/scripts/report_sync.py +134 -7
  61. package/skills/torusguard/payload/scripts/status_runner.py +267 -0
  62. package/skills/torusguard/payload/workflows/apply.md +9 -1
  63. package/skills/torusguard/payload/workflows/audit.md +8 -2
  64. package/skills/torusguard/payload/workflows/harden.md +6 -1
  65. package/skills/torusguard/payload/workflows/init.md +7 -1
  66. package/skills/torusguard/payload/workflows/recipes.md +55 -0
  67. package/skills/torusguard/payload/workflows/report.md +59 -55
  68. package/skills/torusguard/payload/workflows/status.md +4 -1
  69. package/skills/torusguard/payload/workflows/torusguard-apply.md +110 -0
  70. package/skills/torusguard/payload/workflows/torusguard-audit.md +105 -0
  71. package/skills/torusguard/payload/workflows/torusguard-authorize.md +96 -0
  72. package/skills/torusguard/payload/workflows/torusguard-exploit-check.md +98 -0
  73. package/skills/torusguard/payload/workflows/torusguard-harden.md +104 -0
  74. package/skills/torusguard/payload/workflows/torusguard-init.md +106 -0
  75. package/skills/torusguard/payload/workflows/torusguard-recheck.md +101 -0
  76. package/skills/torusguard/payload/workflows/torusguard-recipes.md +55 -0
  77. package/skills/torusguard/payload/workflows/torusguard-report.md +105 -0
  78. package/skills/torusguard/payload/workflows/torusguard-status.md +103 -0
  79. package/skills/torusguard/payload/workflows/torusguard-verify.md +100 -0
  80. package/skills/torusguard/payload/workflows/torusguard-web-validate.md +96 -0
  81. package/skills/torusguard/payload/workflows/torusguard.md +19 -0
@@ -147,6 +147,15 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
147
147
  new_lines[line_number - 1] = replacement
148
148
  applied_rule = True
149
149
  fix_explanation = f"Replaced hardcoded credential with os.environ.get('{env_key}')"
150
+ elif file_path.endswith(".go"):
151
+ if re.search(r'(:=|=)\s*["\'][A-Za-z0-9_\-]{8,}["\']', target_line):
152
+ var_match = re.search(r'([A-Za-z0-9_]+)\s*[:=]', target_line)
153
+ var_name = var_match.group(1) if var_match else "apiKey"
154
+ env_key = re.sub(r'(?<!^)(?=[A-Z])', '_', var_name).upper()
155
+ replacement = f'{indent}{var_name} := os.Getenv("{env_key}")'
156
+ new_lines[line_number - 1] = replacement
157
+ applied_rule = True
158
+ fix_explanation = f"Replaced hardcoded Go credential with os.Getenv('{env_key}')"
150
159
 
151
160
  # Strategy 2: Dangerous React HTML rendering & DOM innerHTML (TG-INPUT-003)
152
161
  elif rule_id == "TG-INPUT-003":
@@ -187,6 +196,13 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
187
196
  new_lines[line_number - 1] = fixed_line
188
197
  applied_rule = True
189
198
  fix_explanation = "Replaced template literal SQL string interpolation with parameterized SQL placeholder (?)"
199
+ elif file_path.endswith(".go") and ("fmt.Sprintf(" in target_line or " + " in target_line):
200
+ if any(w in target_line for w in ("SELECT", "INSERT", "UPDATE", "DELETE")):
201
+ fixed_line = re.sub(r'fmt\.Sprintf\(\s*["\'](SELECT.*?WHERE.*?%s.*?)["\'],\s*([A-Za-z0-9_$.]+)\)', r'/* parameterized */ "\1", \2', target_line)
202
+ if fixed_line != target_line:
203
+ new_lines[line_number - 1] = fixed_line
204
+ applied_rule = True
205
+ fix_explanation = "Replaced unsafe Go SQL fmt.Sprintf concatenation with parameterized placeholder"
190
206
 
191
207
  # Strategy 4: Multi-Tenant Query Scoping (TG-DB-004)
192
208
  elif rule_id == "TG-DB-004":
@@ -246,6 +262,12 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
246
262
  new_lines[line_number - 1] = fixed_line
247
263
  applied_rule = True
248
264
  fix_explanation = "Wrapped dynamic path segment in os.path.basename to neutralize path traversal sequences"
265
+ elif file_path.endswith(".go") and "filepath.Join(" in target_line and "filepath.Base" not in target_line:
266
+ fixed_line = re.sub(r'filepath\.Join\((.*?),\s*([a-zA-Z0-9_$.]+)\)', r'filepath.Join(\1, filepath.Base(\2))', target_line)
267
+ if fixed_line != target_line:
268
+ new_lines[line_number - 1] = fixed_line
269
+ applied_rule = True
270
+ fix_explanation = "Wrapped dynamic path segment in filepath.Base to neutralize Go path traversal"
249
271
 
250
272
  # Strategy 8: TLS Verification Bypass (TG-DIFF-001)
251
273
  elif rule_id == "TG-DIFF-001":
@@ -265,6 +287,22 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
265
287
  applied_rule = True
266
288
  fix_explanation = "Restored mandatory TLS certificate validation (InsecureSkipVerify: false)"
267
289
 
290
+ # Strategy 9: Unbounded HTTP Client Timeout / SSRF (TG-SSRF-004)
291
+ elif rule_id == "TG-SSRF-004":
292
+ if file_path.endswith(".go") and ("&http.Client{}" in target_line or "http.DefaultClient" in target_line):
293
+ fixed_line = target_line.replace("&http.Client{}", "&http.Client{Timeout: 10 * time.Second}")
294
+ fixed_line = fixed_line.replace("http.DefaultClient", "&http.Client{Timeout: 10 * time.Second}")
295
+ if fixed_line != target_line:
296
+ new_lines[line_number - 1] = fixed_line
297
+ applied_rule = True
298
+ fix_explanation = "Injected explicit 10s timeout to Go http.Client to prevent resource exhaustion"
299
+ elif file_path.endswith(".py") and "requests.get(" in target_line and "timeout=" not in target_line:
300
+ fixed_line = re.sub(r'requests\.get\((.*?)\)', r'requests.get(\1, timeout=10)', target_line)
301
+ if fixed_line != target_line:
302
+ new_lines[line_number - 1] = fixed_line
303
+ applied_rule = True
304
+ fix_explanation = "Injected explicit timeout=10 to requests.get call"
305
+
268
306
 
269
307
  # Strategy 9: Hardcoded Database / Service Credential in Config (TG-SEC-003)
270
308
  elif rule_id == "TG-SEC-003":
@@ -466,7 +504,7 @@ def generate_patch_for_finding(finding: Dict[str, Any], target_root: Path) -> Op
466
504
  }
467
505
 
468
506
 
469
- def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[str, Any]:
507
+ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None, severity_floor: str = "medium", dry_run: bool = False) -> Dict[str, Any]:
470
508
  """Execute autonomous remediation formulation and package candidate bundles."""
471
509
  runs_dir = target_root / ".torusguard" / "runs"
472
510
  if run_id_arg:
@@ -490,8 +528,14 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
490
528
  print(f"\n {RED}โœ– Failed to parse findings.json:{RESET} {e}\n")
491
529
  sys.exit(1)
492
530
 
531
+ # Severity filtering
532
+ SEV_RANKS = {"critical": 4, "high": 3, "medium": 2, "low": 1}
533
+ min_rank = SEV_RANKS.get(severity_floor.lower(), 2)
534
+ findings = [f for f in findings if SEV_RANKS.get(f.get("severity", "Medium").lower(), 2) >= min_rank]
535
+
493
536
  bundles_dir = run_folder / "bundles"
494
- bundles_dir.mkdir(parents=True, exist_ok=True)
537
+ if not dry_run:
538
+ bundles_dir.mkdir(parents=True, exist_ok=True)
495
539
 
496
540
  bundles = []
497
541
  seen_file_lines = set()
@@ -503,73 +547,79 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
503
547
  if candidate:
504
548
  seen_file_lines.add(target_key)
505
549
  bundles.append(candidate)
506
- b_dir = bundles_dir / candidate["bundle_id"]
507
- b_dir.mkdir(parents=True, exist_ok=True)
508
-
509
- # 1. patch.diff
510
- (b_dir / "patch.diff").write_text(candidate["proposed_diff"], encoding="utf-8")
511
-
512
- # 2. minimal_patch_plan.md
513
- plan_content = [
514
- f"# Remediation Plan: {candidate['bundle_id']}",
515
- f"- **Rule ID:** `{candidate['rule_id']}`",
516
- f"- **Target File:** `{candidate['target_file']}:{candidate['line_number']}`",
517
- f"- **Ponytail Churn:** `+{candidate['additions']} / -{candidate['deletions']}` (Compliant <=35/<=25)",
518
- f"\n## Proposed Change\n{candidate['what_should_change']}\n",
519
- "## Unified Diff Preview\n```diff",
520
- candidate["proposed_diff"],
521
- "```"
522
- ]
523
- (b_dir / "minimal_patch_plan.md").write_text("\n".join(plan_content), encoding="utf-8")
524
-
525
- # 3. metadata.json
526
- with open(b_dir / "metadata.json", "w", encoding="utf-8") as mf:
527
- json.dump(candidate, mf, indent=2)
528
-
529
- # Render run-level remediation.md
530
- remediation_lines = [
531
- f"# TorusGuard Governed Remediation Catalog",
532
- f"**Run ID:** `{run_folder.name}` ",
533
- f"**Generated:** `{get_ist_now().strftime('%Y-%m-%d %H:%M:%S IST')}` ",
534
- f"**Candidate Bundles Formulated:** `{len(bundles)}` \n",
535
- "---",
536
- "## Formulated Candidate Patches\n"
537
- ]
538
-
539
- for b in bundles:
540
- remediation_lines.append(f"### [{b['rule_id']}] {b['title']}")
541
- remediation_lines.append(f"- **Target:** `{b['target_file']}:{b['line_number']}` | **Bundle ID:** `{b['bundle_id']}`")
542
- remediation_lines.append(f"- **Strategy:** {b['what_should_change']}")
543
- remediation_lines.append(f"- **Ponytail Churn:** `+{b['additions']} / -{b['deletions']}`")
544
- remediation_lines.append("```diff")
545
- remediation_lines.append(b["proposed_diff"].strip())
546
- remediation_lines.append("```\n")
547
-
548
- (run_folder / "remediation.md").write_text("\n".join(remediation_lines), encoding="utf-8")
549
- try:
550
- import report_sync
551
- report_sync.record_harden_bundles(target_root, bundles, run_folder.name)
552
- except Exception:
553
- pass
554
-
555
- # Update manifest
556
- manifest_file = run_folder / "manifest.json"
557
- if manifest_file.is_file():
550
+ if not dry_run:
551
+ b_dir = bundles_dir / candidate["bundle_id"]
552
+ b_dir.mkdir(parents=True, exist_ok=True)
553
+
554
+ # 1. patch.diff
555
+ (b_dir / "patch.diff").write_text(candidate["proposed_diff"], encoding="utf-8")
556
+
557
+ # 2. minimal_patch_plan.md
558
+ plan_content = [
559
+ f"# Remediation Plan: {candidate['bundle_id']}",
560
+ f"- **Rule ID:** `{candidate['rule_id']}`",
561
+ f"- **Target File:** `{candidate['target_file']}:{candidate['line_number']}`",
562
+ f"- **Ponytail Churn:** `+{candidate['additions']} / -{candidate['deletions']}` (Compliant <=35/<=25)",
563
+ f"\n## Proposed Change\n{candidate['what_should_change']}\n",
564
+ "## Unified Diff Preview\n```diff",
565
+ candidate["proposed_diff"],
566
+ "```"
567
+ ]
568
+ (b_dir / "minimal_patch_plan.md").write_text("\n".join(plan_content), encoding="utf-8")
569
+
570
+ # 3. metadata.json
571
+ with open(b_dir / "metadata.json", "w", encoding="utf-8") as mf:
572
+ json.dump(candidate, mf, indent=2)
573
+
574
+ if not dry_run:
575
+ # Render run-level remediation.md
576
+ remediation_lines = [
577
+ f"# TorusGuard Governed Remediation Catalog",
578
+ f"**Run ID:** `{run_folder.name}` ",
579
+ f"**Generated:** `{get_ist_now().strftime('%Y-%m-%d %H:%M:%S IST')}` ",
580
+ f"**Candidate Bundles Formulated:** `{len(bundles)}` \n",
581
+ "---",
582
+ "## Formulated Candidate Patches\n"
583
+ ]
584
+
585
+ for b in bundles:
586
+ remediation_lines.append(f"### [{b['rule_id']}] {b['title']}")
587
+ remediation_lines.append(f"- **Target:** `{b['target_file']}:{b['line_number']}` | **Bundle ID:** `{b['bundle_id']}`")
588
+ remediation_lines.append(f"- **Strategy:** {b['what_should_change']}")
589
+ remediation_lines.append(f"- **Ponytail Churn:** `+{b['additions']} / -{b['deletions']}`")
590
+ remediation_lines.append("```diff")
591
+ remediation_lines.append(b["proposed_diff"].strip())
592
+ remediation_lines.append("```\n")
593
+
594
+ (run_folder / "remediation.md").write_text("\n".join(remediation_lines), encoding="utf-8")
558
595
  try:
559
- mdata = json.loads(manifest_file.read_text(encoding="utf-8"))
560
- mdata["remediation_bundles_count"] = len(bundles)
561
- manifest_file.write_text(json.dumps(mdata, indent=2), encoding="utf-8")
596
+ import report_sync
597
+ report_sync.record_harden_bundles(target_root, bundles, run_folder.name)
562
598
  except Exception:
563
599
  pass
564
600
 
601
+ # Update manifest
602
+ manifest_file = run_folder / "manifest.json"
603
+ if manifest_file.is_file():
604
+ try:
605
+ mdata = json.loads(manifest_file.read_text(encoding="utf-8"))
606
+ mdata["remediation_bundles_count"] = len(bundles)
607
+ manifest_file.write_text(json.dumps(mdata, indent=2), encoding="utf-8")
608
+ except Exception:
609
+ pass
610
+
565
611
  # Print 75-column Terminal Cards
612
+ title_sub = "DRY RUN PREVIEW (No Disk Modifications)" if dry_run else "Autonomous Minimal Patch Formulation & Ponytail Packaging"
566
613
  print()
567
- print(box_header("๐Ÿ›ก๏ธ TORUSGUARD GOVERNED REMEDIATION ENGINE", "Autonomous Minimal Patch Formulation & Ponytail Packaging", "v1.3.6"))
614
+ print(box_header("๐Ÿ›ก๏ธ TORUSGUARD GOVERNED REMEDIATION ENGINE", title_sub, "v1.4.0"))
568
615
  print()
569
616
 
570
617
  print(border_top("Remediation Scope"))
571
618
  print(box_line(f"Active Run: {WHITE}{run_folder.name}{RESET}"))
572
619
  print(box_line(f"Target Scope: {WHITE}{target_root}{RESET}"))
620
+ print(box_line(f"Severity Floor: {YELLOW}{severity_floor.upper()}{RESET}"))
621
+ if dry_run:
622
+ print(box_line(f"Execution Mode: {YELLOW}DRY RUN (Preview Only ยท Zero Disk Churn){RESET}"))
573
623
  print(box_line(f"Total Findings: {len(findings)} evaluated | {GREEN}{len(bundles)} surgical patches formulated{RESET}"))
574
624
  print(box_line(f"Ponytail Guard: {GREEN}All patches strictly <= 35 additions, <= 25 deletions{RESET}"))
575
625
  print(border_bottom())
@@ -585,13 +635,14 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
585
635
  print(border_bottom())
586
636
  print()
587
637
 
588
- print(border_top("Next Governed Action", border_color=GREEN, double=True))
589
- print(box_line(f"Living Report: {CYAN}security_report.md (patch previews attached){RESET}", border="โ•‘", border_color=GREEN))
590
- print(box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="โ•‘", border_color=GREEN))
591
- print(box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="โ•‘", border_color=GREEN))
592
- print(box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="โ•‘", border_color=GREEN))
593
- print(border_bottom(border_color=GREEN, double=True))
594
- print()
638
+ if not dry_run:
639
+ print(border_top("Next Governed Action", border_color=GREEN, double=True))
640
+ print(box_line(f"Living Report: {CYAN}security_report.md (patch previews attached){RESET}", border="โ•‘", border_color=GREEN))
641
+ print(box_line(f"Review & Apply: {BOLD}{WHITE}npx torusguard apply{RESET} (CLI Human Gate)", border="โ•‘", border_color=GREEN))
642
+ print(box_line(f"Auto-Apply Flag: {CYAN}npx torusguard apply --yes{RESET} (Automated mode)", border="โ•‘", border_color=GREEN))
643
+ print(box_line(f"AI IDE Chat: Run {CYAN}/torusguard-apply{RESET} in your AI chat", border="โ•‘", border_color=GREEN))
644
+ print(border_bottom(border_color=GREEN, double=True))
645
+ print()
595
646
  else:
596
647
  print(f" {YELLOW}โ„น No automatic patch templates matched current findings.{RESET}")
597
648
  print(f" {GRAY}Findings require architectural refactoring or AI-assisted guidance.{RESET}\n")
@@ -603,7 +654,7 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
603
654
  print()
604
655
 
605
656
  return {
606
- "status": "success",
657
+ "status": "dry_run" if dry_run else "success",
607
658
  "run_folder": str(run_folder),
608
659
  "bundles_count": len(bundles),
609
660
  "bundles": bundles
@@ -613,12 +664,15 @@ def execute_harden(target_root: Path, run_id_arg: Optional[str] = None) -> Dict[
613
664
  def main():
614
665
  parser = argparse.ArgumentParser(description="TorusGuard Autonomous Governed Remediation Engine")
615
666
  parser.add_argument("path", nargs="?", default=".", help="Target project root directory")
667
+ parser.add_argument("--target", "-t", help="Target project root directory (alias for path)")
616
668
  parser.add_argument("--run", "-r", help="Explicit run ID to harden (default: latest audit run)")
669
+ parser.add_argument("--severity", "-s", choices=["critical", "high", "medium", "low"], default="medium", help="Severity floor for patch generation")
670
+ parser.add_argument("--dry-run", action="store_true", help="Preview patch diffs without writing files to disk")
617
671
  parser.add_argument("--json", action="store_true", help="Output raw JSON")
618
672
  args = parser.parse_args()
619
673
 
620
- target = Path(args.path).resolve()
621
- result = execute_harden(target, run_id_arg=args.run)
674
+ target = Path(args.target or args.path).resolve()
675
+ result = execute_harden(target, run_id_arg=args.run, severity_floor=args.severity, dry_run=args.dry_run)
622
676
 
623
677
  if args.json:
624
678
  print(json.dumps(result, indent=2))