@rafter-security/cli 0.4.2 → 0.5.3
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.
- package/README.md +101 -1
- package/dist/commands/agent/audit-skill.js +6 -0
- package/dist/commands/agent/audit.js +15 -3
- package/dist/commands/agent/exec.js +9 -8
- package/dist/commands/agent/index.js +4 -0
- package/dist/commands/agent/init.js +132 -47
- package/dist/commands/agent/install-hook.js +2 -1
- package/dist/commands/agent/scan.js +180 -103
- package/dist/commands/agent/status.js +115 -0
- package/dist/commands/agent/verify.js +117 -0
- package/dist/commands/ci/index.js +8 -0
- package/dist/commands/ci/init.js +191 -0
- package/dist/commands/completion.js +170 -0
- package/dist/commands/hook/index.js +10 -0
- package/dist/commands/hook/posttool.js +73 -0
- package/dist/commands/hook/pretool.js +122 -0
- package/dist/commands/mcp/index.js +8 -0
- package/dist/commands/mcp/server.js +205 -0
- package/dist/commands/policy/export.js +81 -0
- package/dist/commands/policy/index.js +8 -0
- package/dist/core/audit-logger.js +2 -33
- package/dist/core/command-interceptor.js +6 -50
- package/dist/core/config-defaults.js +4 -15
- package/dist/core/config-manager.js +68 -0
- package/dist/core/custom-patterns.js +157 -0
- package/dist/core/policy-loader.js +167 -0
- package/dist/core/risk-rules.js +72 -0
- package/dist/index.js +26 -2
- package/dist/scanners/gitleaks.js +7 -6
- package/dist/scanners/regex-scanner.js +28 -12
- package/dist/utils/binary-manager.js +100 -7
- package/dist/utils/formatter.js +52 -0
- package/dist/utils/skill-manager.js +22 -9
- package/package.json +7 -3
- package/resources/pre-commit-hook.sh +45 -0
- package/resources/rafter-security-skill.md +323 -0
package/README.md
CHANGED
|
@@ -73,6 +73,14 @@ rafter agent audit
|
|
|
73
73
|
rafter agent config show
|
|
74
74
|
```
|
|
75
75
|
|
|
76
|
+
## Global Options
|
|
77
|
+
|
|
78
|
+
| Flag | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `-a, --agent` | Plain output for AI agents (no colors, no emoji) |
|
|
81
|
+
| `-V, --version` | Print version |
|
|
82
|
+
| `-h, --help` | Show help |
|
|
83
|
+
|
|
76
84
|
## Commands
|
|
77
85
|
|
|
78
86
|
### `rafter run [options]`
|
|
@@ -169,6 +177,7 @@ Scan files or directories for secrets.
|
|
|
169
177
|
**Options:**
|
|
170
178
|
- `-q, --quiet` - Only output if secrets found
|
|
171
179
|
- `--json` - Output results as JSON
|
|
180
|
+
- `--diff <ref>` - Scan files changed since a git ref (e.g., `HEAD~1`, `main`, `v1.0.0`)
|
|
172
181
|
|
|
173
182
|
**Features:**
|
|
174
183
|
- Detects 21+ secret types (AWS, GitHub, Stripe, Google, Slack, etc.)
|
|
@@ -185,6 +194,10 @@ rafter agent scan
|
|
|
185
194
|
# Scan specific file
|
|
186
195
|
rafter agent scan ./config.js
|
|
187
196
|
|
|
197
|
+
# Scan files changed since a ref
|
|
198
|
+
rafter agent scan --diff HEAD~1
|
|
199
|
+
rafter agent scan --diff main
|
|
200
|
+
|
|
188
201
|
# Scan for CI (quiet mode)
|
|
189
202
|
rafter agent scan --quiet
|
|
190
203
|
|
|
@@ -433,6 +446,93 @@ Claude Code skills can:
|
|
|
433
446
|
|
|
434
447
|
Always audit skills from untrusted sources before installation. The skill-auditor provides systematic analysis to identify security risks.
|
|
435
448
|
|
|
449
|
+
### `rafter mcp serve [options]`
|
|
450
|
+
|
|
451
|
+
Start an MCP server exposing Rafter security tools over stdio transport. Any MCP-compatible client (Cursor, Windsurf, Claude Desktop, Cline, etc.) can connect.
|
|
452
|
+
|
|
453
|
+
**Options:**
|
|
454
|
+
- `--transport <type>` - Transport type (default: `stdio`)
|
|
455
|
+
|
|
456
|
+
**MCP client config:**
|
|
457
|
+
```json
|
|
458
|
+
{
|
|
459
|
+
"rafter": {
|
|
460
|
+
"command": "rafter",
|
|
461
|
+
"args": ["mcp", "serve"]
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Tools provided:**
|
|
467
|
+
|
|
468
|
+
| Tool | Description |
|
|
469
|
+
|------|-------------|
|
|
470
|
+
| `scan_secrets` | Scan files/directories for hardcoded secrets. Params: `path` (required), `engine` (auto/gitleaks/patterns) |
|
|
471
|
+
| `evaluate_command` | Check if a shell command is allowed by Rafter policy. Params: `command` (required) |
|
|
472
|
+
| `read_audit_log` | Read audit log entries. Params: `limit`, `event_type`, `since` |
|
|
473
|
+
| `get_config` | Read Rafter config. Params: `key` (optional dot-path) |
|
|
474
|
+
|
|
475
|
+
**Resources provided:**
|
|
476
|
+
|
|
477
|
+
| URI | Description |
|
|
478
|
+
|-----|-------------|
|
|
479
|
+
| `rafter://config` | Current Rafter configuration (JSON) |
|
|
480
|
+
| `rafter://policy` | Active security policy — merged `.rafter.yml` + config (JSON) |
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
### `rafter ci init [options]`
|
|
485
|
+
|
|
486
|
+
Generate CI/CD pipeline configuration for secret scanning.
|
|
487
|
+
|
|
488
|
+
**Options:**
|
|
489
|
+
- `--platform <platform>` - CI platform: `github`, `gitlab`, `circleci` (default: auto-detect)
|
|
490
|
+
- `--output <path>` - Output file path (default: platform-specific)
|
|
491
|
+
- `--with-backend` - Include backend security audit job (requires `RAFTER_API_KEY`)
|
|
492
|
+
|
|
493
|
+
**Auto-detection:** Checks for `.github/`, `.gitlab-ci.yml`, `.circleci/` in the current directory.
|
|
494
|
+
|
|
495
|
+
**Examples:**
|
|
496
|
+
```bash
|
|
497
|
+
# Auto-detect platform
|
|
498
|
+
rafter ci init
|
|
499
|
+
|
|
500
|
+
# Generate GitHub Actions workflow
|
|
501
|
+
rafter ci init --platform github
|
|
502
|
+
|
|
503
|
+
# Include backend scanning job
|
|
504
|
+
rafter ci init --with-backend
|
|
505
|
+
|
|
506
|
+
# Custom output path
|
|
507
|
+
rafter ci init --output .github/workflows/security.yml
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
---
|
|
511
|
+
|
|
512
|
+
## Policy File (`.rafter.yml`)
|
|
513
|
+
|
|
514
|
+
Define per-project security policies by placing a `.rafter.yml` in your project root. The CLI walks from cwd up to git root looking for it.
|
|
515
|
+
|
|
516
|
+
```yaml
|
|
517
|
+
version: "1"
|
|
518
|
+
risk_level: moderate
|
|
519
|
+
command_policy:
|
|
520
|
+
mode: approve-dangerous
|
|
521
|
+
blocked_patterns: ["rm -rf /"]
|
|
522
|
+
require_approval: ["npm publish"]
|
|
523
|
+
scan:
|
|
524
|
+
exclude_paths: ["vendor/", "third_party/"]
|
|
525
|
+
custom_patterns:
|
|
526
|
+
- name: "Internal API Key"
|
|
527
|
+
regex: "INTERNAL_[A-Z0-9]{32}"
|
|
528
|
+
severity: critical
|
|
529
|
+
audit:
|
|
530
|
+
retention_days: 90
|
|
531
|
+
log_level: info
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
**Precedence:** Policy file values override `~/.rafter/config.json`. Arrays replace, not append.
|
|
535
|
+
|
|
436
536
|
---
|
|
437
537
|
|
|
438
538
|
## Configuration
|
|
@@ -466,7 +566,7 @@ Agent security settings are stored in `~/.rafter/config.json`. Key settings:
|
|
|
466
566
|
|
|
467
567
|
**File Locations:**
|
|
468
568
|
- Config: `~/.rafter/config.json`
|
|
469
|
-
- Audit log: `~/.rafter/audit.
|
|
569
|
+
- Audit log: `~/.rafter/audit.jsonl`
|
|
470
570
|
- Binaries: `~/.rafter/bin/`
|
|
471
571
|
- Patterns: `~/.rafter/patterns/`
|
|
472
572
|
|
|
@@ -48,6 +48,9 @@ async function auditSkill(skillPath, opts) {
|
|
|
48
48
|
rafterSkillInstalled
|
|
49
49
|
};
|
|
50
50
|
console.log(JSON.stringify(result, null, 2));
|
|
51
|
+
if (quickScan.secrets > 0 || quickScan.highRiskCommands.length > 0) {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
51
54
|
return;
|
|
52
55
|
}
|
|
53
56
|
// Check if we can use OpenClaw
|
|
@@ -85,6 +88,9 @@ async function auditSkill(skillPath, opts) {
|
|
|
85
88
|
console.log("─".repeat(60));
|
|
86
89
|
}
|
|
87
90
|
console.log();
|
|
91
|
+
if (quickScan.secrets > 0 || quickScan.highRiskCommands.length > 0) {
|
|
92
|
+
process.exit(1);
|
|
93
|
+
}
|
|
88
94
|
}
|
|
89
95
|
async function runQuickScan(content) {
|
|
90
96
|
// 1. Scan for secrets
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { AuditLogger } from "../../core/audit-logger.js";
|
|
3
|
+
import { isAgentMode } from "../../utils/formatter.js";
|
|
3
4
|
export function createAuditCommand() {
|
|
4
5
|
return new Command("audit")
|
|
5
6
|
.description("View audit log entries")
|
|
@@ -29,8 +30,8 @@ export function createAuditCommand() {
|
|
|
29
30
|
console.log(`\nShowing ${entries.length} audit log entries:\n`);
|
|
30
31
|
for (const entry of entries) {
|
|
31
32
|
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
32
|
-
const
|
|
33
|
-
console.log(`${
|
|
33
|
+
const indicator = getEventIndicator(entry.eventType);
|
|
34
|
+
console.log(`${indicator} [${timestamp}] ${entry.eventType}`);
|
|
34
35
|
if (entry.agentType) {
|
|
35
36
|
console.log(` Agent: ${entry.agentType}`);
|
|
36
37
|
}
|
|
@@ -52,7 +53,18 @@ export function createAuditCommand() {
|
|
|
52
53
|
}
|
|
53
54
|
});
|
|
54
55
|
}
|
|
55
|
-
function
|
|
56
|
+
function getEventIndicator(eventType) {
|
|
57
|
+
if (isAgentMode()) {
|
|
58
|
+
const tagMap = {
|
|
59
|
+
command_intercepted: "[INTERCEPT]",
|
|
60
|
+
secret_detected: "[SECRET]",
|
|
61
|
+
content_sanitized: "[SANITIZE]",
|
|
62
|
+
policy_override: "[OVERRIDE]",
|
|
63
|
+
scan_executed: "[SCAN]",
|
|
64
|
+
config_changed: "[CONFIG]",
|
|
65
|
+
};
|
|
66
|
+
return tagMap[eventType] || "[EVENT]";
|
|
67
|
+
}
|
|
56
68
|
const emojiMap = {
|
|
57
69
|
command_intercepted: "🛡️",
|
|
58
70
|
secret_detected: "🔑",
|
|
@@ -3,6 +3,7 @@ import { CommandInterceptor } from "../../core/command-interceptor.js";
|
|
|
3
3
|
import { RegexScanner } from "../../scanners/regex-scanner.js";
|
|
4
4
|
import { execSync } from "child_process";
|
|
5
5
|
import readline from "readline";
|
|
6
|
+
import { fmt } from "../../utils/formatter.js";
|
|
6
7
|
export function createExecCommand() {
|
|
7
8
|
return new Command("exec")
|
|
8
9
|
.description("Execute command with security validation")
|
|
@@ -15,7 +16,7 @@ export function createExecCommand() {
|
|
|
15
16
|
const evaluation = interceptor.evaluate(command);
|
|
16
17
|
// Step 2: Handle blocked commands
|
|
17
18
|
if (!evaluation.allowed && !evaluation.requiresApproval) {
|
|
18
|
-
console.error(`\n
|
|
19
|
+
console.error(`\n${fmt.error("Command BLOCKED")}\n`);
|
|
19
20
|
console.error(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
|
|
20
21
|
console.error(`Reason: ${evaluation.reason}`);
|
|
21
22
|
console.error(`Command: ${command}\n`);
|
|
@@ -26,7 +27,7 @@ export function createExecCommand() {
|
|
|
26
27
|
if (!opts.skipScan && isGitCommand(command)) {
|
|
27
28
|
const scanResult = await scanStagedFiles();
|
|
28
29
|
if (scanResult.secretsFound) {
|
|
29
|
-
console.error(`\n
|
|
30
|
+
console.error(`\n${fmt.warning("Secrets detected in staged files!")}\n`);
|
|
30
31
|
console.error(`Found ${scanResult.count} secret(s) in ${scanResult.files} file(s)`);
|
|
31
32
|
console.error(`\nRun 'rafter agent scan' for details.\n`);
|
|
32
33
|
interceptor.logEvaluation(evaluation, "blocked");
|
|
@@ -35,7 +36,7 @@ export function createExecCommand() {
|
|
|
35
36
|
}
|
|
36
37
|
// Step 4: Handle approval required
|
|
37
38
|
if (evaluation.requiresApproval && !opts.force) {
|
|
38
|
-
console.log(`\n
|
|
39
|
+
console.log(`\n${fmt.warning("Command requires approval")}\n`);
|
|
39
40
|
console.log(`Risk Level: ${evaluation.riskLevel.toUpperCase()}`);
|
|
40
41
|
console.log(`Command: ${command}`);
|
|
41
42
|
if (evaluation.reason) {
|
|
@@ -44,15 +45,15 @@ export function createExecCommand() {
|
|
|
44
45
|
console.log();
|
|
45
46
|
const approved = await promptApproval();
|
|
46
47
|
if (!approved) {
|
|
47
|
-
console.log("
|
|
48
|
+
console.log(`\n${fmt.error("Command cancelled")}\n`);
|
|
48
49
|
interceptor.logEvaluation(evaluation, "blocked");
|
|
49
50
|
process.exit(1);
|
|
50
51
|
}
|
|
51
|
-
console.log("
|
|
52
|
+
console.log(`\n${fmt.success("Command approved by user")}\n`);
|
|
52
53
|
interceptor.logEvaluation(evaluation, "overridden");
|
|
53
54
|
}
|
|
54
55
|
else if (opts.force && evaluation.requiresApproval) {
|
|
55
|
-
console.log(`\n
|
|
56
|
+
console.log(`\n${fmt.warning("Forcing execution (--force flag)")}\n`);
|
|
56
57
|
interceptor.logEvaluation(evaluation, "overridden");
|
|
57
58
|
}
|
|
58
59
|
else {
|
|
@@ -64,11 +65,11 @@ export function createExecCommand() {
|
|
|
64
65
|
stdio: "inherit",
|
|
65
66
|
encoding: "utf-8"
|
|
66
67
|
});
|
|
67
|
-
console.log(`\n
|
|
68
|
+
console.log(`\n${fmt.success("Command executed successfully")}\n`);
|
|
68
69
|
process.exit(0);
|
|
69
70
|
}
|
|
70
71
|
catch (e) {
|
|
71
|
-
console.error(`\n
|
|
72
|
+
console.error(`\n${fmt.error(`Command failed with exit code ${e.status}`)}\n`);
|
|
72
73
|
process.exit(e.status || 1);
|
|
73
74
|
}
|
|
74
75
|
});
|
|
@@ -6,6 +6,8 @@ import { createConfigCommand } from "./config.js";
|
|
|
6
6
|
import { createExecCommand } from "./exec.js";
|
|
7
7
|
import { createAuditSkillCommand } from "./audit-skill.js";
|
|
8
8
|
import { createInstallHookCommand } from "./install-hook.js";
|
|
9
|
+
import { createVerifyCommand } from "./verify.js";
|
|
10
|
+
import { createStatusCommand } from "./status.js";
|
|
9
11
|
export function createAgentCommand() {
|
|
10
12
|
const agent = new Command("agent")
|
|
11
13
|
.description("Agent security features");
|
|
@@ -17,5 +19,7 @@ export function createAgentCommand() {
|
|
|
17
19
|
agent.addCommand(createAuditCommand());
|
|
18
20
|
agent.addCommand(createAuditSkillCommand());
|
|
19
21
|
agent.addCommand(createInstallHookCommand());
|
|
22
|
+
agent.addCommand(createVerifyCommand());
|
|
23
|
+
agent.addCommand(createStatusCommand());
|
|
20
24
|
return agent;
|
|
21
25
|
}
|
|
@@ -6,8 +6,52 @@ import fs from "fs";
|
|
|
6
6
|
import path from "path";
|
|
7
7
|
import os from "os";
|
|
8
8
|
import { fileURLToPath } from "url";
|
|
9
|
+
import { fmt } from "../../utils/formatter.js";
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = path.dirname(__filename);
|
|
12
|
+
function installClaudeCodeHooks() {
|
|
13
|
+
const homeDir = os.homedir();
|
|
14
|
+
const settingsPath = path.join(homeDir, ".claude", "settings.json");
|
|
15
|
+
const claudeDir = path.join(homeDir, ".claude");
|
|
16
|
+
if (!fs.existsSync(claudeDir)) {
|
|
17
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
// Read existing settings or start fresh
|
|
20
|
+
let settings = {};
|
|
21
|
+
if (fs.existsSync(settingsPath)) {
|
|
22
|
+
try {
|
|
23
|
+
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// Corrupted file — start fresh but warn
|
|
27
|
+
console.log(fmt.warning("Existing settings.json was unreadable, creating new one"));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
// Merge hooks — don't overwrite existing non-Rafter hooks
|
|
31
|
+
if (!settings.hooks)
|
|
32
|
+
settings.hooks = {};
|
|
33
|
+
if (!settings.hooks.PreToolUse)
|
|
34
|
+
settings.hooks.PreToolUse = [];
|
|
35
|
+
if (!settings.hooks.PostToolUse)
|
|
36
|
+
settings.hooks.PostToolUse = [];
|
|
37
|
+
const preHook = { type: "command", command: "rafter hook pretool" };
|
|
38
|
+
const postHook = { type: "command", command: "rafter hook posttool" };
|
|
39
|
+
// Remove any existing Rafter hooks to avoid duplicates
|
|
40
|
+
settings.hooks.PreToolUse = settings.hooks.PreToolUse.filter((entry) => {
|
|
41
|
+
const hooks = entry.hooks || [];
|
|
42
|
+
return !hooks.some((h) => h.command === "rafter hook pretool");
|
|
43
|
+
});
|
|
44
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((entry) => {
|
|
45
|
+
const hooks = entry.hooks || [];
|
|
46
|
+
return !hooks.some((h) => h.command === "rafter hook posttool");
|
|
47
|
+
});
|
|
48
|
+
// Add Rafter hooks
|
|
49
|
+
settings.hooks.PreToolUse.push({ matcher: "Bash", hooks: [preHook] }, { matcher: "Write|Edit", hooks: [preHook] });
|
|
50
|
+
settings.hooks.PostToolUse.push({ matcher: ".*", hooks: [postHook] });
|
|
51
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
|
|
52
|
+
console.log(fmt.success(`Installed PreToolUse hooks to ${settingsPath}`));
|
|
53
|
+
console.log(fmt.success(`Installed PostToolUse hooks to ${settingsPath}`));
|
|
54
|
+
}
|
|
11
55
|
async function installClaudeCodeSkills() {
|
|
12
56
|
const homeDir = os.homedir();
|
|
13
57
|
const claudeSkillsDir = path.join(homeDir, ".claude", "skills");
|
|
@@ -24,10 +68,10 @@ async function installClaudeCodeSkills() {
|
|
|
24
68
|
}
|
|
25
69
|
if (fs.existsSync(backendTemplatePath)) {
|
|
26
70
|
fs.copyFileSync(backendTemplatePath, backendSkillPath);
|
|
27
|
-
console.log(
|
|
71
|
+
console.log(fmt.success(`Installed Rafter Backend skill to ${backendSkillPath}`));
|
|
28
72
|
}
|
|
29
73
|
else {
|
|
30
|
-
console.log(
|
|
74
|
+
console.log(fmt.warning(`Backend skill template not found at ${backendTemplatePath}`));
|
|
31
75
|
}
|
|
32
76
|
// Install Agent Security Skill
|
|
33
77
|
const agentSkillDir = path.join(claudeSkillsDir, "rafter-agent-security");
|
|
@@ -38,10 +82,10 @@ async function installClaudeCodeSkills() {
|
|
|
38
82
|
}
|
|
39
83
|
if (fs.existsSync(agentTemplatePath)) {
|
|
40
84
|
fs.copyFileSync(agentTemplatePath, agentSkillPath);
|
|
41
|
-
console.log(
|
|
85
|
+
console.log(fmt.success(`Installed Rafter Agent Security skill to ${agentSkillPath}`));
|
|
42
86
|
}
|
|
43
87
|
else {
|
|
44
|
-
console.log(
|
|
88
|
+
console.log(fmt.warning(`Agent Security skill template not found at ${agentTemplatePath}`));
|
|
45
89
|
}
|
|
46
90
|
}
|
|
47
91
|
export function createInitCommand() {
|
|
@@ -53,100 +97,141 @@ export function createInitCommand() {
|
|
|
53
97
|
.option("--claude-code", "Force Claude Code skill installation")
|
|
54
98
|
.option("--skip-gitleaks", "Skip Gitleaks binary download")
|
|
55
99
|
.action(async (opts) => {
|
|
56
|
-
console.log("
|
|
57
|
-
console.log(
|
|
100
|
+
console.log(fmt.header("Rafter Agent Security Setup"));
|
|
101
|
+
console.log(fmt.divider());
|
|
58
102
|
console.log();
|
|
59
103
|
const manager = new ConfigManager();
|
|
60
104
|
// Detect environments
|
|
61
105
|
const hasOpenClaw = fs.existsSync(path.join(os.homedir(), ".openclaw"));
|
|
62
106
|
const hasClaudeCode = opts.claudeCode || fs.existsSync(path.join(os.homedir(), ".claude"));
|
|
63
107
|
if (hasOpenClaw) {
|
|
64
|
-
console.log("
|
|
108
|
+
console.log(fmt.success("Detected environment: OpenClaw"));
|
|
65
109
|
}
|
|
66
110
|
else {
|
|
67
|
-
console.log("
|
|
111
|
+
console.log(fmt.info("OpenClaw not detected"));
|
|
68
112
|
}
|
|
69
113
|
if (hasClaudeCode) {
|
|
70
|
-
console.log("
|
|
114
|
+
console.log(fmt.success("Detected environment: Claude Code"));
|
|
71
115
|
}
|
|
72
116
|
else {
|
|
73
|
-
console.log("
|
|
117
|
+
console.log(fmt.info("Claude Code not detected"));
|
|
74
118
|
}
|
|
75
119
|
// Initialize directory structure
|
|
76
120
|
try {
|
|
77
121
|
await manager.initialize();
|
|
78
|
-
console.log(
|
|
122
|
+
console.log(fmt.success("Created config at ~/.rafter/config.json"));
|
|
79
123
|
}
|
|
80
124
|
catch (e) {
|
|
81
|
-
console.error(`Failed to initialize: ${e}`);
|
|
125
|
+
console.error(fmt.error(`Failed to initialize: ${e}`));
|
|
82
126
|
process.exit(1);
|
|
83
127
|
}
|
|
84
128
|
// Set risk level
|
|
85
129
|
const validRiskLevels = ["minimal", "moderate", "aggressive"];
|
|
86
130
|
if (!validRiskLevels.includes(opts.riskLevel)) {
|
|
87
|
-
console.error(`Invalid risk level: ${opts.riskLevel}`);
|
|
131
|
+
console.error(fmt.error(`Invalid risk level: ${opts.riskLevel}`));
|
|
88
132
|
console.error(`Valid options: ${validRiskLevels.join(", ")}`);
|
|
89
133
|
process.exit(1);
|
|
90
134
|
}
|
|
91
135
|
manager.set("agent.riskLevel", opts.riskLevel);
|
|
92
|
-
console.log(
|
|
93
|
-
//
|
|
136
|
+
console.log(fmt.success(`Set risk level: ${opts.riskLevel}`));
|
|
137
|
+
// Check / download Gitleaks binary (optional)
|
|
94
138
|
if (!opts.skipGitleaks) {
|
|
95
139
|
const binaryManager = new BinaryManager();
|
|
96
140
|
const platformInfo = binaryManager.getPlatformInfo();
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const
|
|
103
|
-
|
|
141
|
+
// Helper: show diagnostics for a failing binary (mirrors Python's agent init)
|
|
142
|
+
const showDiagnostics = async (binaryPath, verResult) => {
|
|
143
|
+
if (verResult.stderr) {
|
|
144
|
+
console.log(fmt.info(` stderr: ${verResult.stderr}`));
|
|
145
|
+
}
|
|
146
|
+
const diag = await binaryManager.collectBinaryDiagnostics(binaryPath);
|
|
147
|
+
if (diag) {
|
|
148
|
+
console.log(fmt.info("Diagnostics:"));
|
|
149
|
+
console.log(diag);
|
|
150
|
+
}
|
|
151
|
+
console.log(fmt.info("To fix: install gitleaks (https://github.com/gitleaks/gitleaks/releases) and ensure it is on PATH, then re-run 'rafter agent init'."));
|
|
152
|
+
console.log();
|
|
153
|
+
};
|
|
154
|
+
if (binaryManager.isGitleaksInstalled()) {
|
|
155
|
+
// Local binary exists — verify it actually works
|
|
156
|
+
const verResult = await binaryManager.verifyGitleaksVerbose();
|
|
157
|
+
if (verResult.ok) {
|
|
158
|
+
console.log(fmt.success(`Gitleaks already installed (${verResult.stdout})`));
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
console.log(fmt.warning("Gitleaks binary found locally but failed to execute."));
|
|
162
|
+
console.log(fmt.info(` Binary: ${binaryManager.getGitleaksPath()}`));
|
|
163
|
+
await showDiagnostics(binaryManager.getGitleaksPath(), verResult);
|
|
164
|
+
}
|
|
104
165
|
}
|
|
105
166
|
else {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
await binaryManager.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
167
|
+
// Not installed locally — check PATH (mirrors Python's shutil.which)
|
|
168
|
+
const pathBinary = binaryManager.findGitleaksOnPath();
|
|
169
|
+
if (pathBinary) {
|
|
170
|
+
const verResult = await binaryManager.verifyGitleaksVerbose(pathBinary);
|
|
171
|
+
if (verResult.ok) {
|
|
172
|
+
console.log(fmt.success(`Gitleaks available on PATH (${verResult.stdout})`));
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
console.log(fmt.warning("Gitleaks found on PATH but failed to execute."));
|
|
176
|
+
console.log(fmt.info(` Binary: ${pathBinary}`));
|
|
177
|
+
await showDiagnostics(pathBinary, verResult);
|
|
178
|
+
}
|
|
113
179
|
}
|
|
114
|
-
|
|
115
|
-
console.log(
|
|
116
|
-
console.log("
|
|
180
|
+
else if (!platformInfo.supported) {
|
|
181
|
+
console.log(fmt.info(`Gitleaks not available for ${platformInfo.platform}/${platformInfo.arch}`));
|
|
182
|
+
console.log(fmt.success("Using pattern-based scanning (21 patterns)"));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
// Not on PATH, not installed locally — download
|
|
117
186
|
console.log();
|
|
187
|
+
console.log(fmt.info("Downloading Gitleaks (enhanced secret detection)..."));
|
|
188
|
+
try {
|
|
189
|
+
await binaryManager.downloadGitleaks((msg) => {
|
|
190
|
+
console.log(` ${msg}`);
|
|
191
|
+
});
|
|
192
|
+
console.log();
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
console.log();
|
|
196
|
+
console.log(fmt.error(`Gitleaks setup failed — pattern-based scanning will be used instead.`));
|
|
197
|
+
console.log(fmt.warning(String(e)));
|
|
198
|
+
console.log();
|
|
199
|
+
console.log(fmt.info("To fix: install gitleaks manually (https://github.com/gitleaks/gitleaks/releases) and ensure it is on PATH, then re-run 'rafter agent init'."));
|
|
200
|
+
console.log();
|
|
201
|
+
}
|
|
118
202
|
}
|
|
119
203
|
}
|
|
120
204
|
}
|
|
121
205
|
// Install OpenClaw skill if applicable
|
|
122
206
|
if (hasOpenClaw && !opts.skipOpenclaw) {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
manager.set("agent.environments.openclaw.enabled", true);
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
console.log("⚠️ Failed to install Rafter Security skill");
|
|
132
|
-
}
|
|
207
|
+
const skillManager = new SkillManager();
|
|
208
|
+
const result = await skillManager.installRafterSkillVerbose();
|
|
209
|
+
if (result.ok) {
|
|
210
|
+
console.log(fmt.success("Installed Rafter Security skill to ~/.openclaw/skills/rafter-security.md"));
|
|
211
|
+
manager.set("agent.environments.openclaw.enabled", true);
|
|
133
212
|
}
|
|
134
|
-
|
|
135
|
-
console.error(
|
|
213
|
+
else {
|
|
214
|
+
console.log(fmt.error("Failed to install Rafter Security skill"));
|
|
215
|
+
console.log(fmt.warning(` Source: ${result.sourcePath}`));
|
|
216
|
+
console.log(fmt.warning(` Destination: ${result.destPath}`));
|
|
217
|
+
if (result.error) {
|
|
218
|
+
console.log(fmt.warning(` Error: ${result.error}`));
|
|
219
|
+
}
|
|
136
220
|
}
|
|
137
221
|
}
|
|
138
|
-
// Install Claude Code skills if applicable
|
|
222
|
+
// Install Claude Code skills + hooks if applicable
|
|
139
223
|
if (hasClaudeCode && !opts.skipClaudeCode) {
|
|
140
224
|
try {
|
|
141
225
|
await installClaudeCodeSkills();
|
|
226
|
+
installClaudeCodeHooks();
|
|
142
227
|
manager.set("agent.environments.claudeCode.enabled", true);
|
|
143
228
|
}
|
|
144
229
|
catch (e) {
|
|
145
|
-
console.error(`Failed to install Claude Code
|
|
230
|
+
console.error(fmt.error(`Failed to install Claude Code integration: ${e}`));
|
|
146
231
|
}
|
|
147
232
|
}
|
|
148
233
|
console.log();
|
|
149
|
-
console.log("
|
|
234
|
+
console.log(fmt.success("Agent security initialized!"));
|
|
150
235
|
console.log();
|
|
151
236
|
console.log("Next steps:");
|
|
152
237
|
if (hasOpenClaw && !opts.skipOpenclaw) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import os from "os";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { execSync } from "child_process";
|
|
5
6
|
import { fileURLToPath } from 'url';
|
|
@@ -81,7 +82,7 @@ async function installLocalHook() {
|
|
|
81
82
|
*/
|
|
82
83
|
async function installGlobalHook() {
|
|
83
84
|
// Create global hooks directory
|
|
84
|
-
const homeDir =
|
|
85
|
+
const homeDir = os.homedir();
|
|
85
86
|
if (!homeDir) {
|
|
86
87
|
console.error("❌ Error: Could not determine home directory");
|
|
87
88
|
process.exit(1);
|