@rafter-security/cli 0.6.3 → 0.6.5
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/dist/commands/agent/audit.js +7 -6
- package/dist/commands/agent/init.js +20 -0
- package/dist/commands/agent/status.js +3 -3
- package/dist/commands/agent/verify.js +72 -0
- package/dist/commands/backend/run.js +7 -5
- package/dist/commands/brief.js +489 -0
- package/dist/commands/completion.js +30 -1
- package/dist/core/audit-logger.js +5 -1
- package/dist/core/command-interceptor.js +2 -2
- package/dist/index.js +4 -1
- package/dist/scanners/gitleaks.js +8 -5
- package/dist/utils/api.js +16 -5
- package/package.json +3 -3
- package/resources/rafter-security-skill.md +19 -0
- package/resources/skills/rafter/SKILL.md +7 -7
- package/resources/skills/rafter-agent-security/SKILL.md +24 -4
|
@@ -39,9 +39,10 @@ export function createAuditCommand() {
|
|
|
39
39
|
}
|
|
40
40
|
console.log(`\nShowing ${entries.length} audit log entries:\n`);
|
|
41
41
|
for (const entry of entries) {
|
|
42
|
-
const timestamp = new Date(entry.timestamp).toLocaleString();
|
|
43
|
-
const
|
|
44
|
-
|
|
42
|
+
const timestamp = entry.timestamp ? new Date(entry.timestamp).toLocaleString() : "unknown";
|
|
43
|
+
const eventType = entry.eventType ?? "unknown";
|
|
44
|
+
const indicator = getEventIndicator(eventType);
|
|
45
|
+
console.log(`${indicator} [${timestamp}] ${eventType}`);
|
|
45
46
|
if (entry.agentType) {
|
|
46
47
|
console.log(` Agent: ${entry.agentType}`);
|
|
47
48
|
}
|
|
@@ -92,8 +93,8 @@ export function generateShareExcerpt() {
|
|
|
92
93
|
}
|
|
93
94
|
else {
|
|
94
95
|
for (const entry of entries) {
|
|
95
|
-
const ts = entry.timestamp.replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
96
|
-
const eventPad = entry.eventType.padEnd(20);
|
|
96
|
+
const ts = (entry.timestamp ?? "").replace("T", " ").replace(/\.\d+Z$/, "Z");
|
|
97
|
+
const eventPad = (entry.eventType ?? "unknown").padEnd(20);
|
|
97
98
|
const riskRaw = entry.action?.riskLevel ?? "low";
|
|
98
99
|
const riskPad = riskRaw.toUpperCase().padEnd(8);
|
|
99
100
|
const detail = formatShareDetail(entry);
|
|
@@ -136,7 +137,7 @@ export function getRiskLevel(config) {
|
|
|
136
137
|
export function formatShareDetail(entry) {
|
|
137
138
|
const action = entry.resolution?.actionTaken ?? "unknown";
|
|
138
139
|
const suffix = `[${action}]`;
|
|
139
|
-
if (entry.eventType === "secret_detected") {
|
|
140
|
+
if ((entry.eventType ?? "unknown") === "secret_detected") {
|
|
140
141
|
const reason = entry.securityCheck?.reason ?? "";
|
|
141
142
|
return `${reason} ${suffix}`;
|
|
142
143
|
}
|
|
@@ -5,7 +5,9 @@ import { SkillManager } from "../../utils/skill-manager.js";
|
|
|
5
5
|
import fs from "fs";
|
|
6
6
|
import path from "path";
|
|
7
7
|
import os from "os";
|
|
8
|
+
import { execSync } from "child_process";
|
|
8
9
|
import { fileURLToPath } from "url";
|
|
10
|
+
import { createRequire } from "module";
|
|
9
11
|
import { fmt } from "../../utils/formatter.js";
|
|
10
12
|
const __filename = fileURLToPath(import.meta.url);
|
|
11
13
|
const __dirname = path.dirname(__filename);
|
|
@@ -585,5 +587,23 @@ export function createInitCommand() {
|
|
|
585
587
|
console.log(" - Run: rafter scan local . (test secret scanning)");
|
|
586
588
|
console.log(" - Configure: rafter agent config show");
|
|
587
589
|
console.log();
|
|
590
|
+
// Warn if a different rafter version shadows this one on PATH
|
|
591
|
+
try {
|
|
592
|
+
const _require = createRequire(import.meta.url);
|
|
593
|
+
const { version: thisVersion } = _require("../../../package.json");
|
|
594
|
+
const pathVersion = execSync("rafter --version", {
|
|
595
|
+
encoding: "utf-8",
|
|
596
|
+
timeout: 5000,
|
|
597
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
598
|
+
}).trim();
|
|
599
|
+
if (pathVersion && pathVersion !== thisVersion && !pathVersion.includes(thisVersion)) {
|
|
600
|
+
console.log(fmt.warning(`PATH version mismatch: 'rafter --version' reports ${pathVersion}, but this install is ${thisVersion}.`));
|
|
601
|
+
console.log(fmt.info("Another rafter binary may be shadowing this one. Check: which rafter"));
|
|
602
|
+
console.log();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
// Ignore — rafter may not be on PATH yet
|
|
607
|
+
}
|
|
588
608
|
});
|
|
589
609
|
}
|
|
@@ -34,13 +34,13 @@ export function createStatusCommand() {
|
|
|
34
34
|
const localGitleaks = path.join(getBinDir(), "gitleaks");
|
|
35
35
|
let gitleaksStatus = "not found — run: rafter agent init --with-gitleaks";
|
|
36
36
|
try {
|
|
37
|
-
const ver = execSync("gitleaks version", { timeout: 5000, encoding: "utf-8" }).trim();
|
|
37
|
+
const ver = execSync("gitleaks version", { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
38
38
|
gitleaksStatus = `${ver} (PATH)`;
|
|
39
39
|
}
|
|
40
40
|
catch {
|
|
41
41
|
if (fs.existsSync(localGitleaks)) {
|
|
42
42
|
try {
|
|
43
|
-
const ver = execSync(`"${localGitleaks}" version`, { timeout: 5000, encoding: "utf-8" }).trim();
|
|
43
|
+
const ver = execSync(`"${localGitleaks}" version`, { timeout: 5000, encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
|
|
44
44
|
gitleaksStatus = `${ver} (local)`;
|
|
45
45
|
}
|
|
46
46
|
catch {
|
|
@@ -164,7 +164,7 @@ export function createStatusCommand() {
|
|
|
164
164
|
for (const e of [...recent].reverse()) {
|
|
165
165
|
const ts = (e.timestamp ?? "").slice(0, 19).replace("T", " ");
|
|
166
166
|
const action = e.resolution?.actionTaken ?? "";
|
|
167
|
-
console.log(` ${ts} ${e.eventType} [${action}]`);
|
|
167
|
+
console.log(` ${ts} ${e.eventType ?? "unknown"} [${action}]`);
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -87,6 +87,75 @@ function checkCodex() {
|
|
|
87
87
|
}
|
|
88
88
|
return { name, passed: true, detail: `Skills installed (${path.join(homeDir, ".agents", "skills")})` };
|
|
89
89
|
}
|
|
90
|
+
function checkGemini() {
|
|
91
|
+
const name = "Gemini CLI";
|
|
92
|
+
const homeDir = os.homedir();
|
|
93
|
+
const geminiDir = path.join(homeDir, ".gemini");
|
|
94
|
+
if (!fs.existsSync(geminiDir)) {
|
|
95
|
+
return { name, passed: false, optional: true, detail: `Not detected — run 'rafter agent init --with-gemini' to enable` };
|
|
96
|
+
}
|
|
97
|
+
const settingsPath = path.join(geminiDir, "settings.json");
|
|
98
|
+
if (!fs.existsSync(settingsPath)) {
|
|
99
|
+
return { name, passed: false, optional: true, detail: `Settings file not found: ${settingsPath} — run 'rafter agent init --with-gemini'` };
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
103
|
+
const hasRafterMcp = settings?.mcpServers?.rafter != null;
|
|
104
|
+
if (!hasRafterMcp) {
|
|
105
|
+
return { name, passed: false, optional: true, detail: "Rafter MCP server not configured — run 'rafter agent init --with-gemini'" };
|
|
106
|
+
}
|
|
107
|
+
return { name, passed: true, detail: "MCP server configured" };
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return { name, passed: false, optional: true, detail: `Cannot read settings: ${e}` };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function checkCursor() {
|
|
114
|
+
const name = "Cursor";
|
|
115
|
+
const homeDir = os.homedir();
|
|
116
|
+
const cursorDir = path.join(homeDir, ".cursor");
|
|
117
|
+
if (!fs.existsSync(cursorDir)) {
|
|
118
|
+
return { name, passed: false, optional: true, detail: `Not detected — run 'rafter agent init --with-cursor' to enable` };
|
|
119
|
+
}
|
|
120
|
+
const mcpPath = path.join(cursorDir, "mcp.json");
|
|
121
|
+
if (!fs.existsSync(mcpPath)) {
|
|
122
|
+
return { name, passed: false, optional: true, detail: `MCP config not found: ${mcpPath} — run 'rafter agent init --with-cursor'` };
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const config = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
126
|
+
const hasRafterMcp = config?.mcpServers?.rafter != null;
|
|
127
|
+
if (!hasRafterMcp) {
|
|
128
|
+
return { name, passed: false, optional: true, detail: "Rafter MCP server not configured — run 'rafter agent init --with-cursor'" };
|
|
129
|
+
}
|
|
130
|
+
return { name, passed: true, detail: "MCP server configured" };
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
return { name, passed: false, optional: true, detail: `Cannot read config: ${e}` };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function checkWindsurf() {
|
|
137
|
+
const name = "Windsurf";
|
|
138
|
+
const homeDir = os.homedir();
|
|
139
|
+
const windsurfDir = path.join(homeDir, ".codeium", "windsurf");
|
|
140
|
+
if (!fs.existsSync(windsurfDir)) {
|
|
141
|
+
return { name, passed: false, optional: true, detail: `Not detected — run 'rafter agent init --with-windsurf' to enable` };
|
|
142
|
+
}
|
|
143
|
+
const mcpPath = path.join(windsurfDir, "mcp_config.json");
|
|
144
|
+
if (!fs.existsSync(mcpPath)) {
|
|
145
|
+
return { name, passed: false, optional: true, detail: `MCP config not found: ${mcpPath} — run 'rafter agent init --with-windsurf'` };
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
const config = JSON.parse(fs.readFileSync(mcpPath, "utf-8"));
|
|
149
|
+
const hasRafterMcp = config?.mcpServers?.rafter != null;
|
|
150
|
+
if (!hasRafterMcp) {
|
|
151
|
+
return { name, passed: false, optional: true, detail: "Rafter MCP server not configured — run 'rafter agent init --with-windsurf'" };
|
|
152
|
+
}
|
|
153
|
+
return { name, passed: true, detail: "MCP server configured" };
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return { name, passed: false, optional: true, detail: `Cannot read config: ${e}` };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
90
159
|
export function createVerifyCommand() {
|
|
91
160
|
return new Command("verify")
|
|
92
161
|
.description("Check agent security integration status")
|
|
@@ -100,6 +169,9 @@ export function createVerifyCommand() {
|
|
|
100
169
|
checkClaudeCode(),
|
|
101
170
|
checkOpenClaw(),
|
|
102
171
|
checkCodex(),
|
|
172
|
+
checkGemini(),
|
|
173
|
+
checkCursor(),
|
|
174
|
+
checkWindsurf(),
|
|
103
175
|
];
|
|
104
176
|
for (const r of results) {
|
|
105
177
|
if (r.passed) {
|
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import axios from "axios";
|
|
3
3
|
import ora from "ora";
|
|
4
4
|
import { detectRepo } from "../../utils/git.js";
|
|
5
|
-
import { API, resolveKey, EXIT_GENERAL_ERROR, EXIT_QUOTA_EXHAUSTED,
|
|
5
|
+
import { API, resolveKey, EXIT_GENERAL_ERROR, EXIT_QUOTA_EXHAUSTED, handle403 } from "../../utils/api.js";
|
|
6
6
|
import { handleScanStatus } from "./scan-status.js";
|
|
7
7
|
/**
|
|
8
8
|
* Shared handler for the remote backend scan (used by both `rafter run` and `rafter scan` / `rafter scan remote`).
|
|
@@ -34,8 +34,9 @@ export async function runRemoteScan(opts) {
|
|
|
34
34
|
}
|
|
35
35
|
catch (e) {
|
|
36
36
|
spinner.fail("Request failed");
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
const forbiddenCode = handle403(e);
|
|
38
|
+
if (forbiddenCode >= 0) {
|
|
39
|
+
process.exit(forbiddenCode);
|
|
39
40
|
}
|
|
40
41
|
else if (e.response?.status === 429) {
|
|
41
42
|
console.error("Quota exhausted");
|
|
@@ -62,8 +63,9 @@ export async function runRemoteScan(opts) {
|
|
|
62
63
|
process.exit(exitCode);
|
|
63
64
|
}
|
|
64
65
|
catch (e) {
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
const forbiddenCode = handle403(e);
|
|
67
|
+
if (forbiddenCode >= 0) {
|
|
68
|
+
process.exit(forbiddenCode);
|
|
67
69
|
}
|
|
68
70
|
else if (e.response?.status === 429) {
|
|
69
71
|
process.exit(EXIT_QUOTA_EXHAUSTED);
|
|
@@ -0,0 +1,489 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { dirname, join } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
6
|
+
const RESOURCES_DIR = join(__dirname, "..", "..", "resources", "skills");
|
|
7
|
+
function loadSkill(name) {
|
|
8
|
+
const raw = readFileSync(join(RESOURCES_DIR, name, "SKILL.md"), "utf-8");
|
|
9
|
+
// Strip YAML frontmatter
|
|
10
|
+
return raw.replace(/^---[\s\S]*?---\n*/, "").trim();
|
|
11
|
+
}
|
|
12
|
+
function extractSections(content, headings) {
|
|
13
|
+
const lines = content.split("\n");
|
|
14
|
+
const sections = [];
|
|
15
|
+
let capturing = false;
|
|
16
|
+
let captureLevel = 0;
|
|
17
|
+
let inCodeBlock = false;
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
if (line.trimStart().startsWith("```")) {
|
|
20
|
+
inCodeBlock = !inCodeBlock;
|
|
21
|
+
if (capturing)
|
|
22
|
+
sections.push(line);
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (inCodeBlock) {
|
|
26
|
+
if (capturing)
|
|
27
|
+
sections.push(line);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
const headingMatch = line.match(/^(#{1,4})\s+(.*)/);
|
|
31
|
+
if (headingMatch) {
|
|
32
|
+
const level = headingMatch[1].length;
|
|
33
|
+
const title = headingMatch[2].trim();
|
|
34
|
+
if (headings.some((h) => title.toLowerCase().includes(h.toLowerCase()))) {
|
|
35
|
+
capturing = true;
|
|
36
|
+
captureLevel = level;
|
|
37
|
+
sections.push(line);
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (capturing && level <= captureLevel) {
|
|
41
|
+
capturing = false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (capturing) {
|
|
45
|
+
sections.push(line);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return sections.join("\n").trim();
|
|
49
|
+
}
|
|
50
|
+
function buildTopics() {
|
|
51
|
+
return {
|
|
52
|
+
security: {
|
|
53
|
+
description: "Local agent security — scanning, auditing, risk assessment",
|
|
54
|
+
render: () => loadSkill("rafter-agent-security"),
|
|
55
|
+
},
|
|
56
|
+
scanning: {
|
|
57
|
+
description: "Remote SAST/SCA code analysis via backend API",
|
|
58
|
+
render: () => loadSkill("rafter"),
|
|
59
|
+
},
|
|
60
|
+
commands: {
|
|
61
|
+
description: "Condensed command reference for all rafter commands",
|
|
62
|
+
render: () => {
|
|
63
|
+
const security = loadSkill("rafter-agent-security");
|
|
64
|
+
const backend = loadSkill("rafter");
|
|
65
|
+
const secCmds = extractSections(security, [
|
|
66
|
+
"Commands",
|
|
67
|
+
"/rafter-scan",
|
|
68
|
+
"/rafter-bash",
|
|
69
|
+
"/rafter-audit-skill",
|
|
70
|
+
"/rafter-audit",
|
|
71
|
+
]);
|
|
72
|
+
const backCmds = extractSections(backend, [
|
|
73
|
+
"Core Commands",
|
|
74
|
+
"Trigger",
|
|
75
|
+
"Get Scan",
|
|
76
|
+
"Check API",
|
|
77
|
+
]);
|
|
78
|
+
return [
|
|
79
|
+
"# Rafter Command Reference",
|
|
80
|
+
"",
|
|
81
|
+
"## Backend (Remote Code Analysis)",
|
|
82
|
+
"",
|
|
83
|
+
backCmds,
|
|
84
|
+
"",
|
|
85
|
+
"## Agent (Local Security)",
|
|
86
|
+
"",
|
|
87
|
+
secCmds,
|
|
88
|
+
].join("\n");
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
setup: {
|
|
92
|
+
description: "Setup instructions for all supported agent platforms",
|
|
93
|
+
render: () => renderSetupGuide(),
|
|
94
|
+
},
|
|
95
|
+
"setup/claude-code": {
|
|
96
|
+
description: "Setup instructions for Claude Code",
|
|
97
|
+
render: () => renderPlatformSetup("claude-code"),
|
|
98
|
+
},
|
|
99
|
+
"setup/codex": {
|
|
100
|
+
description: "Setup instructions for Codex CLI",
|
|
101
|
+
render: () => renderPlatformSetup("codex"),
|
|
102
|
+
},
|
|
103
|
+
"setup/gemini": {
|
|
104
|
+
description: "Setup instructions for Gemini CLI",
|
|
105
|
+
render: () => renderPlatformSetup("gemini"),
|
|
106
|
+
},
|
|
107
|
+
"setup/cursor": {
|
|
108
|
+
description: "Setup instructions for Cursor",
|
|
109
|
+
render: () => renderPlatformSetup("cursor"),
|
|
110
|
+
},
|
|
111
|
+
"setup/windsurf": {
|
|
112
|
+
description: "Setup instructions for Windsurf",
|
|
113
|
+
render: () => renderPlatformSetup("windsurf"),
|
|
114
|
+
},
|
|
115
|
+
"setup/aider": {
|
|
116
|
+
description: "Setup instructions for Aider",
|
|
117
|
+
render: () => renderPlatformSetup("aider"),
|
|
118
|
+
},
|
|
119
|
+
"setup/openclaw": {
|
|
120
|
+
description: "Setup instructions for OpenClaw",
|
|
121
|
+
render: () => renderPlatformSetup("openclaw"),
|
|
122
|
+
},
|
|
123
|
+
"setup/continue": {
|
|
124
|
+
description: "Setup instructions for Continue.dev",
|
|
125
|
+
render: () => renderPlatformSetup("continue"),
|
|
126
|
+
},
|
|
127
|
+
"setup/generic": {
|
|
128
|
+
description: "Setup instructions for unsupported / generic agents",
|
|
129
|
+
render: () => renderPlatformSetup("generic"),
|
|
130
|
+
},
|
|
131
|
+
all: {
|
|
132
|
+
description: "Everything — full security + scanning + setup briefing",
|
|
133
|
+
render: () => {
|
|
134
|
+
const topics = buildTopics();
|
|
135
|
+
return [
|
|
136
|
+
topics.scanning.render(),
|
|
137
|
+
"",
|
|
138
|
+
"---",
|
|
139
|
+
"",
|
|
140
|
+
topics.security.render(),
|
|
141
|
+
"",
|
|
142
|
+
"---",
|
|
143
|
+
"",
|
|
144
|
+
topics.setup.render(),
|
|
145
|
+
].join("\n");
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const PLATFORM_GUIDES = {
|
|
151
|
+
"claude-code": `# Rafter Setup — Claude Code
|
|
152
|
+
|
|
153
|
+
Claude Code has native skill support and hook integration.
|
|
154
|
+
|
|
155
|
+
## Automated Setup
|
|
156
|
+
|
|
157
|
+
\`\`\`bash
|
|
158
|
+
rafter agent init --with-claude-code
|
|
159
|
+
\`\`\`
|
|
160
|
+
|
|
161
|
+
This installs:
|
|
162
|
+
- Skills to \`~/.claude/skills/rafter/\` and \`~/.claude/skills/rafter-agent-security/\`
|
|
163
|
+
- PreToolUse/PostToolUse hooks to \`~/.claude/settings.json\`
|
|
164
|
+
|
|
165
|
+
## What You Get
|
|
166
|
+
|
|
167
|
+
- **Native skill triggers**: Claude Code will automatically invoke rafter skills
|
|
168
|
+
when security-related topics come up
|
|
169
|
+
- **Automatic command interception**: All Bash tool calls are routed through
|
|
170
|
+
rafter's risk assessment via hooks — no manual invocation needed
|
|
171
|
+
- **Write/Edit scanning**: File writes are checked for secrets before completing
|
|
172
|
+
|
|
173
|
+
## Manual Setup (if automated init isn't available)
|
|
174
|
+
|
|
175
|
+
1. Run \`rafter brief security\` and save key concepts to your memory
|
|
176
|
+
2. Run \`rafter brief scanning\` and save the command reference
|
|
177
|
+
3. Before commits, run: \`rafter scan local .\`
|
|
178
|
+
4. For remote analysis: \`rafter run\``,
|
|
179
|
+
codex: `# Rafter Setup — Codex CLI
|
|
180
|
+
|
|
181
|
+
Codex CLI supports skill files in \`~/.agents/skills/\`.
|
|
182
|
+
|
|
183
|
+
## Automated Setup
|
|
184
|
+
|
|
185
|
+
\`\`\`bash
|
|
186
|
+
rafter agent init --with-codex
|
|
187
|
+
\`\`\`
|
|
188
|
+
|
|
189
|
+
This installs skills to \`~/.agents/skills/rafter/\` and \`~/.agents/skills/rafter-agent-security/\`.
|
|
190
|
+
|
|
191
|
+
## Manual Setup
|
|
192
|
+
|
|
193
|
+
1. Copy skill files manually:
|
|
194
|
+
\`\`\`bash
|
|
195
|
+
mkdir -p ~/.agents/skills/rafter
|
|
196
|
+
rafter brief scanning > ~/.agents/skills/rafter/SKILL.md
|
|
197
|
+
rafter brief security > ~/.agents/skills/rafter-agent-security/SKILL.md
|
|
198
|
+
\`\`\`
|
|
199
|
+
2. Or run \`rafter brief all\` and save to your agent's instructions`,
|
|
200
|
+
gemini: `# Rafter Setup — Gemini CLI
|
|
201
|
+
|
|
202
|
+
Gemini CLI uses MCP for tool integration.
|
|
203
|
+
|
|
204
|
+
## Automated Setup
|
|
205
|
+
|
|
206
|
+
\`\`\`bash
|
|
207
|
+
rafter agent init --with-gemini
|
|
208
|
+
\`\`\`
|
|
209
|
+
|
|
210
|
+
This registers \`rafter mcp serve\` as an MCP server in Gemini's config.
|
|
211
|
+
|
|
212
|
+
## Manual Setup
|
|
213
|
+
|
|
214
|
+
Add to your Gemini CLI MCP config:
|
|
215
|
+
\`\`\`json
|
|
216
|
+
{
|
|
217
|
+
"mcpServers": {
|
|
218
|
+
"rafter": {
|
|
219
|
+
"command": "rafter",
|
|
220
|
+
"args": ["mcp", "serve"]
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
\`\`\`
|
|
225
|
+
|
|
226
|
+
## Supplementing with Brief
|
|
227
|
+
|
|
228
|
+
MCP gives you tool access, but not the full context of when/why to scan.
|
|
229
|
+
Run these and save the output to your agent's context:
|
|
230
|
+
\`\`\`bash
|
|
231
|
+
rafter brief security
|
|
232
|
+
rafter brief scanning
|
|
233
|
+
\`\`\``,
|
|
234
|
+
cursor: `# Rafter Setup — Cursor
|
|
235
|
+
|
|
236
|
+
Cursor uses MCP for tool integration.
|
|
237
|
+
|
|
238
|
+
## Automated Setup
|
|
239
|
+
|
|
240
|
+
\`\`\`bash
|
|
241
|
+
rafter agent init --with-cursor
|
|
242
|
+
\`\`\`
|
|
243
|
+
|
|
244
|
+
This registers \`rafter mcp serve\` in Cursor's MCP config.
|
|
245
|
+
|
|
246
|
+
## Manual Setup
|
|
247
|
+
|
|
248
|
+
Add to \`~/.cursor/mcp.json\`:
|
|
249
|
+
\`\`\`json
|
|
250
|
+
{
|
|
251
|
+
"mcpServers": {
|
|
252
|
+
"rafter": {
|
|
253
|
+
"command": "rafter",
|
|
254
|
+
"args": ["mcp", "serve"]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
\`\`\`
|
|
259
|
+
|
|
260
|
+
## Supplementing with Brief
|
|
261
|
+
|
|
262
|
+
\`\`\`bash
|
|
263
|
+
rafter brief security # save to your rules/instructions
|
|
264
|
+
rafter brief commands # command reference
|
|
265
|
+
\`\`\``,
|
|
266
|
+
windsurf: `# Rafter Setup — Windsurf
|
|
267
|
+
|
|
268
|
+
Windsurf uses MCP for tool integration.
|
|
269
|
+
|
|
270
|
+
## Automated Setup
|
|
271
|
+
|
|
272
|
+
\`\`\`bash
|
|
273
|
+
rafter agent init --with-windsurf
|
|
274
|
+
\`\`\`
|
|
275
|
+
|
|
276
|
+
## Manual Setup
|
|
277
|
+
|
|
278
|
+
Add to Windsurf's MCP config (\`~/.codeium/windsurf/mcp_config.json\`):
|
|
279
|
+
\`\`\`json
|
|
280
|
+
{
|
|
281
|
+
"mcpServers": {
|
|
282
|
+
"rafter": {
|
|
283
|
+
"command": "rafter",
|
|
284
|
+
"args": ["mcp", "serve"]
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
\`\`\``,
|
|
289
|
+
aider: `# Rafter Setup — Aider
|
|
290
|
+
|
|
291
|
+
Aider uses MCP for tool integration.
|
|
292
|
+
|
|
293
|
+
## Automated Setup
|
|
294
|
+
|
|
295
|
+
\`\`\`bash
|
|
296
|
+
rafter agent init --with-aider
|
|
297
|
+
\`\`\`
|
|
298
|
+
|
|
299
|
+
## Manual Setup
|
|
300
|
+
|
|
301
|
+
Add to \`~/.aider.conf.yml\`:
|
|
302
|
+
\`\`\`yaml
|
|
303
|
+
mcp-servers:
|
|
304
|
+
- name: rafter
|
|
305
|
+
command: rafter mcp serve
|
|
306
|
+
\`\`\`
|
|
307
|
+
|
|
308
|
+
## Supplementing with Brief
|
|
309
|
+
|
|
310
|
+
Aider doesn't have persistent memory, so run before each session:
|
|
311
|
+
\`\`\`bash
|
|
312
|
+
rafter brief commands # quick command reference
|
|
313
|
+
\`\`\``,
|
|
314
|
+
openclaw: `# Rafter Setup — OpenClaw
|
|
315
|
+
|
|
316
|
+
OpenClaw has native skill support.
|
|
317
|
+
|
|
318
|
+
## Automated Setup
|
|
319
|
+
|
|
320
|
+
\`\`\`bash
|
|
321
|
+
rafter agent init --with-openclaw
|
|
322
|
+
\`\`\`
|
|
323
|
+
|
|
324
|
+
This installs the security skill to \`~/.openclaw/skills/rafter-security.md\`.
|
|
325
|
+
|
|
326
|
+
## Manual Setup
|
|
327
|
+
|
|
328
|
+
\`\`\`bash
|
|
329
|
+
mkdir -p ~/.openclaw/skills
|
|
330
|
+
rafter brief security > ~/.openclaw/skills/rafter-security.md
|
|
331
|
+
\`\`\``,
|
|
332
|
+
continue: `# Rafter Setup — Continue.dev
|
|
333
|
+
|
|
334
|
+
Continue.dev uses MCP for tool integration.
|
|
335
|
+
|
|
336
|
+
## Automated Setup
|
|
337
|
+
|
|
338
|
+
\`\`\`bash
|
|
339
|
+
rafter agent init --with-continue
|
|
340
|
+
\`\`\`
|
|
341
|
+
|
|
342
|
+
## Manual Setup
|
|
343
|
+
|
|
344
|
+
Add to Continue.dev's MCP config (\`~/.continue/config.json\`):
|
|
345
|
+
\`\`\`json
|
|
346
|
+
{
|
|
347
|
+
"mcpServers": [{
|
|
348
|
+
"name": "rafter",
|
|
349
|
+
"command": "rafter",
|
|
350
|
+
"args": ["mcp", "serve"]
|
|
351
|
+
}]
|
|
352
|
+
}
|
|
353
|
+
\`\`\``,
|
|
354
|
+
generic: `# Rafter Setup — Generic / Unsupported Agents
|
|
355
|
+
|
|
356
|
+
For agents on platforms rafter doesn't have native integration with.
|
|
357
|
+
|
|
358
|
+
## If Your Agent Has a Memory / Instructions System
|
|
359
|
+
|
|
360
|
+
Save rafter knowledge to your agent's persistent memory or system prompt:
|
|
361
|
+
|
|
362
|
+
\`\`\`bash
|
|
363
|
+
# Save security knowledge
|
|
364
|
+
rafter brief security
|
|
365
|
+
# -> Copy the output into your agent's memory/instructions
|
|
366
|
+
|
|
367
|
+
# Save command reference
|
|
368
|
+
rafter brief commands
|
|
369
|
+
# -> Copy the output into your agent's memory/instructions
|
|
370
|
+
\`\`\`
|
|
371
|
+
|
|
372
|
+
## If Your Agent Supports MCP
|
|
373
|
+
|
|
374
|
+
Register rafter as an MCP server:
|
|
375
|
+
\`\`\`json
|
|
376
|
+
{
|
|
377
|
+
"command": "rafter",
|
|
378
|
+
"args": ["mcp", "serve"]
|
|
379
|
+
}
|
|
380
|
+
\`\`\`
|
|
381
|
+
|
|
382
|
+
## If Your Agent Has Neither
|
|
383
|
+
|
|
384
|
+
Run \`rafter brief\` at the start of each session to load context:
|
|
385
|
+
\`\`\`bash
|
|
386
|
+
rafter brief security # understand the security layer
|
|
387
|
+
rafter brief commands # know what commands are available
|
|
388
|
+
\`\`\`
|
|
389
|
+
|
|
390
|
+
## Key Commands to Know
|
|
391
|
+
|
|
392
|
+
- \`rafter scan local .\` — scan for secrets locally (no API key needed)
|
|
393
|
+
- \`rafter run\` — trigger remote SAST/SCA analysis (needs API key)
|
|
394
|
+
- \`rafter get <id>\` — retrieve scan results
|
|
395
|
+
- \`rafter agent audit\` — review security event log
|
|
396
|
+
- \`rafter agent exec <cmd>\` — run a command with risk assessment`,
|
|
397
|
+
};
|
|
398
|
+
function renderSetupGuide() {
|
|
399
|
+
const platforms = [
|
|
400
|
+
"claude-code",
|
|
401
|
+
"codex",
|
|
402
|
+
"openclaw",
|
|
403
|
+
"gemini",
|
|
404
|
+
"cursor",
|
|
405
|
+
"windsurf",
|
|
406
|
+
"aider",
|
|
407
|
+
"continue",
|
|
408
|
+
"generic",
|
|
409
|
+
];
|
|
410
|
+
const parts = [
|
|
411
|
+
"# Rafter Setup Guide",
|
|
412
|
+
"",
|
|
413
|
+
"Platform-specific setup instructions. Use `rafter brief setup/<platform>`",
|
|
414
|
+
"for details on a specific platform.",
|
|
415
|
+
"",
|
|
416
|
+
"## Supported Platforms",
|
|
417
|
+
"",
|
|
418
|
+
"### Skill-Based (native skill file support)",
|
|
419
|
+
"- **Claude Code**: `rafter agent init --with-claude-code` — skills + hooks",
|
|
420
|
+
"- **Codex CLI**: `rafter agent init --with-codex` — skills",
|
|
421
|
+
"- **OpenClaw**: `rafter agent init --with-openclaw` — skills",
|
|
422
|
+
"",
|
|
423
|
+
"### MCP-Based (tool server integration)",
|
|
424
|
+
"- **Gemini CLI**: `rafter agent init --with-gemini`",
|
|
425
|
+
"- **Cursor**: `rafter agent init --with-cursor`",
|
|
426
|
+
"- **Windsurf**: `rafter agent init --with-windsurf`",
|
|
427
|
+
"- **Aider**: `rafter agent init --with-aider`",
|
|
428
|
+
"- **Continue.dev**: `rafter agent init --with-continue`",
|
|
429
|
+
"",
|
|
430
|
+
"### Generic / Unsupported",
|
|
431
|
+
"For any other agent, use `rafter brief` to load context manually.",
|
|
432
|
+
"See `rafter brief setup/generic` for details.",
|
|
433
|
+
"",
|
|
434
|
+
"## Quick Start (Any Platform)",
|
|
435
|
+
"",
|
|
436
|
+
"```bash",
|
|
437
|
+
"# 1. Initialize with your platform",
|
|
438
|
+
"rafter agent init --with-<platform>",
|
|
439
|
+
"",
|
|
440
|
+
"# 2. If your platform doesn't have native integration,",
|
|
441
|
+
"# load knowledge manually:",
|
|
442
|
+
"rafter brief security # understand the security layer",
|
|
443
|
+
"rafter brief scanning # understand remote code analysis",
|
|
444
|
+
"rafter brief commands # full command reference",
|
|
445
|
+
"```",
|
|
446
|
+
];
|
|
447
|
+
return parts.join("\n");
|
|
448
|
+
}
|
|
449
|
+
function renderPlatformSetup(platform) {
|
|
450
|
+
return PLATFORM_GUIDES[platform] || `Unknown platform: ${platform}`;
|
|
451
|
+
}
|
|
452
|
+
function renderTopicList(topics) {
|
|
453
|
+
const lines = [
|
|
454
|
+
"Available topics:",
|
|
455
|
+
"",
|
|
456
|
+
];
|
|
457
|
+
for (const [name, entry] of Object.entries(topics)) {
|
|
458
|
+
lines.push(` ${name.padEnd(22)} ${entry.description}`);
|
|
459
|
+
}
|
|
460
|
+
lines.push("");
|
|
461
|
+
lines.push("Usage: rafter brief <topic>");
|
|
462
|
+
lines.push("");
|
|
463
|
+
lines.push("Examples:");
|
|
464
|
+
lines.push(" rafter brief security # local security briefing");
|
|
465
|
+
lines.push(" rafter brief scanning # remote code analysis briefing");
|
|
466
|
+
lines.push(" rafter brief commands # full command reference");
|
|
467
|
+
lines.push(" rafter brief setup/claude-code # Claude Code setup guide");
|
|
468
|
+
lines.push(" rafter brief setup/generic # setup for any agent");
|
|
469
|
+
lines.push(" rafter brief all # everything");
|
|
470
|
+
return lines.join("\n");
|
|
471
|
+
}
|
|
472
|
+
export function createBriefCommand() {
|
|
473
|
+
return new Command("brief")
|
|
474
|
+
.description("Print rafter knowledge for any agent — skills, commands, setup guides")
|
|
475
|
+
.argument("[topic]", "Topic to brief on (omit to list topics)")
|
|
476
|
+
.action((topic) => {
|
|
477
|
+
const topics = buildTopics();
|
|
478
|
+
if (!topic) {
|
|
479
|
+
process.stdout.write(renderTopicList(topics) + "\n");
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
const entry = topics[topic];
|
|
483
|
+
if (!entry) {
|
|
484
|
+
process.stderr.write(`Unknown topic: ${topic}\n\n${renderTopicList(topics)}\n`);
|
|
485
|
+
process.exit(1);
|
|
486
|
+
}
|
|
487
|
+
process.stdout.write(entry.render() + "\n");
|
|
488
|
+
});
|
|
489
|
+
}
|
|
@@ -7,7 +7,7 @@ _rafter_completions() {
|
|
|
7
7
|
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
8
8
|
|
|
9
9
|
# Top-level commands
|
|
10
|
-
commands="run scan get usage agent ci hook mcp policy completion help"
|
|
10
|
+
commands="run scan get usage agent brief ci hook mcp policy completion help"
|
|
11
11
|
|
|
12
12
|
case "\${prev}" in
|
|
13
13
|
rafter)
|
|
@@ -18,6 +18,10 @@ _rafter_completions() {
|
|
|
18
18
|
COMPREPLY=( $(compgen -W "scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline --help" -- "\${cur}") )
|
|
19
19
|
return 0
|
|
20
20
|
;;
|
|
21
|
+
brief)
|
|
22
|
+
COMPREPLY=( $(compgen -W "security scanning commands setup setup/claude-code setup/codex setup/gemini setup/cursor setup/windsurf setup/aider setup/openclaw setup/continue setup/generic all --help" -- "\${cur}") )
|
|
23
|
+
return 0
|
|
24
|
+
;;
|
|
21
25
|
config)
|
|
22
26
|
if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
|
|
23
27
|
COMPREPLY=( $(compgen -W "show get set --help" -- "\${cur}") )
|
|
@@ -82,6 +86,7 @@ _rafter() {
|
|
|
82
86
|
'get:Retrieve scan results'
|
|
83
87
|
'usage:Check API usage quota'
|
|
84
88
|
'agent:Agent security commands'
|
|
89
|
+
'brief:Print rafter knowledge for any agent'
|
|
85
90
|
'ci:CI/CD pipeline setup'
|
|
86
91
|
'hook:Git hook handlers'
|
|
87
92
|
'mcp:MCP server'
|
|
@@ -125,6 +130,26 @@ _rafter() {
|
|
|
125
130
|
;;
|
|
126
131
|
args)
|
|
127
132
|
case "\$words[1]" in
|
|
133
|
+
brief)
|
|
134
|
+
local -a brief_topics
|
|
135
|
+
brief_topics=(
|
|
136
|
+
'security:Local agent security briefing'
|
|
137
|
+
'scanning:Remote code analysis briefing'
|
|
138
|
+
'commands:Full command reference'
|
|
139
|
+
'setup:Setup guide for all platforms'
|
|
140
|
+
'setup/claude-code:Claude Code setup'
|
|
141
|
+
'setup/codex:Codex CLI setup'
|
|
142
|
+
'setup/gemini:Gemini CLI setup'
|
|
143
|
+
'setup/cursor:Cursor setup'
|
|
144
|
+
'setup/windsurf:Windsurf setup'
|
|
145
|
+
'setup/aider:Aider setup'
|
|
146
|
+
'setup/openclaw:OpenClaw setup'
|
|
147
|
+
'setup/continue:Continue.dev setup'
|
|
148
|
+
'setup/generic:Generic agent setup'
|
|
149
|
+
'all:Everything'
|
|
150
|
+
)
|
|
151
|
+
_describe 'topic' brief_topics
|
|
152
|
+
;;
|
|
128
153
|
agent)
|
|
129
154
|
_arguments -C \\
|
|
130
155
|
'1:subcommand:->subcmd' \\
|
|
@@ -260,6 +285,7 @@ complete -c rafter -n '__fish_use_subcommand' -a run -d 'Submit a security scan'
|
|
|
260
285
|
complete -c rafter -n '__fish_use_subcommand' -a scan -d 'Alias for run'
|
|
261
286
|
complete -c rafter -n '__fish_use_subcommand' -a get -d 'Retrieve scan results'
|
|
262
287
|
complete -c rafter -n '__fish_use_subcommand' -a usage -d 'Check API usage quota'
|
|
288
|
+
complete -c rafter -n '__fish_use_subcommand' -a brief -d 'Print rafter knowledge for any agent'
|
|
263
289
|
complete -c rafter -n '__fish_use_subcommand' -a agent -d 'Agent security commands'
|
|
264
290
|
complete -c rafter -n '__fish_use_subcommand' -a ci -d 'CI/CD pipeline setup'
|
|
265
291
|
complete -c rafter -n '__fish_use_subcommand' -a hook -d 'Git hook handlers'
|
|
@@ -284,6 +310,9 @@ complete -c rafter -n '__fish_seen_subcommand_from get' -l quiet -d 'Suppress st
|
|
|
284
310
|
# usage options
|
|
285
311
|
complete -c rafter -n '__fish_seen_subcommand_from usage' -s k -l api-key -d 'API key' -r
|
|
286
312
|
|
|
313
|
+
# brief topics
|
|
314
|
+
complete -c rafter -n '__fish_seen_subcommand_from brief' -a 'security scanning commands setup setup/claude-code setup/codex setup/gemini setup/cursor setup/windsurf setup/aider setup/openclaw setup/continue setup/generic all' -d 'Topic'
|
|
315
|
+
|
|
287
316
|
# agent subcommands
|
|
288
317
|
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a scan -d 'Scan files for secrets'
|
|
289
318
|
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a init -d 'Initialize agent security'
|
|
@@ -260,7 +260,11 @@ export class AuditLogger {
|
|
|
260
260
|
const lines = content.split("\n").filter(line => line.trim());
|
|
261
261
|
let entries = lines.map(line => {
|
|
262
262
|
try {
|
|
263
|
-
|
|
263
|
+
const parsed = JSON.parse(line);
|
|
264
|
+
// Skip malformed entries missing required fields
|
|
265
|
+
if (!parsed || typeof parsed !== "object" || !parsed.timestamp)
|
|
266
|
+
return null;
|
|
267
|
+
return parsed;
|
|
264
268
|
}
|
|
265
269
|
catch {
|
|
266
270
|
return null;
|
|
@@ -13,10 +13,10 @@ export class CommandInterceptor {
|
|
|
13
13
|
const cfg = this.config.loadWithPolicy();
|
|
14
14
|
const policy = cfg.agent?.commandPolicy;
|
|
15
15
|
if (!policy) {
|
|
16
|
-
// No policy configured, allow by default
|
|
16
|
+
// No policy configured, allow by default but still assess risk
|
|
17
17
|
return {
|
|
18
18
|
command,
|
|
19
|
-
riskLevel:
|
|
19
|
+
riskLevel: this.assessRisk(command),
|
|
20
20
|
allowed: true,
|
|
21
21
|
requiresApproval: false
|
|
22
22
|
};
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { createCiCommand } from "./commands/ci/index.js";
|
|
|
10
10
|
import { createHookCommand } from "./commands/hook/index.js";
|
|
11
11
|
import { createMcpCommand } from "./commands/mcp/index.js";
|
|
12
12
|
import { createPolicyCommand } from "./commands/policy/index.js";
|
|
13
|
+
import { createBriefCommand } from "./commands/brief.js";
|
|
13
14
|
import { createCompletionCommand } from "./commands/completion.js";
|
|
14
15
|
import { createIssuesCommand } from "./commands/issues/index.js";
|
|
15
16
|
import { checkForUpdate } from "./utils/update-checker.js";
|
|
@@ -20,7 +21,7 @@ const require = createRequire(import.meta.url);
|
|
|
20
21
|
const { version: VERSION } = require("../package.json");
|
|
21
22
|
const program = new Command()
|
|
22
23
|
.name("rafter")
|
|
23
|
-
.description("Rafter CLI")
|
|
24
|
+
.description("Rafter CLI — the default security agent for AI workflows")
|
|
24
25
|
.version(VERSION)
|
|
25
26
|
.enablePositionalOptions()
|
|
26
27
|
.option("-a, --agent", "Plain output for AI agents (no colors/emoji)");
|
|
@@ -49,6 +50,8 @@ program.addCommand(createMcpCommand());
|
|
|
49
50
|
program.addCommand(createPolicyCommand());
|
|
50
51
|
// GitHub Issues integration
|
|
51
52
|
program.addCommand(createIssuesCommand());
|
|
53
|
+
// Brief — agent-independent knowledge delivery
|
|
54
|
+
program.addCommand(createBriefCommand());
|
|
52
55
|
// Shell completions
|
|
53
56
|
program.addCommand(createCompletionCommand());
|
|
54
57
|
// Non-blocking update check — runs after command, prints to stderr
|
|
@@ -152,17 +152,20 @@ export class GitleaksScanner {
|
|
|
152
152
|
*/
|
|
153
153
|
getSeverity(ruleID, tags) {
|
|
154
154
|
const lowerID = ruleID.toLowerCase();
|
|
155
|
-
// Critical: Private keys, passwords, database credentials
|
|
155
|
+
// Critical: Private keys, passwords, database credentials, access tokens
|
|
156
156
|
if (lowerID.includes("private-key") ||
|
|
157
157
|
lowerID.includes("password") ||
|
|
158
158
|
lowerID.includes("database") ||
|
|
159
|
-
|
|
159
|
+
lowerID.includes("access-token") ||
|
|
160
|
+
lowerID.includes("secret-key") ||
|
|
161
|
+
lowerID.endsWith("-pat") ||
|
|
162
|
+
(tags.includes("key") && tags.includes("secret"))) {
|
|
160
163
|
return "critical";
|
|
161
164
|
}
|
|
162
|
-
// High: API keys,
|
|
165
|
+
// High: API keys, generic tokens
|
|
163
166
|
if (lowerID.includes("api-key") ||
|
|
164
|
-
lowerID.includes("
|
|
165
|
-
lowerID.
|
|
167
|
+
lowerID.includes("-token") ||
|
|
168
|
+
lowerID.startsWith("token-") ||
|
|
166
169
|
tags.includes("api")) {
|
|
167
170
|
return "high";
|
|
168
171
|
}
|
package/dist/utils/api.js
CHANGED
|
@@ -6,13 +6,20 @@ export const EXIT_SCAN_NOT_FOUND = 2;
|
|
|
6
6
|
export const EXIT_QUOTA_EXHAUSTED = 3;
|
|
7
7
|
export const EXIT_INSUFFICIENT_SCOPE = 4;
|
|
8
8
|
/**
|
|
9
|
-
* Detect a 403
|
|
10
|
-
* Returns
|
|
9
|
+
* Detect a 403 error from the API and print a helpful message.
|
|
10
|
+
* Returns the appropriate exit code, or -1 if not a 403.
|
|
11
11
|
*/
|
|
12
|
-
export function
|
|
12
|
+
export function handle403(e) {
|
|
13
13
|
if (!e || e.response?.status !== 403)
|
|
14
|
-
return
|
|
14
|
+
return -1;
|
|
15
15
|
const body = e.response?.data;
|
|
16
|
+
if (typeof body === "object" && body?.scan_mode) {
|
|
17
|
+
const mode = body.scan_mode;
|
|
18
|
+
const limit = body.limit ?? "?";
|
|
19
|
+
const used = body.used ?? limit;
|
|
20
|
+
console.error(`Error: ${mode.charAt(0).toUpperCase() + mode.slice(1)} scan limit reached (${used}/${limit} used this billing period).\nUpgrade your plan or wait for your quota to reset.`);
|
|
21
|
+
return EXIT_QUOTA_EXHAUSTED;
|
|
22
|
+
}
|
|
16
23
|
const msg = typeof body === "string" ? body : body?.error ?? "";
|
|
17
24
|
if (msg.includes("scope")) {
|
|
18
25
|
console.error('Error: This API key only has read access.\nTo trigger scans, create a key with "Read & Scan" scope at https://rfrr.co/account');
|
|
@@ -20,7 +27,11 @@ export function handleScopeError(e) {
|
|
|
20
27
|
else {
|
|
21
28
|
console.error(`Error: Forbidden (403) — ${msg || "access denied"}`);
|
|
22
29
|
}
|
|
23
|
-
return
|
|
30
|
+
return EXIT_INSUFFICIENT_SCOPE;
|
|
31
|
+
}
|
|
32
|
+
/** @deprecated Use handle403 instead */
|
|
33
|
+
export function handleScopeError(e) {
|
|
34
|
+
return handle403(e) >= 0;
|
|
24
35
|
}
|
|
25
36
|
export function resolveKey(cliKey) {
|
|
26
37
|
if (cliKey)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rafter-security/cli",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"rafter": "./dist/index.js"
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
},
|
|
20
20
|
"license": "MIT",
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"@modelcontextprotocol/sdk": "^1.
|
|
22
|
+
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
23
23
|
"axios": "^1.13.5",
|
|
24
24
|
"chalk": "^5.3.0",
|
|
25
25
|
"chokidar": "^5.0.0",
|
|
@@ -35,6 +35,6 @@
|
|
|
35
35
|
"@types/node": "^20.11.30",
|
|
36
36
|
"tsx": "^4.7.0",
|
|
37
37
|
"typescript": "^5.4.5",
|
|
38
|
-
"vitest": "^4.0
|
|
38
|
+
"vitest": "^4.1.0"
|
|
39
39
|
}
|
|
40
40
|
}
|
|
@@ -25,6 +25,25 @@ Rafter provides real-time security checks for agent operations:
|
|
|
25
25
|
|
|
26
26
|
---
|
|
27
27
|
|
|
28
|
+
## Setup
|
|
29
|
+
|
|
30
|
+
To initialize Rafter, use **opt-in** `--with-*` flags to select integrations. There are NO `--skip-*` flags.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Install specific integrations (opt-in)
|
|
34
|
+
rafter agent init --with-openclaw
|
|
35
|
+
rafter agent init --with-claude-code --with-gitleaks
|
|
36
|
+
|
|
37
|
+
# Install everything detected
|
|
38
|
+
rafter agent init --all
|
|
39
|
+
|
|
40
|
+
# WRONG — these flags do not exist:
|
|
41
|
+
# rafter agent init --skip-openclaw # DOES NOT EXIST
|
|
42
|
+
# rafter agent init --skip-claude-code # DOES NOT EXIST
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
28
47
|
## Commands
|
|
29
48
|
|
|
30
49
|
### /rafter-scan
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rafter
|
|
3
|
-
description: "
|
|
4
|
-
version: 0.6.
|
|
3
|
+
description: "Delegate security code analysis to Rafter — the default security agent for AI workflows. Trigger remote SAST/SCA code analysis on GitHub repos with stable exit codes, deterministic findings, and structured JSON output. Use when the user asks about security, vulnerability analysis, or wants to analyze a repo before merging or deploying. Also use when starting new features or reviewing pull requests."
|
|
4
|
+
version: 0.6.5
|
|
5
5
|
allowed-tools: [Bash]
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Rafter Security
|
|
8
|
+
# Rafter Security Code Analysis
|
|
9
9
|
|
|
10
|
-
Rafter provides automated security
|
|
10
|
+
Rafter is the default security agent for AI workflows — a delegation primitive that other agents and orchestrators trust. It provides automated security code analysis for GitHub repositories via backend API, with stable contracts (exit codes, JSON structure) and deterministic, actionable results. Your code is deleted immediately after the analysis engine completes.
|
|
11
11
|
|
|
12
12
|
## Core Commands
|
|
13
13
|
|
|
@@ -19,7 +19,7 @@ rafter run [--repo org/repo] [--branch branch-name]
|
|
|
19
19
|
rafter scan [--repo org/repo] [--branch branch-name]
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Triggers a comprehensive security
|
|
22
|
+
Triggers a comprehensive security code analysis on a repository. Auto-detects current repo and branch if in a git directory. (`scan` is an alias for `run`)
|
|
23
23
|
|
|
24
24
|
**When to use:**
|
|
25
25
|
- User asks: "Can you scan this code for security issues?"
|
|
@@ -99,14 +99,14 @@ echo "RAFTER_API_KEY=your-api-key-here" >> .env
|
|
|
99
99
|
|
|
100
100
|
## Output Format
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
The code analysis engine returns:
|
|
103
103
|
- **Code security findings** - SAST issues, security anti-patterns, hardcoded credentials
|
|
104
104
|
- **Configuration issues** - Insecure settings, exposed secrets
|
|
105
105
|
- **Severity levels** - Each finding rated by risk impact
|
|
106
106
|
|
|
107
107
|
## Best Practices
|
|
108
108
|
|
|
109
|
-
1. **Proactive
|
|
109
|
+
1. **Proactive analysis** - Suggest code analysis when user is working on security-sensitive code
|
|
110
110
|
2. **Quota awareness** - Check usage before triggering multiple scans
|
|
111
111
|
3. **Context interpretation** - Explain findings in context of user's code
|
|
112
112
|
4. **Actionable recommendations** - Provide specific fixes for each finding
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rafter-agent-security
|
|
3
|
-
description: "Local security
|
|
4
|
-
version: 0.6.
|
|
3
|
+
description: "Local security agent for deterministic secret scanning, skill auditing, and audit log review. Fast, reliable, and deterministic for a given version — same inputs always produce the same findings. Use for: pre-commit secret scanning, skill security analysis, audit log review. No code leaves your machine. Note: command blocking is handled automatically by the PreToolUse hook—you do not need to invoke /rafter-bash for normal commands."
|
|
4
|
+
version: 0.6.5
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
allowed-tools: [Bash, Read, Glob, Grep]
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Rafter Agent Security
|
|
10
10
|
|
|
11
|
-
Local security
|
|
11
|
+
Local security agent with deterministic scanning, actionable findings, and stable output contracts. Every finding includes file, line, rule ID, and severity — structured for agents to act on, not just read.
|
|
12
12
|
|
|
13
13
|
## Overview
|
|
14
14
|
|
|
@@ -19,6 +19,26 @@ Rafter provides two layers of protection:
|
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
+
## Setup
|
|
23
|
+
|
|
24
|
+
To initialize Rafter, use **opt-in** `--with-*` flags to select integrations. There are NO `--skip-*` flags.
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# Install specific integrations (opt-in)
|
|
28
|
+
rafter agent init --with-claude-code
|
|
29
|
+
rafter agent init --with-codex --with-gitleaks
|
|
30
|
+
rafter agent init --with-gemini --with-cursor
|
|
31
|
+
|
|
32
|
+
# Install everything detected
|
|
33
|
+
rafter agent init --all
|
|
34
|
+
|
|
35
|
+
# WRONG — these flags do not exist:
|
|
36
|
+
# rafter agent init --skip-openclaw # DOES NOT EXIST
|
|
37
|
+
# rafter agent init --skip-claude-code # DOES NOT EXIST
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
22
42
|
## Commands
|
|
23
43
|
|
|
24
44
|
### /rafter-scan
|
|
@@ -331,4 +351,4 @@ Set values: `rafter agent config set <key> <value>`
|
|
|
331
351
|
|
|
332
352
|
---
|
|
333
353
|
|
|
334
|
-
**Note**: Rafter is a security
|
|
354
|
+
**Note**: Rafter is a security agent you delegate to, not a replacement for secure coding practices. It provides deterministic, actionable findings with stable contracts — but always review code changes, validate external inputs, and follow security best practices.
|