@rafter-security/cli 0.5.5 → 0.5.9

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 (35) hide show
  1. package/README.md +15 -3
  2. package/dist/commands/agent/audit-skill.js +1 -1
  3. package/dist/commands/agent/audit.js +96 -0
  4. package/dist/commands/agent/baseline.js +10 -0
  5. package/dist/commands/agent/exec.js +1 -1
  6. package/dist/commands/agent/init.js +366 -26
  7. package/dist/commands/agent/scan.js +157 -16
  8. package/dist/commands/agent/status.js +65 -4
  9. package/dist/commands/agent/verify.js +18 -4
  10. package/dist/commands/backend/run.js +69 -61
  11. package/dist/commands/ci/init.js +10 -3
  12. package/dist/commands/completion.js +21 -9
  13. package/dist/commands/hook/posttool.js +21 -7
  14. package/dist/commands/hook/pretool.js +50 -13
  15. package/dist/commands/issues/dedup.js +39 -0
  16. package/dist/commands/issues/from-scan.js +143 -0
  17. package/dist/commands/issues/from-text.js +185 -0
  18. package/dist/commands/issues/github-client.js +85 -0
  19. package/dist/commands/issues/index.js +25 -0
  20. package/dist/commands/issues/issue-builder.js +101 -0
  21. package/dist/commands/policy/export.js +7 -2
  22. package/dist/commands/scan/index.js +44 -0
  23. package/dist/core/config-defaults.js +24 -0
  24. package/dist/core/config-manager.js +19 -2
  25. package/dist/core/pattern-engine.js +26 -1
  26. package/dist/index.js +8 -2
  27. package/dist/scanners/gitleaks.js +5 -5
  28. package/dist/scanners/regex-scanner.js +12 -1
  29. package/dist/scanners/secret-patterns.js +3 -3
  30. package/dist/utils/binary-manager.js +7 -6
  31. package/dist/utils/skill-manager.js +5 -3
  32. package/package.json +2 -1
  33. package/resources/pre-commit-hook.sh +2 -2
  34. package/resources/pre-push-hook.sh +2 -2
  35. package/resources/rafter-security-skill.md +7 -11
@@ -0,0 +1,25 @@
1
+ /**
2
+ * rafter issues — GitHub Issues integration.
3
+ *
4
+ * Subcommands:
5
+ * rafter issues create --from-scan <id> Create issues from backend scan results
6
+ * rafter issues create --from-local <path> Create issues from local scan JSON
7
+ * rafter issues create --from-text Create issue from natural text (stdin/file/inline)
8
+ */
9
+ import { Command } from "commander";
10
+ import { createFromScanCommand } from "./from-scan.js";
11
+ import { createFromTextCommand } from "./from-text.js";
12
+ export function createIssuesCommand() {
13
+ const issuesGroup = new Command("issues")
14
+ .description("GitHub Issues integration — create issues from scan results or text");
15
+ const createGroup = new Command("create")
16
+ .description("Create GitHub issues from scan findings or natural text");
17
+ createGroup.addCommand(createFromScanCommand());
18
+ createGroup.addCommand(createFromTextCommand());
19
+ // Default action for `rafter issues create` with no subcommand — show help
20
+ createGroup.action(() => {
21
+ createGroup.help();
22
+ });
23
+ issuesGroup.addCommand(createGroup);
24
+ return issuesGroup;
25
+ }
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Build structured GitHub issues from scan findings.
3
+ *
4
+ * Handles both:
5
+ * - Backend scan vulnerabilities (SAST/policy findings)
6
+ * - Local scan results (secret detection)
7
+ */
8
+ import { fingerprint, embedFingerprint } from "./dedup.js";
9
+ function severityLabel(level) {
10
+ const map = {
11
+ error: "critical",
12
+ critical: "critical",
13
+ warning: "high",
14
+ high: "high",
15
+ note: "medium",
16
+ medium: "medium",
17
+ low: "low",
18
+ };
19
+ return map[level.toLowerCase()] || "medium";
20
+ }
21
+ function severityEmoji(level) {
22
+ const sev = severityLabel(level);
23
+ const emojis = {
24
+ critical: "🔴",
25
+ high: "🟠",
26
+ medium: "🟡",
27
+ low: "🟢",
28
+ };
29
+ return emojis[sev] || "🟡";
30
+ }
31
+ export function buildFromBackendVulnerability(vuln) {
32
+ const sev = severityLabel(vuln.level);
33
+ const emoji = severityEmoji(vuln.level);
34
+ const fp = fingerprint(vuln.file, vuln.ruleId);
35
+ const title = `${emoji} [${sev.toUpperCase()}] ${vuln.ruleId}: ${truncate(vuln.message, 80)}`;
36
+ let body = `## Security Finding\n\n`;
37
+ body += `**Rule:** \`${vuln.ruleId}\`\n`;
38
+ body += `**Severity:** ${sev}\n`;
39
+ body += `**File:** \`${vuln.file}\``;
40
+ if (vuln.line)
41
+ body += ` (line ${vuln.line})`;
42
+ body += `\n\n`;
43
+ body += `### Description\n\n${vuln.message}\n\n`;
44
+ body += `### Remediation\n\nReview and fix the finding in \`${vuln.file}\`.\n`;
45
+ body += `\n---\n*Created by [Rafter CLI](https://rafter.so) — security for AI builders*\n`;
46
+ const labels = [
47
+ "security",
48
+ `severity:${sev}`,
49
+ `rule:${vuln.ruleId}`,
50
+ ];
51
+ return {
52
+ title,
53
+ body: embedFingerprint(body, fp),
54
+ labels,
55
+ fingerprint: fp,
56
+ };
57
+ }
58
+ export function buildFromLocalMatch(file, match) {
59
+ const sev = severityLabel(match.pattern.severity);
60
+ const emoji = severityEmoji(match.pattern.severity);
61
+ const fp = fingerprint(file, match.pattern.name);
62
+ const title = `${emoji} [${sev.toUpperCase()}] Secret detected: ${match.pattern.name} in ${basename(file)}`;
63
+ let body = `## Secret Detection\n\n`;
64
+ body += `**Pattern:** \`${match.pattern.name}\`\n`;
65
+ body += `**Severity:** ${sev}\n`;
66
+ body += `**File:** \`${file}\``;
67
+ if (match.line)
68
+ body += ` (line ${match.line})`;
69
+ body += `\n`;
70
+ if (match.redacted) {
71
+ body += `**Match:** \`${match.redacted}\`\n`;
72
+ }
73
+ body += `\n`;
74
+ if (match.pattern.description) {
75
+ body += `### Description\n\n${match.pattern.description}\n\n`;
76
+ }
77
+ body += `### Remediation\n\n`;
78
+ body += `1. Rotate the exposed credential immediately\n`;
79
+ body += `2. Remove the secret from source code\n`;
80
+ body += `3. Use environment variables or a secrets manager instead\n`;
81
+ body += `\n---\n*Created by [Rafter CLI](https://rafter.so) — security for AI builders*\n`;
82
+ const labels = [
83
+ "security",
84
+ "secret-detected",
85
+ `severity:${sev}`,
86
+ ];
87
+ return {
88
+ title,
89
+ body: embedFingerprint(body, fp),
90
+ labels,
91
+ fingerprint: fp,
92
+ };
93
+ }
94
+ function truncate(s, max) {
95
+ if (s.length <= max)
96
+ return s;
97
+ return s.slice(0, max - 3) + "...";
98
+ }
99
+ function basename(filepath) {
100
+ return filepath.split("/").pop() || filepath;
101
+ }
@@ -54,9 +54,14 @@ function generateCodexConfig() {
54
54
  const policy = cfg.agent?.commandPolicy;
55
55
  const blocked = policy?.blockedPatterns || [];
56
56
  const approval = policy?.requireApproval || [];
57
- let toml = `# Rafter security policy for OpenAI Codex
57
+ let toml = `# Rafter security policy for OpenAI Codex CLI
58
58
  # Generated by: rafter policy export --format codex
59
- # Docs: https://docs.rafter.so/cli/pretool-hooks
59
+ #
60
+ # Usage: Save this file to your project root as codex-policy.toml
61
+ # then reference it in your Codex sandbox configuration.
62
+ #
63
+ # For full Codex integration, run: rafter agent init
64
+ # This auto-detects Codex CLI and installs skills to ~/.agents/skills/
60
65
 
61
66
  `;
62
67
  if (blocked.length > 0) {
@@ -0,0 +1,44 @@
1
+ /**
2
+ * rafter scan — top-level scan command group.
3
+ *
4
+ * Default (no subcommand): remote backend scan (same as `rafter run`)
5
+ * rafter scan remote: explicit alias for remote backend scan
6
+ * rafter scan local [path]: local secret scanner (was `rafter agent scan`)
7
+ */
8
+ import { Command } from "commander";
9
+ import { runRemoteScan } from "../backend/run.js";
10
+ import { createScanCommand as createLocalScanCommand } from "../agent/scan.js";
11
+ export function createScanGroupCommand() {
12
+ // "local" subcommand — reuses agent/scan.ts logic, renamed
13
+ const localCmd = createLocalScanCommand();
14
+ localCmd.name("local");
15
+ localCmd.description("Scan files or directories for secrets (local)");
16
+ // "remote" subcommand — same handler as `rafter run`
17
+ const remoteCmd = new Command("remote")
18
+ .description("Trigger a remote backend security scan (explicit alias for 'rafter run')")
19
+ .option("-r, --repo <repo>", "org/repo (default: current)")
20
+ .option("-b, --branch <branch>", "branch (default: current else main)")
21
+ .option("-k, --api-key <key>", "API key or RAFTER_API_KEY env var")
22
+ .option("-f, --format <format>", "json | md", "md")
23
+ .option("--skip-interactive", "do not wait for scan to complete")
24
+ .option("--quiet", "suppress status messages")
25
+ .action(async (opts) => {
26
+ await runRemoteScan(opts);
27
+ });
28
+ // Root scan group — default action is remote backend scan
29
+ const scanGroup = new Command("scan")
30
+ .description("Scan for security issues. Default: remote backend scan. Use 'scan local' for local secret scanning.")
31
+ .option("-r, --repo <repo>", "org/repo (default: current)")
32
+ .option("-b, --branch <branch>", "branch (default: current else main)")
33
+ .option("-k, --api-key <key>", "API key or RAFTER_API_KEY env var")
34
+ .option("-f, --format <format>", "json | md", "md")
35
+ .option("--skip-interactive", "do not wait for scan to complete")
36
+ .option("--quiet", "suppress status messages");
37
+ scanGroup.addCommand(localCmd);
38
+ scanGroup.addCommand(remoteCmd);
39
+ // When invoked with no subcommand, run remote backend scan
40
+ scanGroup.action(async (opts) => {
41
+ await runRemoteScan(opts);
42
+ });
43
+ return scanGroup;
44
+ }
@@ -19,6 +19,30 @@ export function getDefaultConfig() {
19
19
  claudeCode: {
20
20
  enabled: false,
21
21
  mcpPath: path.join(os.homedir(), ".claude", "mcp", "rafter-security.json")
22
+ },
23
+ codex: {
24
+ enabled: false,
25
+ skillsDir: path.join(os.homedir(), ".agents", "skills")
26
+ },
27
+ gemini: {
28
+ enabled: false,
29
+ configPath: path.join(os.homedir(), ".gemini", "settings.json")
30
+ },
31
+ aider: {
32
+ enabled: false,
33
+ configPath: path.join(os.homedir(), ".aider.conf.yml")
34
+ },
35
+ cursor: {
36
+ enabled: false,
37
+ mcpPath: path.join(os.homedir(), ".cursor", "mcp.json")
38
+ },
39
+ windsurf: {
40
+ enabled: false,
41
+ mcpPath: path.join(os.homedir(), ".codeium", "windsurf", "mcp_config.json")
42
+ },
43
+ continueDev: {
44
+ enabled: false,
45
+ configPath: path.join(os.homedir(), ".continue", "config.json")
22
46
  }
23
47
  },
24
48
  skills: {
@@ -179,10 +179,27 @@ export class ConfigManager {
179
179
  * Migrate config to latest version
180
180
  */
181
181
  migrate(config) {
182
- // For now, just ensure version is current
183
- // In future, handle version-specific migrations
182
+ let dirty = false;
184
183
  if (config.version !== CONFIG_VERSION) {
185
184
  config.version = CONFIG_VERSION;
185
+ dirty = true;
186
+ }
187
+ // Fix overly broad curl/wget pipe-to-shell patterns.
188
+ // Old pattern: "curl.*\|.*sh" — matches any command containing "sh" after a pipe
189
+ // (e.g. `grep "curl\|sh"` or `git push`). Replace with word-bounded shell names.
190
+ const badToGood = {
191
+ "curl.*\\|.*sh": "curl.*\\|\\s*(bash|sh|zsh|dash)\\b",
192
+ "wget.*\\|.*sh": "wget.*\\|\\s*(bash|sh|zsh|dash)\\b",
193
+ };
194
+ const approval = config.agent?.commandPolicy?.requireApproval;
195
+ if (Array.isArray(approval)) {
196
+ const fixed = approval.map(p => badToGood[p] ?? p);
197
+ if (fixed.some((p, i) => p !== approval[i])) {
198
+ config.agent.commandPolicy.requireApproval = fixed;
199
+ dirty = true;
200
+ }
201
+ }
202
+ if (dirty) {
186
203
  this.save(config);
187
204
  }
188
205
  return config;
@@ -1,3 +1,7 @@
1
+ const GENERIC_PATTERN_NAMES = new Set(["Generic API Key", "Generic Secret"]);
2
+ const VARIABLE_NAME_RE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/;
3
+ const LOWERCASE_IDENT_RE = /^[a-z][a-z0-9]*(?:_[a-z0-9]+)+$/;
4
+ const QUOTED_VALUE_RE = /['"]([^'"]+)['"]/;
1
5
  export class PatternEngine {
2
6
  constructor(patterns) {
3
7
  this.patterns = patterns;
@@ -11,6 +15,8 @@ export class PatternEngine {
11
15
  const regex = this.createRegex(pattern.regex);
12
16
  let match;
13
17
  while ((match = regex.exec(text)) !== null) {
18
+ if (this.isFalsePositive(pattern, match[0]))
19
+ continue;
14
20
  matches.push({
15
21
  pattern,
16
22
  match: match[0],
@@ -32,6 +38,8 @@ export class PatternEngine {
32
38
  const regex = this.createRegex(pattern.regex);
33
39
  let match;
34
40
  while ((match = regex.exec(line)) !== null) {
41
+ if (this.isFalsePositive(pattern, match[0]))
42
+ continue;
35
43
  matches.push({
36
44
  pattern,
37
45
  match: match[0],
@@ -51,7 +59,7 @@ export class PatternEngine {
51
59
  let redacted = text;
52
60
  for (const pattern of this.patterns) {
53
61
  const regex = this.createRegex(pattern.regex);
54
- redacted = redacted.replace(regex, (match) => this.redact(match));
62
+ redacted = redacted.replace(regex, (match) => this.isFalsePositive(pattern, match) ? match : this.redact(match));
55
63
  }
56
64
  return redacted;
57
65
  }
@@ -67,6 +75,23 @@ export class PatternEngine {
67
75
  getPatternsBySeverity(severity) {
68
76
  return this.patterns.filter(p => p.severity === severity);
69
77
  }
78
+ /**
79
+ * Check if a match from a generic pattern looks like a variable name
80
+ * rather than an actual secret value.
81
+ */
82
+ isFalsePositive(pattern, matchText) {
83
+ if (!GENERIC_PATTERN_NAMES.has(pattern.name))
84
+ return false;
85
+ const m = QUOTED_VALUE_RE.exec(matchText);
86
+ if (!m)
87
+ return false;
88
+ const value = m[1];
89
+ if (VARIABLE_NAME_RE.test(value))
90
+ return true;
91
+ if (LOWERCASE_IDENT_RE.test(value))
92
+ return true;
93
+ return false;
94
+ }
70
95
  /**
71
96
  * Create RegExp from pattern string, extracting inline flags
72
97
  */
package/dist/index.js CHANGED
@@ -4,16 +4,18 @@ import * as dotenv from "dotenv";
4
4
  import { createRunCommand } from "./commands/backend/run.js";
5
5
  import { createGetCommand } from "./commands/backend/get.js";
6
6
  import { createUsageCommand } from "./commands/backend/usage.js";
7
+ import { createScanGroupCommand } from "./commands/scan/index.js";
7
8
  import { createAgentCommand } from "./commands/agent/index.js";
8
9
  import { createCiCommand } from "./commands/ci/index.js";
9
10
  import { createHookCommand } from "./commands/hook/index.js";
10
11
  import { createMcpCommand } from "./commands/mcp/index.js";
11
12
  import { createPolicyCommand } from "./commands/policy/index.js";
12
13
  import { createCompletionCommand } from "./commands/completion.js";
14
+ import { createIssuesCommand } from "./commands/issues/index.js";
13
15
  import { checkForUpdate } from "./utils/update-checker.js";
14
16
  import { setAgentMode } from "./utils/formatter.js";
15
17
  dotenv.config();
16
- const VERSION = "0.5.5";
18
+ const VERSION = "0.5.7";
17
19
  const program = new Command()
18
20
  .name("rafter")
19
21
  .description("Rafter CLI")
@@ -26,10 +28,12 @@ program.hook("preAction", (thisCommand) => {
26
28
  setAgentMode(true);
27
29
  }
28
30
  });
29
- // Backend commands (existing)
31
+ // Backend commands
30
32
  program.addCommand(createRunCommand());
31
33
  program.addCommand(createGetCommand());
32
34
  program.addCommand(createUsageCommand());
35
+ // Scan command group (default: remote backend scan; subcommands: local, remote)
36
+ program.addCommand(createScanGroupCommand());
33
37
  // Agent commands
34
38
  program.addCommand(createAgentCommand());
35
39
  // CI commands
@@ -40,6 +44,8 @@ program.addCommand(createHookCommand());
40
44
  program.addCommand(createMcpCommand());
41
45
  // Policy commands
42
46
  program.addCommand(createPolicyCommand());
47
+ // GitHub Issues integration
48
+ program.addCommand(createIssuesCommand());
43
49
  // Shell completions
44
50
  program.addCommand(createCompletionCommand());
45
51
  // Non-blocking update check — runs after command, prints to stderr
@@ -44,11 +44,7 @@ export class GitleaksScanner {
44
44
  };
45
45
  }
46
46
  catch (e) {
47
- // Clean up report
48
- if (fs.existsSync(tmpReport)) {
49
- fs.unlinkSync(tmpReport);
50
- }
51
- // Gitleaks exits with code 1 when leaks found
47
+ // Gitleaks exits with code 1 when leaks found — read report before cleanup
52
48
  if (e.code === 1 && fs.existsSync(tmpReport)) {
53
49
  const results = this.parseResults(tmpReport);
54
50
  fs.unlinkSync(tmpReport);
@@ -57,6 +53,10 @@ export class GitleaksScanner {
57
53
  matches: results.map(r => this.convertToPatternMatch(r))
58
54
  };
59
55
  }
56
+ // Clean up report for non-leak errors
57
+ if (fs.existsSync(tmpReport)) {
58
+ fs.unlinkSync(tmpReport);
59
+ }
60
60
  throw new Error(`Gitleaks scan failed: ${e.message}`);
61
61
  }
62
62
  }
@@ -57,7 +57,18 @@ export class RegexScanner {
57
57
  ".next",
58
58
  "coverage",
59
59
  ".vscode",
60
- ".idea"
60
+ ".idea",
61
+ // Vendored / virtual-env / generated dirs that cause false positives
62
+ "vendor",
63
+ ".venv",
64
+ "venv",
65
+ "__pycache__",
66
+ ".tox",
67
+ ".mypy_cache",
68
+ ".pytest_cache",
69
+ "results",
70
+ ".terraform",
71
+ "bower_components"
61
72
  ];
62
73
  // Merge policy excludePaths into the exclude list
63
74
  if (options?.excludePaths) {
@@ -90,13 +90,13 @@ export const DEFAULT_SECRET_PATTERNS = [
90
90
  // Generic patterns
91
91
  {
92
92
  name: "Generic API Key",
93
- regex: "(?i)(api[_-]?key|apikey)[\\s]*[:=][\\s]*['\"]?[0-9a-zA-Z\\-_]{16,}['\"]?",
93
+ regex: "(?i)(?<![a-zA-Z0-9_])(api[_-]?key|apikey)[\\s]*[:=][\\s]*['\"](?=[0-9a-zA-Z\\-_]*[0-9])[0-9a-zA-Z\\-_]{16,}['\"]",
94
94
  severity: "high",
95
95
  description: "Generic API key pattern detected"
96
96
  },
97
97
  {
98
98
  name: "Generic Secret",
99
- regex: "(?i)(secret|password|passwd|pwd)[\\s]*[:=][\\s]*['\"]?[0-9a-zA-Z\\-_!@#$%^&*()]{8,}['\"]?",
99
+ regex: "(?i)(?<![a-zA-Z0-9_])(secret|password|passwd|pwd)[\\s]*[:=][\\s]*['\"](?=[^\\s'\"]*[0-9])(?=[^\\s'\"]*[a-zA-Z])[0-9a-zA-Z\\-_!@#$%^&*()]{12,}['\"]",
100
100
  severity: "high",
101
101
  description: "Generic secret pattern detected"
102
102
  },
@@ -108,7 +108,7 @@ export const DEFAULT_SECRET_PATTERNS = [
108
108
  },
109
109
  {
110
110
  name: "Bearer Token",
111
- regex: "(?i)bearer[\\s]+[a-zA-Z0-9\\-_\\.=]+",
111
+ regex: "(?i)bearer[\\s]+(?=[a-zA-Z0-9\\-_\\.=]*[0-9])(?=[a-zA-Z0-9\\-_\\.=]*[a-zA-Z])[a-zA-Z0-9\\-_\\.=]{20,}",
112
112
  severity: "high",
113
113
  description: "Bearer token detected"
114
114
  },
@@ -75,10 +75,10 @@ export class BinaryManager {
75
75
  return false;
76
76
  }
77
77
  try {
78
- const { stdout } = await execAsync(`"${this.getGitleaksPath()}" version`, {
79
- timeout: 5000
80
- });
81
- return stdout.includes("gitleaks version");
78
+ // execAsync rejects on non-zero exit, so reaching here means exit code 0.
79
+ // Accept any successful exit — don't require specific stdout content.
80
+ await execAsync(`"${this.getGitleaksPath()}" version`, { timeout: 5000 });
81
+ return true;
82
82
  }
83
83
  catch {
84
84
  return false;
@@ -91,8 +91,9 @@ export class BinaryManager {
91
91
  const gitleaksPath = binaryPath ?? this.getGitleaksPath();
92
92
  try {
93
93
  const { stdout, stderr } = await execAsync(`"${gitleaksPath}" version`, { timeout: 5000 });
94
- const ok = stdout.includes("gitleaks version");
95
- return { ok, stdout: stdout.trim(), stderr: stderr.trim() };
94
+ // execAsync rejects on non-zero exit, so reaching here means exit code 0.
95
+ // Accept any successful exit — don't require specific stdout content.
96
+ return { ok: true, stdout: stdout.trim(), stderr: stderr.trim() };
96
97
  }
97
98
  catch (e) {
98
99
  const err = e;
@@ -156,15 +156,17 @@ export class SkillManager {
156
156
  async installRafterSkillVerbose(force = false) {
157
157
  const skillPath = this.getRafterSkillPath();
158
158
  const sourcePath = this.getRafterSkillSourcePath();
159
- if (!this.isOpenClawInstalled()) {
160
- return { ok: false, sourcePath, destPath: skillPath, error: `OpenClaw skills directory not found: ${this.getOpenClawSkillsDir()}` };
159
+ // Check if ~/.openclaw exists (the parent dir), not just the skills subdir
160
+ const openclawDir = path.join(os.homedir(), ".openclaw");
161
+ if (!fs.existsSync(openclawDir)) {
162
+ return { ok: false, sourcePath, destPath: skillPath, error: `OpenClaw not found: ${openclawDir}` };
161
163
  }
162
164
  // Check if already installed and not forcing
163
165
  if (!force && this.isRafterSkillInstalled()) {
164
166
  return { ok: true, sourcePath, destPath: skillPath };
165
167
  }
166
168
  try {
167
- // Ensure skills directory exists
169
+ // Ensure skills directory exists (may not exist on fresh OpenClaw installs)
168
170
  const skillsDir = this.getOpenClawSkillsDir();
169
171
  if (!fs.existsSync(skillsDir)) {
170
172
  fs.mkdirSync(skillsDir, { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafter-security/cli",
3
- "version": "0.5.5",
3
+ "version": "0.5.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "rafter": "./dist/index.js"
@@ -22,6 +22,7 @@
22
22
  "@modelcontextprotocol/sdk": "^1.12.0",
23
23
  "axios": "^1.6.8",
24
24
  "chalk": "^5.3.0",
25
+ "chokidar": "^5.0.0",
25
26
  "commander": "^11.1.0",
26
27
  "dotenv": "^16.4.5",
27
28
  "js-yaml": "^4.1.0",
@@ -27,14 +27,14 @@ fi
27
27
  echo "🔍 Rafter: Scanning staged files for secrets..."
28
28
 
29
29
  # Scan staged files
30
- rafter agent scan --staged --quiet
30
+ rafter scan local --staged --quiet
31
31
 
32
32
  EXIT_CODE=$?
33
33
 
34
34
  if [ $EXIT_CODE -ne 0 ]; then
35
35
  echo -e "${RED}❌ Commit blocked: Secrets detected in staged files${NC}"
36
36
  echo ""
37
- echo " Run: rafter agent scan --staged"
37
+ echo " Run: rafter scan local --staged"
38
38
  echo " To see details and remediate."
39
39
  echo ""
40
40
  echo " To bypass (NOT recommended): git commit --no-verify"
@@ -38,7 +38,7 @@ while read local_ref local_sha remote_ref remote_sha; do
38
38
 
39
39
  echo "🔍 Rafter: Scanning commits being pushed ($local_ref)..."
40
40
 
41
- rafter agent scan --diff "$ref_arg" --quiet
41
+ rafter scan local --diff "$ref_arg" --quiet
42
42
  EXIT_CODE=$?
43
43
 
44
44
  if [ $EXIT_CODE -ne 0 ]; then
@@ -49,7 +49,7 @@ done
49
49
  if [ $FOUND_SECRETS -ne 0 ]; then
50
50
  echo -e "${RED}❌ Push blocked: Secrets detected in commits being pushed${NC}"
51
51
  echo ""
52
- echo " Run: rafter agent scan --diff <remote-sha>"
52
+ echo " Run: rafter scan local --diff <remote-sha>"
53
53
  echo " To see details and remediate."
54
54
  echo ""
55
55
  echo " To bypass (NOT recommended): git push --no-verify"
@@ -6,8 +6,8 @@ openclaw:
6
6
  always: false
7
7
  requires:
8
8
  bins: [rafter]
9
- version: 0.4.0
10
- last_updated: 2026-02-03
9
+ version: 0.5.8
10
+ last_updated: 2026-03-04
11
11
  ---
12
12
 
13
13
  # Rafter Security
@@ -32,7 +32,7 @@ Rafter provides real-time security checks for agent operations:
32
32
  Scan files for secrets before committing.
33
33
 
34
34
  ```bash
35
- rafter agent scan <path>
35
+ rafter scan local <path>
36
36
  ```
37
37
 
38
38
  **When to use:**
@@ -57,21 +57,17 @@ rafter agent scan <path>
57
57
 
58
58
  ### /rafter-bash
59
59
 
60
- Execute shell command with security validation.
60
+ Explicitly run a command through Rafter's security validator.
61
61
 
62
62
  ```bash
63
63
  rafter agent exec <command>
64
64
  ```
65
65
 
66
- **Features:**
67
- - Blocks destructive commands (rm -rf /, fork bombs)
68
- - Requires approval for dangerous operations
69
- - Logs all command attempts
70
- - Scans staged files before git commits
66
+ **When to use:** Only needed in environments where the `PreToolUse` hook is not installed. When `rafter agent init` has been run, all shell commands are validated automatically — you do not need to route commands through this.
71
67
 
72
68
  **Risk levels:**
73
69
  - **Critical** (blocked): rm -rf /, fork bombs, dd to /dev
74
- - **High** (approval required): sudo rm, chmod 777, curl|bash
70
+ - **High** (approval required): sudo rm, chmod 777, curl | bash
75
71
  - **Medium** (approval on moderate+): sudo, chmod, kill -9
76
72
  - **Low** (allowed): npm install, git commit, ls
77
73
 
@@ -269,7 +265,7 @@ Configure with: `rafter agent config set agent.riskLevel moderate`
269
265
 
270
266
  ## Best Practices
271
267
 
272
- 1. **Always scan before commits**: Run `rafter agent scan` before `git commit`
268
+ 1. **Always scan before commits**: Run `rafter scan local` before `git commit`
273
269
  2. **Audit untrusted skills**: Run `/rafter-audit-skill` on skills from unknown sources before installation
274
270
  3. **Review audit logs**: Check `rafter agent audit` after suspicious activity
275
271
  4. **Keep patterns updated**: Patterns updated automatically with CLI updates