rafter-cli 0.10.2__tar.gz → 0.10.3__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 (101) hide show
  1. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/PKG-INFO +1 -1
  2. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/pyproject.toml +1 -1
  3. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/hook.py +52 -2
  4. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/risk_rules.py +98 -0
  5. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/rafter-security-skill.md +1 -1
  6. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/README.md +0 -0
  7. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/__init__.py +0 -0
  8. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/__main__.py +0 -0
  9. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/__init__.py +0 -0
  10. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/agent.py +0 -0
  11. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/agent_components.py +0 -0
  12. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/backend.py +0 -0
  13. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/brief.py +0 -0
  14. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/ci.py +0 -0
  15. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/docs.py +0 -0
  16. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/issues/__init__.py +0 -0
  17. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/issues/dedup.py +0 -0
  18. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/issues/github_client.py +0 -0
  19. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/issues/issue_builder.py +0 -0
  20. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/issues/issues_app.py +0 -0
  21. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/mcp_server.py +0 -0
  22. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/notify.py +0 -0
  23. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/policy.py +0 -0
  24. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/report.py +0 -0
  25. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/scan.py +0 -0
  26. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/sites.py +0 -0
  27. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/skill.py +0 -0
  28. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/commands/skill_remote.py +0 -0
  29. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/__init__.py +0 -0
  30. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/audit_logger.py +0 -0
  31. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/command_interceptor.py +0 -0
  32. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/config_manager.py +0 -0
  33. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/config_schema.py +0 -0
  34. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/custom_patterns.py +0 -0
  35. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/docs_loader.py +0 -0
  36. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/hook_control.py +0 -0
  37. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/pattern_engine.py +0 -0
  38. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/policy_loader.py +0 -0
  39. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/core/suppression_writer.py +0 -0
  40. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/__init__.py +0 -0
  41. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/agents/__init__.py +0 -0
  42. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/agents/rafter.md +0 -0
  43. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/continue-rules/rafter-code-review.md +0 -0
  44. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/continue-rules/rafter-secure-design.md +0 -0
  45. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/continue-rules/rafter-skill-review.md +0 -0
  46. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/continue-rules/rafter.md +0 -0
  47. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc +0 -0
  48. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc +0 -0
  49. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/cursor-rules/rafter-skill-review.mdc +0 -0
  50. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/cursor-rules/rafter.mdc +0 -0
  51. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/pre-commit-hook.sh +0 -0
  52. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/pre-push-hook.sh +0 -0
  53. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/__init__.py +0 -0
  54. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/SKILL.md +0 -0
  55. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/__init__.py +0 -0
  56. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/docs/backend.md +0 -0
  57. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/docs/cli-reference.md +0 -0
  58. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/docs/finding-triage.md +0 -0
  59. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/docs/guardrails.md +0 -0
  60. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter/docs/shift-left.md +0 -0
  61. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/SKILL.md +0 -0
  62. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/api.md +0 -0
  63. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md +0 -0
  64. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/cwe-top25.md +0 -0
  65. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/investigation-playbook.md +0 -0
  66. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md +0 -0
  67. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-code-review/docs/web-app.md +0 -0
  68. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/SKILL.md +0 -0
  69. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/api-design.md +0 -0
  70. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/auth.md +0 -0
  71. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/data-storage.md +0 -0
  72. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/dependencies.md +0 -0
  73. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/deployment.md +0 -0
  74. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/ingestion.md +0 -0
  75. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/standards-pointers.md +0 -0
  76. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-secure-design/docs/threat-modeling.md +0 -0
  77. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/SKILL.md +0 -0
  78. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/authorship-provenance.md +0 -0
  79. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/changelog-review.md +0 -0
  80. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/data-practices.md +0 -0
  81. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/malware-indicators.md +0 -0
  82. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/prompt-injection.md +0 -0
  83. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/skills/rafter-skill-review/docs/telemetry.md +0 -0
  84. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/windsurf-rules/rafter-code-review.md +0 -0
  85. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md +0 -0
  86. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/windsurf-rules/rafter-skill-review.md +0 -0
  87. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/resources/windsurf-rules/rafter.md +0 -0
  88. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/__init__.py +0 -0
  89. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/betterleaks.py +0 -0
  90. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/git_diff_scan.py +0 -0
  91. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/regex_scanner.py +0 -0
  92. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/secret_patterns.py +0 -0
  93. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/skill_scanner.py +0 -0
  94. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/scanners/union.py +0 -0
  95. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/__init__.py +0 -0
  96. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/api.py +0 -0
  97. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/binary_manager.py +0 -0
  98. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/formatter.py +0 -0
  99. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/git.py +0 -0
  100. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/rafter_cli/utils/git_diff.py +0 -0
  101. {rafter_cli-0.10.2 → rafter_cli-0.10.3}/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.2
3
+ Version: 0.10.3
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.2"
3
+ version = "0.10.3"
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"
@@ -6,10 +6,12 @@ import math
6
6
  import os
7
7
  import subprocess
8
8
  import sys
9
+ from pathlib import Path
9
10
 
10
11
  import typer
11
12
 
12
13
  from ..core.audit_logger import AuditLogger
14
+ from ..core.risk_rules import modifies_rafter_security_config
13
15
  from ..core.command_interceptor import CommandInterceptor
14
16
  from ..scanners.regex_scanner import RegexScanner
15
17
 
@@ -37,6 +39,17 @@ _RISK_DESCRIPTIONS = {
37
39
 
38
40
  def _format_blocked_message(command: str, evaluation) -> str:
39
41
  cmd_display = command[:60] + "..." if len(command) > 60 else command
42
+ # rf-vnxs: say what actually happened. "irreversible system damage" is the
43
+ # wrong sentence for a config write, and a wrong reason teaches the agent to
44
+ # go looking for a way around a rule it was never told the shape of.
45
+ if modifies_rafter_security_config(command):
46
+ return (
47
+ f"\u2717 Rafter blocked: {cmd_display}\n"
48
+ f" Rule: rafter security configuration is protected (rf-vnxs)\n"
49
+ f" Risk: CRITICAL\u2014this command reconfigures or removes rafter's own\n"
50
+ f" guardrail. The gate cannot approve its own disarming. A person\n"
51
+ f" must make this change outside an agent session."
52
+ )
40
53
  rule = evaluation.matched_pattern or "policy violation"
41
54
  label = _RISK_LABELS.get(evaluation.risk_level, evaluation.risk_level.upper())
42
55
  desc = _RISK_DESCRIPTIONS.get(evaluation.risk_level, "")
@@ -54,8 +67,9 @@ def _format_approval_message(command: str, evaluation) -> str:
54
67
  f" Rule: {rule}\n"
55
68
  f" Risk: {label}\u2014{desc}\n"
56
69
  f"\n"
57
- f'To approve: rafter agent exec --approve "{command}"\n'
58
- f"To configure: rafter agent config set agent.riskLevel minimal"
70
+ f'To approve: a person must run `rafter agent exec "{command}"` at an\n'
71
+ f" interactive terminal and confirm. There is no flag that\n"
72
+ f" skips the prompt."
59
73
  )
60
74
 
61
75
 
@@ -376,7 +390,43 @@ def _evaluate_bash(command: str, control=None) -> dict:
376
390
  return {"decision": "allow"}
377
391
 
378
392
 
393
+ def _writes_rafter_security_config(file_path: str) -> bool:
394
+ """True if this write targets rafter's own configuration directory.
395
+
396
+ rf-vnxs, fourth route. Blocking the CLI invocation is not enough: the config
397
+ is a FILE, and an agent with a Write/Edit tool does not need the CLI to
398
+ disable the hook — it can write ``~/.rafter/config.json`` directly. Three
399
+ independent routes to the unblockable tier had already been found one at a
400
+ time in this codebase; this is the one that would have been the fourth.
401
+ """
402
+ if not file_path:
403
+ return False
404
+ try:
405
+ from ..core.config_schema import get_rafter_dir
406
+
407
+ target = Path(file_path).expanduser().resolve()
408
+ rafter_dir = get_rafter_dir().expanduser().resolve()
409
+ except Exception:
410
+ return False
411
+ return target == rafter_dir or rafter_dir in target.parents
412
+
413
+
379
414
  def _evaluate_write(tool_input: dict) -> dict:
415
+ # rf-vnxs: checked FIRST, and before the no-content early return, because a
416
+ # truncating write with empty content disables the hook just as well as one
417
+ # carrying JSON.
418
+ if _writes_rafter_security_config(tool_input.get("file_path", "")):
419
+ return {
420
+ "decision": "deny",
421
+ "reason": (
422
+ "\u2717 Rafter blocked: write to rafter's own configuration\n"
423
+ " Rule: rafter security configuration is protected (rf-vnxs)\n"
424
+ " Risk: CRITICAL\u2014editing this file reconfigures or removes the\n"
425
+ " guardrail. The gate cannot approve its own disarming. A\n"
426
+ " person must make this change outside an agent session."
427
+ ),
428
+ }
429
+
380
430
  content = tool_input.get("content", "") or tool_input.get("new_string", "")
381
431
  if not content:
382
432
  return {"decision": "allow"}
@@ -774,8 +774,106 @@ def sanitize_command_for_matching(command: str) -> str:
774
774
  return _sanitize(command, 0)
775
775
 
776
776
 
777
+ # rf-vnxs — reconfiguring rafter's OWN security controls is a PROTECTED CLASS.
778
+ #
779
+ # The gate must never approve its own disarming. On 0.10.1 it did: the hook
780
+ # rated `rafter agent config set agent.hooks.enabled false` as `low`, allowed
781
+ # it, and every later command — including the CRITICAL tier that no policy,
782
+ # mode or deny-list can override — was then permitted. Three steps, no
783
+ # malformed command, and it persists to config.json.
784
+ #
785
+ # Matched STRUCTURALLY (resolved exec + argv walk), never by string: every one
786
+ # of these was observed ALLOWED on the published artifact — extra whitespace,
787
+ # `FALSE`, `npx @rafter-security/cli …`, `pnpm dlx`, `sudo -E rafter …`, an
788
+ # absolute path, the `agent disable` component route, and three separate
789
+ # self-disabling keys. A blacklist of one string is one alias from being inert.
790
+ _RAFTER_EXECS = {"rafter", "rafter-cli"}
791
+ _PACKAGE_RUNNERS = {"npx", "bunx", "pnpx", "dlx", "pnpm", "yarn", "bun"}
792
+ _RAFTER_PACKAGE = re.compile(r"^(?:@rafter-security/cli|rafter-cli|rafter)(?:@[\w.^~*-]+)?$")
793
+
794
+ # Config keys that gate the hook. A namespace, not a leaf — see hook_control.
795
+ _PROTECTED_CONFIG_KEY = re.compile(r"^(?:agent\.)?(?:hooks|commandpolicy)\b|^agent\.risklevel$")
796
+
797
+
798
+ def _statement_disarms_rafter(words: list[_Piece]) -> bool:
799
+ """True if this statement's argv reconfigures or removes a rafter control.
800
+
801
+ The rafter invocation is looked for at ANY position, not just as the
802
+ statement's own exec: a shell carrying a command string puts it in the
803
+ operand, so `bash -c "rafter agent config set …"` and `bash -c "$(echo
804
+ rafter …)"` both reduce to a statement whose exec is `bash`. A quoted word
805
+ is skipped on the raw pass so `echo 'rafter agent config set …'` stays
806
+ prose; the sanitized pass sees operands that are actually executed already
807
+ unwrapped.
808
+ """
809
+ for j in range(len(words)):
810
+ if words[j].quoted:
811
+ continue
812
+ k = j
813
+ exec_ = _exec_name(words[k].text)
814
+ k += 1
815
+
816
+ if exec_ in _PACKAGE_RUNNERS:
817
+ while k < len(words) and (words[k].text.startswith("-") or words[k].text.lower() in ("dlx", "exec")):
818
+ k += 1
819
+ if k >= len(words) or not _RAFTER_PACKAGE.match(words[k].text.lower()):
820
+ continue
821
+ k += 1
822
+ exec_ = "rafter"
823
+ if exec_ not in _RAFTER_EXECS:
824
+ continue
825
+
826
+ args = [w.text.lower() for w in words[k:] if not w.text.startswith("-")]
827
+ for n in range(len(args) - 1):
828
+ if args[n] == "config" and args[n + 1] == "set":
829
+ key = args[n + 2] if n + 2 < len(args) else None
830
+ if key and _PROTECTED_CONFIG_KEY.match(key):
831
+ return True
832
+ if args[n] == "agent" and args[n + 1] == "disable":
833
+ return True
834
+ return False
835
+
836
+
837
+ def _disarms_rafter(command: str, depth: int = 0) -> bool:
838
+ """True if any statement (or command substitution) disarms rafter."""
839
+ if depth > _MAX_SANITIZE_DEPTH:
840
+ return False
841
+ pieces, _ = _tokenize(command)
842
+ statements: list[list[_Piece]] = []
843
+ stmt: list[_Piece] = []
844
+ for p in pieces:
845
+ if p.op is not None:
846
+ if p.op in _CHAIN_OPS or p.op in ("\n", "\r"):
847
+ statements.append(stmt)
848
+ stmt = []
849
+ continue
850
+ stmt.append(p)
851
+ for sub in (p.substs or []):
852
+ if _disarms_rafter(sub, depth + 1):
853
+ return True
854
+ statements.append(stmt)
855
+ return any(_statement_disarms_rafter(st) for st in statements)
856
+
857
+
858
+ def modifies_rafter_security_config(command: str) -> bool:
859
+ """True if the command disarms rafter, checked on raw AND sanitized text.
860
+
861
+ Raw catches the ordinary invocation; sanitized catches a shell running the
862
+ OUTPUT of something else, whose own exec is `echo` and which a walk over
863
+ the raw argv therefore reads, correctly, as a print.
864
+ """
865
+ if _disarms_rafter(command):
866
+ return True
867
+ sanitized = sanitize_command_for_matching(command)
868
+ return sanitized != command and _disarms_rafter(sanitized)
869
+
870
+
777
871
  def assess_command_risk(command: str) -> str:
778
872
  """Assess risk level of a command string."""
873
+ # rf-vnxs: checked before the pattern loops. The disarm is an ordinary
874
+ # well-formed invocation — nothing malformed for the sanitizer to normalise.
875
+ if modifies_rafter_security_config(command):
876
+ return "critical"
779
877
  cmd = sanitize_command_for_matching(command).strip()
780
878
  if not cmd:
781
879
  return "low"
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: rafter-security
3
3
  description: Security toolkit for AI workflows. Use when scanning code or repos for vulnerabilities, auditing third-party skills/MCPs/agent configs before installing, evaluating shell commands before running them, or generating secure design questions for new features. Provides `rafter run` (remote SAST + SCA, needs RAFTER_API_KEY), `rafter secrets` (offline secrets-only), `rafter agent exec --dry-run` (command-risk classification), and `rafter skill review`.
4
- version: 0.10.2
4
+ version: 0.10.3
5
5
  homepage: https://rafter.so
6
6
  metadata:
7
7
  openclaw:
File without changes