rafter-cli 0.11.2__tar.gz → 0.11.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.
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/PKG-INFO +1 -1
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/pyproject.toml +1 -1
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/agent.py +39 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/rafter-security-skill.md +1 -1
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/SKILL.md +1 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/betterleaks.py +135 -80
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/regex_scanner.py +1 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/README.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/__main__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/agent_components.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/backend.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/brief.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/ci.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/docs.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/hook.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/issues/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/issues/dedup.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/issues/github_client.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/issues/issue_builder.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/issues/issues_app.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/mcp_server.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/notify.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/policy.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/report.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/scan.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/sites.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/skill.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/commands/skill_remote.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/audit_logger.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/command_interceptor.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/config_manager.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/config_schema.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/custom_patterns.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/docs_loader.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/hook_control.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/pattern_engine.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/policy_loader.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/risk_rules.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/core/suppression_writer.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/agents/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/agents/rafter.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-code-review.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-secure-design.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-skill-review.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter-skill-review.mdc +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter.mdc +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/pre-commit-hook.sh +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/pre-push-hook.sh +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/backend.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/cli-reference.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/finding-triage.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/guardrails.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/shift-left.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/SKILL.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/api.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/cwe-top25.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/investigation-playbook.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/web-app.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/SKILL.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/api-design.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/auth.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/data-storage.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/dependencies.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/deployment.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/ingestion.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/standards-pointers.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-secure-design/docs/threat-modeling.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/SKILL.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/authorship-provenance.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/changelog-review.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/data-practices.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/malware-indicators.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/prompt-injection.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-skill-review/docs/telemetry.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter-code-review.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter-skill-review.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter.md +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/git_diff_scan.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/git_history_scan.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/secret_patterns.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/skill_scanner.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/scanners/union.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/__init__.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/api.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/binary_manager.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/formatter.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/git.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/git_diff.py +0 -0
- {rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/utils/skill_manager.py +0 -0
|
@@ -783,6 +783,44 @@ def _install_codex_hooks(root: Path, quiet: bool = False) -> None:
|
|
|
783
783
|
_init_print(quiet, fmt.success(f"Installed PostToolUse hooks to {hooks_path}"))
|
|
784
784
|
|
|
785
785
|
|
|
786
|
+
def _install_gemini_hooks(root: Path, quiet: bool = False) -> None:
|
|
787
|
+
"""Install Rafter BeforeTool/AfterTool hooks into <root>/.gemini/settings.json.
|
|
788
|
+
|
|
789
|
+
Mirrors Node's installGeminiHooks. Gemini matchers are regexes against its
|
|
790
|
+
built-in tool names; the mutating ones are matched explicitly.
|
|
791
|
+
"""
|
|
792
|
+
gemini_dir = root / ".gemini"
|
|
793
|
+
gemini_dir.mkdir(parents=True, exist_ok=True)
|
|
794
|
+
settings_path = gemini_dir / "settings.json"
|
|
795
|
+
|
|
796
|
+
settings: dict[str, Any] = {}
|
|
797
|
+
if settings_path.exists():
|
|
798
|
+
try:
|
|
799
|
+
settings = json.loads(settings_path.read_text())
|
|
800
|
+
except (json.JSONDecodeError, ValueError):
|
|
801
|
+
_init_alert(quiet, fmt.warning("Existing Gemini settings.json was unreadable, creating new one"))
|
|
802
|
+
|
|
803
|
+
hooks = settings.setdefault("hooks", {})
|
|
804
|
+
rafter_bin = _resolve_rafter_path()
|
|
805
|
+
for event, sub, matcher in (
|
|
806
|
+
("BeforeTool", "pretool", "run_shell_command|write_file|replace|edit"),
|
|
807
|
+
("AfterTool", "posttool", ".*"),
|
|
808
|
+
):
|
|
809
|
+
entries = hooks.get(event) or []
|
|
810
|
+
entries = [
|
|
811
|
+
e for e in entries
|
|
812
|
+
if not any(f"hook {sub}" in str(h.get("command", "")) for h in (e.get("hooks") or []))
|
|
813
|
+
]
|
|
814
|
+
entries.append({
|
|
815
|
+
"matcher": matcher,
|
|
816
|
+
"hooks": [{"type": "command", "command": f"{rafter_bin} hook {sub} --format gemini", "timeout": 5000}],
|
|
817
|
+
})
|
|
818
|
+
hooks[event] = entries
|
|
819
|
+
|
|
820
|
+
settings_path.write_text(json.dumps(settings, indent=2))
|
|
821
|
+
_init_print(quiet, fmt.success(f"Installed hooks to {settings_path}"))
|
|
822
|
+
|
|
823
|
+
|
|
786
824
|
def _install_cursor_hooks(root: Path, quiet: bool = False) -> None:
|
|
787
825
|
"""Install Cursor hooks at <root>/.cursor/hooks.json.
|
|
788
826
|
|
|
@@ -1505,6 +1543,7 @@ def init(
|
|
|
1505
1543
|
_init_alert(quiet, fmt.error(f"Failed to install Gemini CLI skills: {skills_error}"))
|
|
1506
1544
|
else:
|
|
1507
1545
|
_register_gemini_skills(root / ".agents" / "skills", quiet=quiet)
|
|
1546
|
+
_install_gemini_hooks(root, quiet=quiet)
|
|
1508
1547
|
if gemini_ok and scope == "user":
|
|
1509
1548
|
manager.set("agent.environments.gemini.enabled", True)
|
|
1510
1549
|
except Exception as e:
|
|
@@ -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.11.
|
|
4
|
+
version: 0.11.3
|
|
5
5
|
homepage: https://rafter.so
|
|
6
6
|
metadata:
|
|
7
7
|
openclaw:
|
|
@@ -113,6 +113,7 @@ rafter usage # check API quota
|
|
|
113
113
|
|
|
114
114
|
Full CLI tree: **Read `docs/cli-reference.md`**. Full digest: `rafter brief commands`.
|
|
115
115
|
|
|
116
|
+
**Scan budget:** Remote scans spend the user's quota. `rafter run` scans the **pushed** branch, so re-running without a new push repeats it — run it once per task or before a PR and reuse `rafter get <scan-id>`; while iterating scan only changes (`rafter secrets --staged` / `--diff <ref>`); never loop, retry, or `--watch` from an agent session — exit `3` means quota exhausted, so stop, tell the user, and check `rafter usage`.
|
|
116
117
|
## Configuration
|
|
117
118
|
|
|
118
119
|
`rafter run` (the full code scan) needs an API key:
|
|
@@ -4,6 +4,7 @@ from __future__ import annotations
|
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
6
|
import platform
|
|
7
|
+
import re
|
|
7
8
|
import subprocess
|
|
8
9
|
import sys
|
|
9
10
|
import tempfile
|
|
@@ -16,74 +17,118 @@ from ..utils.binary_manager import BinaryManager
|
|
|
16
17
|
# Per-invocation timeout for the betterleaks subprocess.
|
|
17
18
|
SCAN_TIMEOUT_S = 60
|
|
18
19
|
|
|
19
|
-
#
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# file. Mirrors MAX_FILES_PER_CALL in node/src/scanners/betterleaks.ts.
|
|
33
|
-
MAX_FILES_PER_CALL = 64
|
|
34
|
-
|
|
35
|
-
# Config env vars betterleaks consults before the target's own .toml.
|
|
36
|
-
CONFIG_ENV_VARS = (
|
|
37
|
-
"BETTERLEAKS_CONFIG", "GITLEAKS_CONFIG", "BETTERLEAKS_CONFIG_TOML", "GITLEAKS_CONFIG_TOML",
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
def git_visible_files(dir_path: str) -> list[str] | None:
|
|
42
|
-
"""Files under ``dir_path`` that git does not ignore (tracked, plus
|
|
43
|
-
untracked and not ignored), as absolute paths. Returns None when
|
|
20
|
+
# Config env vars betterleaks consults before the target's own .toml, in its order.
|
|
21
|
+
CONFIG_PATH_ENV_VARS = ("BETTERLEAKS_CONFIG", "GITLEAKS_CONFIG")
|
|
22
|
+
CONFIG_TOML_ENV_VARS = ("BETTERLEAKS_CONFIG_TOML", "GITLEAKS_CONFIG_TOML")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def git_skipped_paths(dir_path: str) -> list[str] | None:
|
|
26
|
+
"""Paths under ``dir_path`` that a git-aware scan must skip, relative to
|
|
27
|
+
it: what git ignores (a wholly ignored directory as one ``dir/`` entry),
|
|
28
|
+
submodules (a gitlink, mode 160000, in the index) and untracked nested
|
|
29
|
+
repositories (which ``ls-files --others`` lists as one ``dir/`` entry). Git
|
|
30
|
+
lists no files inside either kind, so the file-list scan this replaced
|
|
31
|
+
never scanned them. A tracked path that is now a directory on disk is NOT
|
|
32
|
+
skipped: its files are untracked and get scanned. Returns None when
|
|
44
33
|
``dir_path`` is not inside a git work tree or git is unavailable, so the
|
|
45
|
-
caller scans the directory whole. Mirrors ``
|
|
34
|
+
caller scans the directory whole. Mirrors ``gitSkippedPaths`` in
|
|
46
35
|
node/src/scanners/betterleaks.ts.
|
|
47
36
|
"""
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
37
|
+
def ls(*args: str) -> list[str] | None:
|
|
38
|
+
try:
|
|
39
|
+
r = subprocess.run(
|
|
40
|
+
["git", "-C", dir_path, "ls-files", "-z", *args, "--", "."],
|
|
41
|
+
capture_output=True, timeout=30,
|
|
42
|
+
)
|
|
43
|
+
except (OSError, subprocess.TimeoutExpired):
|
|
44
|
+
return None
|
|
45
|
+
if r.returncode != 0:
|
|
46
|
+
return None
|
|
47
|
+
return [p for p in r.stdout.decode("utf-8", "surrogateescape").split("\0") if p]
|
|
48
|
+
|
|
49
|
+
ignored = ls("--others", "--ignored", "--exclude-standard", "--directory")
|
|
50
|
+
staged = ls("--cached", "--stage")
|
|
51
|
+
untracked = ls("--others", "--exclude-standard")
|
|
52
|
+
if ignored is None or staged is None or untracked is None:
|
|
56
53
|
return None
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
54
|
+
# ``--stage`` lines read "<mode> <object> <stage>\t<path>".
|
|
55
|
+
submodules = [line.split("\t", 1)[1] for line in staged if line.startswith("160000 ")]
|
|
56
|
+
nested_repos = [rel for rel in untracked if rel.endswith("/")]
|
|
57
|
+
return ignored + submodules + nested_repos
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _toml_string(s: str) -> str:
|
|
61
|
+
"""Write ``s`` as a TOML basic string. JSON escapes every control character
|
|
62
|
+
TOML forbids raw except DEL (U+007F), which would make the whole config
|
|
63
|
+
unreadable and drop the scan to the patterns engine."""
|
|
64
|
+
return json.dumps(s, ensure_ascii=False).replace("\x7f", "\\u007f")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _is_utf8(s: str) -> bool:
|
|
68
|
+
try:
|
|
69
|
+
s.encode("utf-8")
|
|
70
|
+
return True
|
|
71
|
+
except UnicodeEncodeError: # surrogateescape-decoded raw bytes
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _path_prefix_pattern(p: str) -> str:
|
|
76
|
+
"""A regex (RE2, JS and Python read it alike) matching ``p`` and everything
|
|
77
|
+
below it. Only on Windows is ``\\`` a separator; on POSIX it is an ordinary
|
|
78
|
+
filename character and stays literal, or an ignored ``a\\b`` would also
|
|
79
|
+
skip ``a/b``."""
|
|
80
|
+
slashed = (p.replace("\\", "/") if sys.platform == "win32" else p).rstrip("/")
|
|
81
|
+
return "^" + re.sub(r"[\\.+*?()|\[\]{}^$]", r"\\\g<0>", slashed) + "(?:/.*)?$"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _read_text(path: str) -> str:
|
|
85
|
+
with open(path, encoding="utf-8") as fh:
|
|
86
|
+
return fh.read()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
_LEGACY_ALLOWLIST = re.compile(r"^[ \t]*\[[ \t]*allowlist[ \t]*\][ \t]*(#.*)?$", re.MULTILINE)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def scan_config_toml(dir_path: str, skipped: list[str], base_toml: str | None) -> str:
|
|
93
|
+
"""The betterleaks config for one git-aware directory scan: the config the
|
|
94
|
+
scan would otherwise use (``base_toml``, its text; betterleaks' default
|
|
95
|
+
rules when None) plus a global path allowlist for ``skipped``, so one
|
|
96
|
+
``betterleaks dir <root>`` walk passes over those paths without reading them.
|
|
97
|
+
|
|
98
|
+
The base config is copied in, not extended: betterleaks 1.1.x stops
|
|
99
|
+
following ``[extend]`` two levels down, so wrapping a repo config that
|
|
100
|
+
itself extends a shared config (which extends the defaults) silently
|
|
101
|
+
dropped the default rules. A relative ``extend.path`` in the copy still
|
|
102
|
+
resolves as it did, against the working directory. The deprecated
|
|
103
|
+
``[allowlist]`` table is renamed to ``[[allowlists]]``, its equivalent,
|
|
104
|
+
since betterleaks rejects a config that has both.
|
|
105
|
+
|
|
106
|
+
#262 — rafter used to hand betterleaks the git-visible files as path
|
|
107
|
+
arguments, 64 per call. betterleaks 1.1.x rebuilds its config and detector
|
|
108
|
+
for every path argument (~0.2s each with the default rules), and its
|
|
109
|
+
``extend`` depth counter is a process global it never resets, so with a
|
|
110
|
+
config that extends another (``[extend] useDefault = true``) only the first
|
|
111
|
+
two path arguments of a call got the extended rules: every later file was
|
|
112
|
+
scanned with the repo's own rules alone. A single walk builds one detector
|
|
113
|
+
and applies the full rule set to every file. Mirrors ``scanConfigToml`` in
|
|
114
|
+
node/src/scanners/betterleaks.ts.
|
|
74
115
|
"""
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
for p in
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
116
|
+
# A path git reports in bytes that are not UTF-8 cannot be written into a
|
|
117
|
+
# TOML file, so it is left out: that file is scanned rather than skipped.
|
|
118
|
+
joined = (os.path.join(dir_path, rel) for rel in skipped)
|
|
119
|
+
patterns = [r"(?:^|/)\.git(?:/.*)?$"] + [_path_prefix_pattern(p) for p in joined if _is_utf8(p)]
|
|
120
|
+
lines = [
|
|
121
|
+
"# Generated by rafter for one scan (scan_config_toml, rafter's betterleaks scanner).",
|
|
122
|
+
*(["[extend]", "useDefault = true"] if base_toml is None else [_LEGACY_ALLOWLIST.sub("[[allowlists]]", base_toml)]),
|
|
123
|
+
"",
|
|
124
|
+
"[[allowlists]]",
|
|
125
|
+
'description = "rafter: paths git ignores"',
|
|
126
|
+
"paths = [",
|
|
127
|
+
*(f" {_toml_string(p)}," for p in patterns),
|
|
128
|
+
"]",
|
|
129
|
+
"",
|
|
130
|
+
]
|
|
131
|
+
return "\n".join(lines)
|
|
87
132
|
|
|
88
133
|
|
|
89
134
|
@dataclass
|
|
@@ -197,26 +242,36 @@ class BetterleaksScanner:
|
|
|
197
242
|
walks node_modules, build output and local secret stores (.env) alike:
|
|
198
243
|
slow enough to hit the timeout on an ordinary checkout, and noisy
|
|
199
244
|
(#262). With respect_gitignore (the default, matching the patterns
|
|
200
|
-
engine) and a target inside a git work tree, it
|
|
201
|
-
|
|
245
|
+
engine) and a target inside a git work tree, it walks the tree once
|
|
246
|
+
under a generated config that skips what git ignores (see
|
|
247
|
+
scan_config_toml).
|
|
202
248
|
"""
|
|
203
|
-
|
|
204
|
-
if
|
|
249
|
+
skipped = None if use_git or not respect_gitignore else git_skipped_paths(dir_path)
|
|
250
|
+
if skipped is None:
|
|
205
251
|
results = self._run_scan(dir_path, use_git=use_git)
|
|
206
252
|
else:
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
for
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
253
|
+
with tempfile.TemporaryDirectory(prefix="betterleaks-cfg-") as cfg_dir:
|
|
254
|
+
# The generated config is passed as -c, which outranks every
|
|
255
|
+
# other source, so it carries whichever config betterleaks
|
|
256
|
+
# would have chosen, in betterleaks' own order: env path, env
|
|
257
|
+
# content, the target's file.
|
|
258
|
+
env_path = next((os.environ[v] for v in CONFIG_PATH_ENV_VARS if os.environ.get(v)), None)
|
|
259
|
+
env_toml = next((os.environ[v] for v in CONFIG_TOML_ENV_VARS if os.environ.get(v)), None)
|
|
260
|
+
repo_path = next(
|
|
261
|
+
(p for p in (os.path.join(dir_path, n) for n in (".betterleaks.toml", ".gitleaks.toml"))
|
|
262
|
+
if os.path.exists(p)),
|
|
263
|
+
None,
|
|
264
|
+
)
|
|
265
|
+
if env_path:
|
|
266
|
+
base_toml = _read_text(env_path)
|
|
267
|
+
elif env_toml:
|
|
268
|
+
base_toml = env_toml
|
|
269
|
+
else:
|
|
270
|
+
base_toml = _read_text(repo_path) if repo_path else None
|
|
271
|
+
cfg = os.path.join(cfg_dir, "scan.toml")
|
|
272
|
+
with open(cfg, "w", encoding="utf-8") as fh:
|
|
273
|
+
fh.write(scan_config_toml(dir_path, skipped, base_toml))
|
|
274
|
+
results = self._run_scan(dir_path, extra_args=["-c", cfg])
|
|
220
275
|
grouped: dict[str, list[PatternMatch]] = {}
|
|
221
276
|
for r in results:
|
|
222
277
|
f = r.get("File", "unknown")
|
|
@@ -38,6 +38,7 @@ DEFAULT_EXCLUDE = [
|
|
|
38
38
|
# Vendored / virtual-env / generated dirs that cause false positives
|
|
39
39
|
"vendor", ".venv", "venv", "__pycache__", ".tox", ".mypy_cache",
|
|
40
40
|
".pytest_cache", ".ruff_cache", "results", ".terraform", "bower_components",
|
|
41
|
+
".trigger", # Trigger.dev build output (bundled JS + source maps)
|
|
41
42
|
]
|
|
42
43
|
|
|
43
44
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-code-review.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-secure-design.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/continue-rules/rafter-skill-review.md
RENAMED
|
File without changes
|
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter-code-review.mdc
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/cursor-rules/rafter-secure-design.mdc
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/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
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/cli-reference.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/finding-triage.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/guardrails.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter/docs/shift-left.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/SKILL.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/api.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/asvs.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/skills/rafter-code-review/docs/llm.md
RENAMED
|
File without changes
|
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/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.11.2 → rafter_cli-0.11.3}/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.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter-code-review.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/rafter_cli/resources/windsurf-rules/rafter-secure-design.md
RENAMED
|
File without changes
|
{rafter_cli-0.11.2 → rafter_cli-0.11.3}/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
|