rafter-cli 0.10.3__tar.gz → 0.10.4__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.
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/PKG-INFO +1 -1
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/pyproject.toml +1 -1
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/command_interceptor.py +40 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/config_manager.py +30 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/config_schema.py +14 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/policy_loader.py +14 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/risk_rules.py +17 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/rafter-security-skill.md +1 -1
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/secret_patterns.py +28 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/README.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/__main__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/agent.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/agent_components.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/backend.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/brief.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/ci.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/docs.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/hook.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/issues/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/issues/dedup.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/issues/github_client.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/issues/issue_builder.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/issues/issues_app.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/mcp_server.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/notify.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/policy.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/report.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/scan.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/sites.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/skill.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/commands/skill_remote.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/audit_logger.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/custom_patterns.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/docs_loader.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/hook_control.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/pattern_engine.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/core/suppression_writer.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/agents/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/agents/rafter.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-code-review.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-secure-design.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-skill-review.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-skill-review.mdc +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter.mdc +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/pre-commit-hook.sh +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/pre-push-hook.sh +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/SKILL.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/backend.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/cli-reference.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/finding-triage.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/guardrails.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/shift-left.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/SKILL.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/api.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/cwe-top25.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/investigation-playbook.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/web-app.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/SKILL.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/api-design.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/auth.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/data-storage.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/dependencies.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/deployment.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/ingestion.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/standards-pointers.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/docs/threat-modeling.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/SKILL.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/authorship-provenance.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/changelog-review.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/data-practices.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/malware-indicators.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/prompt-injection.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/docs/telemetry.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-code-review.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-skill-review.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter.md +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/betterleaks.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/git_diff_scan.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/regex_scanner.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/skill_scanner.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/scanners/union.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/__init__.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/api.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/binary_manager.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/formatter.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/git.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/git_diff.py +0 -0
- {rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/utils/skill_manager.py +0 -0
|
@@ -7,6 +7,7 @@ from dataclasses import dataclass
|
|
|
7
7
|
from .audit_logger import AuditLogger
|
|
8
8
|
from .config_manager import ConfigManager
|
|
9
9
|
from .risk_rules import (
|
|
10
|
+
is_chained_command,
|
|
10
11
|
assess_command_risk,
|
|
11
12
|
match_critical_pattern,
|
|
12
13
|
sanitize_command_for_matching,
|
|
@@ -69,6 +70,45 @@ class CommandInterceptor:
|
|
|
69
70
|
matched_pattern=pattern,
|
|
70
71
|
)
|
|
71
72
|
|
|
73
|
+
# Check the positive allowlist. Deliberately AFTER blocked_patterns and
|
|
74
|
+
# BEFORE require_approval: a deny rule always wins, and an allow rule's
|
|
75
|
+
# whole job is to suppress the approval prompt for a known-safe command.
|
|
76
|
+
#
|
|
77
|
+
# Two guards keep an allowlist from becoming a hole in the guard rail:
|
|
78
|
+
#
|
|
79
|
+
# - A ``critical`` command is never allowlistable. NOTE: this guard is
|
|
80
|
+
# currently UNREACHABLE — evaluate() hard-blocks critical above,
|
|
81
|
+
# before this loop — and a mutation sweep proved it: deleting the
|
|
82
|
+
# guard leaves every test green. It is kept as defence in depth,
|
|
83
|
+
# because it becomes the only protection the day that early block is
|
|
84
|
+
# narrowed. Do not write a test claiming to exercise it; such a test
|
|
85
|
+
# passes with the guard deleted.
|
|
86
|
+
# - A match does not apply when the command holds more than one
|
|
87
|
+
# statement. Patterns are unanchored by request, so without this
|
|
88
|
+
# "^git push" would wave through ``rm -rf / && git push`` — or, via
|
|
89
|
+
# the newline the original regex missed, anything on a second line.
|
|
90
|
+
allowed = getattr(policy, "allowed_patterns", None) or []
|
|
91
|
+
if not isinstance(allowed, list):
|
|
92
|
+
# Defence in depth behind the validator: iterating a str yields
|
|
93
|
+
# CHARACTERS, and a leading "^" then matches every command.
|
|
94
|
+
allowed = []
|
|
95
|
+
for pattern in allowed:
|
|
96
|
+
if not self._matches(command, pattern):
|
|
97
|
+
continue
|
|
98
|
+
if is_chained_command(command):
|
|
99
|
+
# Fall through to normal classification rather than allowing.
|
|
100
|
+
break
|
|
101
|
+
if risk_level == "critical":
|
|
102
|
+
break
|
|
103
|
+
return CommandEvaluation(
|
|
104
|
+
command=command,
|
|
105
|
+
risk_level="low",
|
|
106
|
+
allowed=True,
|
|
107
|
+
requires_approval=False,
|
|
108
|
+
reason=f"Matches allowed pattern: {pattern}",
|
|
109
|
+
matched_pattern=pattern,
|
|
110
|
+
)
|
|
111
|
+
|
|
72
112
|
# Check approval patterns
|
|
73
113
|
for pattern in policy.require_approval:
|
|
74
114
|
if self._matches(command, pattern):
|
|
@@ -206,6 +206,17 @@ class ConfigManager:
|
|
|
206
206
|
if key in cp and (not isinstance(cp[key], list) or not all(isinstance(v, str) for v in cp[key])):
|
|
207
207
|
print(f'rafter: config "commandPolicy.{key}" must be an array of strings — using default.', file=sys.stderr)
|
|
208
208
|
del cp[key]
|
|
209
|
+
# Every sibling key is validated; this one was added to node's
|
|
210
|
+
# validator and not to python's. A bare STRING is the shape
|
|
211
|
+
# `rafter agent config set agent.commandPolicy.allowedPatterns
|
|
212
|
+
# '^git status'` actually writes — json.loads fails, the raw string
|
|
213
|
+
# is stored — and python then iterates its CHARACTERS, so the first
|
|
214
|
+
# one, "^", matches every command and the allowlist allows
|
|
215
|
+
# everything. Node warned and fell back to []; python did not.
|
|
216
|
+
for key in ("allowedPatterns", "allowed_patterns"):
|
|
217
|
+
if key in cp and (not isinstance(cp[key], list) or not all(isinstance(v, str) for v in cp[key])):
|
|
218
|
+
print(f'rafter: config "commandPolicy.{key}" must be an array of strings — using default.', file=sys.stderr)
|
|
219
|
+
del cp[key]
|
|
209
220
|
for key in ("allowProjectOverride", "allow_project_override"):
|
|
210
221
|
if key in cp and not isinstance(cp[key], bool):
|
|
211
222
|
print(f'rafter: config "commandPolicy.{key}" must be a boolean — ignoring (project policies cannot loosen command policy).', file=sys.stderr)
|
|
@@ -514,6 +525,8 @@ def merge_command_policy(target, project: dict, allow_override: bool) -> None:
|
|
|
514
525
|
target.blocked_patterns = project["blocked_patterns"]
|
|
515
526
|
if project.get("require_approval") is not None:
|
|
516
527
|
target.require_approval = project["require_approval"]
|
|
528
|
+
if project.get("allowed_patterns") is not None:
|
|
529
|
+
target.allowed_patterns = project["allowed_patterns"]
|
|
517
530
|
return
|
|
518
531
|
|
|
519
532
|
mode = project.get("mode")
|
|
@@ -540,3 +553,20 @@ def merge_command_policy(target, project: dict, allow_override: bool) -> None:
|
|
|
540
553
|
target.require_approval = _union_patterns(
|
|
541
554
|
target.require_approval, project["require_approval"]
|
|
542
555
|
)
|
|
556
|
+
|
|
557
|
+
# allowed_patterns is NOT unioned, and that asymmetry is the whole point.
|
|
558
|
+
# Unioning blocked_patterns or require_approval can only ever ADD
|
|
559
|
+
# restriction, so a project contributing to them is safe. An allowlist is
|
|
560
|
+
# the opposite: it is a grant. Union it and a cloned repo ships
|
|
561
|
+
# command_policy: {allowed_patterns: [".*"]}
|
|
562
|
+
# and waves every non-critical command through — precisely the bypass the
|
|
563
|
+
# floor exists to prevent (sable-nz4y / rf-adth). So the owner's allowlist
|
|
564
|
+
# stands and the project's is refused unless the owner opted in.
|
|
565
|
+
if project.get("allowed_patterns") is not None:
|
|
566
|
+
print(
|
|
567
|
+
"rafter: project policy sets agent.commandPolicy.allowed_patterns, which can "
|
|
568
|
+
"only loosen command policy — ignoring. Set "
|
|
569
|
+
"agent.commandPolicy.allowProjectOverride: true in your global config to "
|
|
570
|
+
"allow project policies to loosen command policy.",
|
|
571
|
+
file=sys.stderr,
|
|
572
|
+
)
|
|
@@ -56,6 +56,20 @@ class CommandPolicyConfig:
|
|
|
56
56
|
#: would be no floor at all. Default (absent/False) keeps the floor: a
|
|
57
57
|
#: project policy may tighten command policy, never loosen it.
|
|
58
58
|
allow_project_override: bool = False
|
|
59
|
+
#: Positive allowlist: unanchored regexes that force a command to ``low``
|
|
60
|
+
#: and skip the approval prompt. For the known-safe command that would
|
|
61
|
+
#: otherwise trip a broad risk tier — the motivating case being
|
|
62
|
+
#: ``git push --force-with-lease`` to a feature branch on a repo whose main
|
|
63
|
+
#: is protected server-side.
|
|
64
|
+
#:
|
|
65
|
+
#: Three properties make this safe to put on a guard rail, and all three are
|
|
66
|
+
#: enforced in CommandInterceptor, not here:
|
|
67
|
+
#: 1. blocked_patterns always wins. An allowlist never re-opens what a
|
|
68
|
+
#: deny rule closed.
|
|
69
|
+
#: 2. A ``critical`` command is never allowlistable.
|
|
70
|
+
#: 3. A match does not apply when the command contains a chain operator,
|
|
71
|
+
#: so ``^git push`` cannot wave through ``rm -rf / && git push``.
|
|
72
|
+
allowed_patterns: list[str] = field(default_factory=list)
|
|
59
73
|
|
|
60
74
|
|
|
61
75
|
@dataclass
|
|
@@ -94,6 +94,13 @@ def _map_policy(raw: dict) -> dict:
|
|
|
94
94
|
policy["command_policy"]["blocked_patterns"] = cp["blocked_patterns"]
|
|
95
95
|
if isinstance(cp.get("require_approval"), list):
|
|
96
96
|
policy["command_policy"]["require_approval"] = cp["require_approval"]
|
|
97
|
+
# Without this the allowlist is unreachable from .rafter.yml: the
|
|
98
|
+
# interceptor reads allowed_patterns off the merged config and nothing
|
|
99
|
+
# ever put it there. Any new command_policy key needs a line HERE and
|
|
100
|
+
# in config_manager's merge, or it is documentation for a feature that
|
|
101
|
+
# does not run. (rf-3n1i)
|
|
102
|
+
if isinstance(cp.get("allowed_patterns"), list):
|
|
103
|
+
policy["command_policy"]["allowed_patterns"] = cp["allowed_patterns"]
|
|
97
104
|
|
|
98
105
|
scan = raw.get("scan")
|
|
99
106
|
if isinstance(scan, dict):
|
|
@@ -252,6 +259,13 @@ def _validate_policy(policy: dict, raw: dict) -> dict:
|
|
|
252
259
|
if "mode" in cp and cp["mode"] not in _VALID_COMMAND_MODES:
|
|
253
260
|
print('Warning: "command_policy.mode" must be one of: allow-all, approve-dangerous, deny-list \u2014 ignoring.', file=sys.stderr)
|
|
254
261
|
del cp["mode"]
|
|
262
|
+
if "allowed_patterns" in cp:
|
|
263
|
+
if not isinstance(cp["allowed_patterns"], list) or not all(isinstance(v, str) for v in cp["allowed_patterns"]):
|
|
264
|
+
print(
|
|
265
|
+
'rafter: "command_policy.allowed_patterns" must be a list of strings — ignoring.',
|
|
266
|
+
file=sys.stderr,
|
|
267
|
+
)
|
|
268
|
+
cp.pop("allowed_patterns", None)
|
|
255
269
|
if "blocked_patterns" in cp:
|
|
256
270
|
if not isinstance(cp["blocked_patterns"], list) or not all(isinstance(v, str) for v in cp["blocked_patterns"]):
|
|
257
271
|
print('Warning: "command_policy.blocked_patterns" must be an array of strings \u2014 ignoring.', file=sys.stderr)
|
|
@@ -129,6 +129,8 @@ _TEXT_FLAGS = {
|
|
|
129
129
|
# Operators that chain independent commands.
|
|
130
130
|
_CHAIN_OPS = {";", "&&", "||", "|", "&"}
|
|
131
131
|
|
|
132
|
+
|
|
133
|
+
|
|
132
134
|
# Operators whose following token is a redirect target (a path — never data).
|
|
133
135
|
_REDIRECT_OPS = {">", ">>", "<", "<<"}
|
|
134
136
|
|
|
@@ -348,6 +350,21 @@ def _tokenize(s: str) -> tuple[list[_Piece], bool]:
|
|
|
348
350
|
return pieces, unterminated
|
|
349
351
|
|
|
350
352
|
|
|
353
|
+
def is_chained_command(command: str) -> bool:
|
|
354
|
+
r"""True if the command contains more than one statement.
|
|
355
|
+
|
|
356
|
+
Asked of the TOKENIZER rather than a regex. The first version of this was
|
|
357
|
+
``re.compile(r"[;|&]|&&|\|\|")``, which omits the newline — and a newline
|
|
358
|
+
has been a statement separator in this module since rf-6pqx. With
|
|
359
|
+
``^git push origin feature/`` allowlisted, ``git push origin feature/x`` and
|
|
360
|
+
a second line holding ``git push --force origin main`` classified ``allow``.
|
|
361
|
+
The tokenizer already normalises ``\n`` to ``;``, so routing the question
|
|
362
|
+
through it removes the second, narrower definition instead of widening it.
|
|
363
|
+
"""
|
|
364
|
+
pieces, _ = _tokenize(command)
|
|
365
|
+
return any(p.op is not None and p.op in _CHAIN_OPS for p in pieces)
|
|
366
|
+
|
|
367
|
+
|
|
351
368
|
def _exec_name(text: str) -> str:
|
|
352
369
|
"""`/usr/bin/rm` -> `rm`; used to classify the executable of a segment."""
|
|
353
370
|
return text[text.rfind("/") + 1:].lower()
|
|
@@ -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.
|
|
4
|
+
version: 0.10.4
|
|
5
5
|
homepage: https://rafter.so
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -81,6 +81,34 @@ DEFAULT_SECRET_PATTERNS: list[Pattern] = [
|
|
|
81
81
|
severity="critical",
|
|
82
82
|
description="Stripe Restricted API Key detected",
|
|
83
83
|
),
|
|
84
|
+
# OpenAI (rf-f5is / se-wagv, external report). The hook's Write gate is
|
|
85
|
+
# regex-only and this file had NO OpenAI rule at all, so `sk-proj-` and
|
|
86
|
+
# legacy keys were ALLOWED through the gate at any length while
|
|
87
|
+
# `rafter secrets` caught them via betterleaks. The two engines disagreed
|
|
88
|
+
# and the one guarding writes was the blind one.
|
|
89
|
+
#
|
|
90
|
+
# No `(?i)`: these prefixes and their base62 bodies are case-sensitive, and
|
|
91
|
+
# the convention here is that prefixed vendor tokens (ghp_, AKIA, AIza, xox)
|
|
92
|
+
# match case-sensitively. Lower-casing them would only add false positives.
|
|
93
|
+
Pattern(
|
|
94
|
+
name="OpenAI API Key",
|
|
95
|
+
regex=r"sk-(proj|svcacct|admin)-[A-Za-z0-9_-]{40,}",
|
|
96
|
+
severity="critical",
|
|
97
|
+
description="OpenAI project/service/admin API key detected",
|
|
98
|
+
),
|
|
99
|
+
Pattern(
|
|
100
|
+
name="OpenAI API Key (legacy)",
|
|
101
|
+
regex=r"sk-[A-Za-z0-9]{20}T3BlbkFJ[A-Za-z0-9]{20}",
|
|
102
|
+
severity="critical",
|
|
103
|
+
description="OpenAI legacy API key detected",
|
|
104
|
+
),
|
|
105
|
+
# Supabase (rf-f5is / se-wagv). Detected by NEITHER engine at any length.
|
|
106
|
+
Pattern(
|
|
107
|
+
name="Supabase Secret Key",
|
|
108
|
+
regex=r"sb_secret_[A-Za-z0-9_-]{20,}",
|
|
109
|
+
severity="critical",
|
|
110
|
+
description="Supabase secret key detected",
|
|
111
|
+
),
|
|
84
112
|
# Twilio
|
|
85
113
|
Pattern(
|
|
86
114
|
name="Twilio API Key",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-code-review.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-secure-design.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/continue-rules/rafter-skill-review.md
RENAMED
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/cursor-rules/rafter-skill-review.mdc
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/cli-reference.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/finding-triage.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/guardrails.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter/docs/shift-left.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/SKILL.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/api.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md
RENAMED
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-secure-design/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/skills/rafter-skill-review/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-code-review.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md
RENAMED
|
File without changes
|
{rafter_cli-0.10.3 → rafter_cli-0.10.4}/rafter_cli/resources/windsurf-rules/rafter-skill-review.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|