rafter-cli 0.10.0__tar.gz → 0.10.2__tar.gz

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 (102) hide show
  1. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/PKG-INFO +1 -1
  2. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/pyproject.toml +1 -1
  3. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/agent.py +95 -11
  4. rafter_cli-0.10.2/rafter_cli/commands/backend.py +520 -0
  5. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/issues/issues_app.py +38 -5
  6. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/mcp_server.py +5 -5
  7. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/notify.py +4 -2
  8. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/sites.py +6 -4
  9. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/config_manager.py +111 -6
  10. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/config_schema.py +7 -0
  11. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/risk_rules.py +318 -19
  12. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/rafter-security-skill.md +1 -1
  13. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/betterleaks.py +38 -21
  14. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/api.py +67 -0
  15. rafter_cli-0.10.0/rafter_cli/commands/backend.py +0 -288
  16. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/README.md +0 -0
  17. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/__init__.py +0 -0
  18. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/__main__.py +0 -0
  19. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/__init__.py +0 -0
  20. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/agent_components.py +0 -0
  21. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/brief.py +0 -0
  22. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/ci.py +0 -0
  23. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/docs.py +0 -0
  24. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/hook.py +0 -0
  25. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/issues/__init__.py +0 -0
  26. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/issues/dedup.py +0 -0
  27. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/issues/github_client.py +0 -0
  28. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/issues/issue_builder.py +0 -0
  29. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/policy.py +0 -0
  30. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/report.py +0 -0
  31. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/scan.py +0 -0
  32. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/skill.py +0 -0
  33. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/commands/skill_remote.py +0 -0
  34. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/__init__.py +0 -0
  35. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/audit_logger.py +0 -0
  36. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/command_interceptor.py +0 -0
  37. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/custom_patterns.py +0 -0
  38. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/docs_loader.py +0 -0
  39. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/hook_control.py +0 -0
  40. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/pattern_engine.py +0 -0
  41. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/policy_loader.py +0 -0
  42. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/core/suppression_writer.py +0 -0
  43. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/__init__.py +0 -0
  44. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/agents/__init__.py +0 -0
  45. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/agents/rafter.md +0 -0
  46. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/continue-rules/rafter-code-review.md +0 -0
  47. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/continue-rules/rafter-secure-design.md +0 -0
  48. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/continue-rules/rafter-skill-review.md +0 -0
  49. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/continue-rules/rafter.md +0 -0
  50. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc +0 -0
  51. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc +0 -0
  52. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/cursor-rules/rafter-skill-review.mdc +0 -0
  53. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/cursor-rules/rafter.mdc +0 -0
  54. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/pre-commit-hook.sh +0 -0
  55. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/pre-push-hook.sh +0 -0
  56. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/__init__.py +0 -0
  57. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/SKILL.md +0 -0
  58. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/__init__.py +0 -0
  59. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/docs/backend.md +0 -0
  60. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/docs/cli-reference.md +0 -0
  61. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/docs/finding-triage.md +0 -0
  62. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/docs/guardrails.md +0 -0
  63. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter/docs/shift-left.md +0 -0
  64. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/SKILL.md +0 -0
  65. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/api.md +0 -0
  66. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md +0 -0
  67. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/cwe-top25.md +0 -0
  68. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/investigation-playbook.md +0 -0
  69. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md +0 -0
  70. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-code-review/docs/web-app.md +0 -0
  71. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/SKILL.md +0 -0
  72. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/api-design.md +0 -0
  73. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/auth.md +0 -0
  74. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/data-storage.md +0 -0
  75. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/dependencies.md +0 -0
  76. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/deployment.md +0 -0
  77. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/ingestion.md +0 -0
  78. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/standards-pointers.md +0 -0
  79. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-secure-design/docs/threat-modeling.md +0 -0
  80. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/SKILL.md +0 -0
  81. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/authorship-provenance.md +0 -0
  82. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/changelog-review.md +0 -0
  83. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/data-practices.md +0 -0
  84. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/malware-indicators.md +0 -0
  85. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/prompt-injection.md +0 -0
  86. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/skills/rafter-skill-review/docs/telemetry.md +0 -0
  87. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/windsurf-rules/rafter-code-review.md +0 -0
  88. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md +0 -0
  89. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/windsurf-rules/rafter-skill-review.md +0 -0
  90. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/resources/windsurf-rules/rafter.md +0 -0
  91. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/__init__.py +0 -0
  92. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/git_diff_scan.py +0 -0
  93. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/regex_scanner.py +0 -0
  94. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/secret_patterns.py +0 -0
  95. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/skill_scanner.py +0 -0
  96. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/scanners/union.py +0 -0
  97. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/__init__.py +0 -0
  98. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/binary_manager.py +0 -0
  99. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/formatter.py +0 -0
  100. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/git.py +0 -0
  101. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/git_diff.py +0 -0
  102. {rafter_cli-0.10.0 → rafter_cli-0.10.2}/rafter_cli/utils/skill_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: rafter-cli
3
- Version: 0.10.0
3
+ Version: 0.10.2
4
4
  Summary: Rafter CLI — the default security agent for AI workflows. Free for individuals and open source.
5
5
  License: MIT
6
6
  Author: Rafter Team
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "rafter-cli"
3
- version = "0.10.0"
3
+ version = "0.10.2"
4
4
  description = "Rafter CLI — the default security agent for AI workflows. Free for individuals and open source."
5
5
  authors = ["Rafter Team <hello@rafter.so>"]
6
6
  license = "MIT"
@@ -1711,6 +1711,31 @@ def _scan_file(file_path: str, engine: str, custom_patterns=None) -> list[ScanRe
1711
1711
  return run_patterns()
1712
1712
 
1713
1713
 
1714
+ def _output_empty_diff_scan(
1715
+ empty_message: str,
1716
+ json_output: bool,
1717
+ quiet: bool,
1718
+ context_label: str,
1719
+ format: str,
1720
+ suppressions,
1721
+ ) -> None:
1722
+ """Emit the result of a diff scan that had no files to scan.
1723
+
1724
+ ``empty_message`` ("No files changed since <ref>") is a *status* line, so it
1725
+ goes to stderr — stdout has to stay parseable as JSON under ``--json``, and
1726
+ ``_output_scan_results`` owns the single stdout success line. Mirrors the
1727
+ empty branches of ``runGitAddedLineScan`` in
1728
+ node/src/commands/agent/scan.ts — keep the two in sync.
1729
+ """
1730
+ if not quiet:
1731
+ # rprint, not print — fmt.success returns rich markup that plain print
1732
+ # would emit literally as "[green]...[/green]".
1733
+ rprint(fmt.success(empty_message), file=sys.stderr)
1734
+ _output_scan_results(
1735
+ [], json_output, quiet, context_label, format=format, suppressions=suppressions
1736
+ )
1737
+
1738
+
1714
1739
  def _run_git_added_line_scan(
1715
1740
  git_args: list[str],
1716
1741
  git_cwd: str | None,
@@ -1743,14 +1768,16 @@ def _run_git_added_line_scan(
1743
1768
  raise typer.Exit(code=2)
1744
1769
 
1745
1770
  if not patch.strip():
1746
- if not quiet:
1747
- rprint(fmt.success(empty_message))
1771
+ _output_empty_diff_scan(
1772
+ empty_message, json_output, quiet, context_label, format, suppressions
1773
+ )
1748
1774
  raise typer.Exit(code=0)
1749
1775
 
1750
1776
  added = parse_unified_diff_added_lines(patch)
1751
1777
  if not added:
1752
- if not quiet:
1753
- rprint(fmt.success(empty_message))
1778
+ _output_empty_diff_scan(
1779
+ empty_message, json_output, quiet, context_label, format, suppressions
1780
+ )
1754
1781
  raise typer.Exit(code=0)
1755
1782
 
1756
1783
  try:
@@ -2397,16 +2424,65 @@ def _audit_share() -> None:
2397
2424
  # ── exec ─────────────────────────────────────────────────────────────
2398
2425
 
2399
2426
 
2427
+ # Approval model (rf-ss67): the only party who can approve a command that the
2428
+ # policy says needs approval is a person at an interactive terminal. No flag,
2429
+ # env var or stdin trick stands in for that, because every one of those can be
2430
+ # produced by the agent whose command is being gated:
2431
+ # * `--force` used to skip the prompt. Combined with the PreToolUse hook
2432
+ # treating a quoted argument as prose, `rafter agent exec --force "<cmd>"`
2433
+ # ran any HIGH-tier command unprompted with the hook blind. The flag is
2434
+ # kept only so old invocations parse; it changes nothing.
2435
+ # * A piped "yes" is not a person. Approval is offered only when stdin is a
2436
+ # TTY; otherwise the command is denied and says why.
2437
+ # The machine owner widens policy in ~/.rafter/config.json, not per call.
2438
+
2439
+ _DRY_RUN_EXIT = {"allowed": 0, "blocked": 1, "approval": 2}
2440
+
2441
+
2442
+ def _join_command_parts(parts: list[str]) -> str:
2443
+ """One quoted argument is the command verbatim; several (the `-- rm -rf x`
2444
+ form the docs show) are re-joined with shell quoting so the classifier sees
2445
+ what will run — `-- echo "a b"` becomes `echo 'a b'`, not `echo a b`."""
2446
+ import shlex
2447
+
2448
+ if len(parts) == 1:
2449
+ return parts[0]
2450
+ return shlex.join(parts)
2451
+
2452
+
2400
2453
  @agent_app.command("exec")
2401
2454
  def exec_cmd(
2402
- command: str = typer.Argument(..., help="Command to execute"),
2455
+ command: list[str] = typer.Argument(..., help="Command to execute (quote it, or pass it after --)"),
2403
2456
  skip_scan: bool = typer.Option(False, "--skip-scan", help="Skip pre-execution file scanning"),
2404
- force: bool = typer.Option(False, "--force", help="Skip approval prompts"),
2457
+ dry_run: bool = typer.Option(
2458
+ False, "--dry-run",
2459
+ help="Classify the command and exit without running it (exit 0 allowed, 1 blocked, 2 needs approval)",
2460
+ ),
2461
+ force: bool = typer.Option(False, "--force", hidden=True, help="Deprecated: no longer skips approval (rf-ss67)"),
2405
2462
  ):
2406
2463
  """Execute command with security validation."""
2464
+ command = _join_command_parts(command)
2407
2465
  interceptor = CommandInterceptor()
2408
2466
  evaluation = interceptor.evaluate(command)
2409
2467
 
2468
+ # --dry-run reports the classification and stops. Nothing runs, nothing is
2469
+ # scanned, nothing is logged as executed.
2470
+ if dry_run:
2471
+ blocked = not evaluation.allowed and not evaluation.requires_approval
2472
+ verdict = "BLOCKED" if blocked else ("REQUIRES APPROVAL" if evaluation.requires_approval else "ALLOWED")
2473
+ print(f"Dry run: {verdict}")
2474
+ print(f"Risk Level: {evaluation.risk_level.upper()}")
2475
+ print(f"Requires approval: {'yes' if evaluation.requires_approval else 'no'}")
2476
+ if evaluation.reason:
2477
+ print(f"Reason: {evaluation.reason}")
2478
+ print(f"Command: {command}")
2479
+ print("Not executed (--dry-run).")
2480
+ raise typer.Exit(
2481
+ code=_DRY_RUN_EXIT["blocked"] if blocked
2482
+ else _DRY_RUN_EXIT["approval"] if evaluation.requires_approval
2483
+ else _DRY_RUN_EXIT["allowed"]
2484
+ )
2485
+
2410
2486
  # Blocked
2411
2487
  if not evaluation.allowed and not evaluation.requires_approval:
2412
2488
  rprint(f"\n{fmt.error('Command BLOCKED')}\n")
@@ -2446,8 +2522,10 @@ def exec_cmd(
2446
2522
  except (subprocess.CalledProcessError, FileNotFoundError):
2447
2523
  pass
2448
2524
 
2449
- # Requires approval
2450
- if evaluation.requires_approval and not force:
2525
+ # Requires approval — only a person at a terminal can give it.
2526
+ if evaluation.requires_approval:
2527
+ if force:
2528
+ rprint(f"\n{fmt.warning('--force no longer skips approval (rf-ss67); approval needs a person at an interactive terminal')}\n")
2451
2529
  rprint(f"\n{fmt.warning('Command requires approval')}\n")
2452
2530
  print(f"Risk Level: {evaluation.risk_level.upper()}")
2453
2531
  print(f"Command: {command}")
@@ -2455,6 +2533,15 @@ def exec_cmd(
2455
2533
  print(f"Reason: {evaluation.reason}")
2456
2534
  print()
2457
2535
 
2536
+ if not sys.stdin.isatty():
2537
+ rprint(fmt.error("Command denied: approval needs an interactive terminal, and stdin is not one"))
2538
+ print(
2539
+ "Run the command yourself at a terminal, or have the machine owner adjust "
2540
+ "commandPolicy in ~/.rafter/config.json.\n"
2541
+ )
2542
+ interceptor.log_evaluation(evaluation, "blocked")
2543
+ raise typer.Exit(code=1)
2544
+
2458
2545
  answer = input("Approve this command? (yes/no): ").strip().lower()
2459
2546
  if answer not in ("yes", "y"):
2460
2547
  rprint(f"\n{fmt.error('Command cancelled')}\n")
@@ -2463,9 +2550,6 @@ def exec_cmd(
2463
2550
 
2464
2551
  rprint(f"\n{fmt.success('Command approved by user')}\n")
2465
2552
  interceptor.log_evaluation(evaluation, "overridden")
2466
- elif force and evaluation.requires_approval:
2467
- rprint(f"\n{fmt.warning('Forcing execution (--force flag)')}\n")
2468
- interceptor.log_evaluation(evaluation, "overridden")
2469
2553
  else:
2470
2554
  interceptor.log_evaluation(evaluation, "allowed")
2471
2555