vibesafu 0.1.24 → 0.1.25
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/index.js +32 -14
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -84,7 +84,7 @@ async function uninstall() {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// src/cli/config.ts
|
|
87
|
-
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
|
|
87
|
+
import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2, chmod } from "fs/promises";
|
|
88
88
|
import { homedir as homedir2 } from "os";
|
|
89
89
|
import { join as join2 } from "path";
|
|
90
90
|
import { createInterface } from "readline";
|
|
@@ -109,10 +109,20 @@ var DEFAULT_CONFIG = {
|
|
|
109
109
|
path: join2(CONFIG_DIR, "logs")
|
|
110
110
|
}
|
|
111
111
|
};
|
|
112
|
+
function mergeConfig(defaults, user) {
|
|
113
|
+
return {
|
|
114
|
+
anthropic: { ...defaults.anthropic, ...user.anthropic },
|
|
115
|
+
models: { ...defaults.models, ...user.models },
|
|
116
|
+
trustedDomains: user.trustedDomains ?? defaults.trustedDomains,
|
|
117
|
+
customPatterns: { ...defaults.customPatterns, ...user.customPatterns },
|
|
118
|
+
allowedMCPTools: user.allowedMCPTools ?? defaults.allowedMCPTools,
|
|
119
|
+
logging: { ...defaults.logging, ...user.logging }
|
|
120
|
+
};
|
|
121
|
+
}
|
|
112
122
|
async function readConfig() {
|
|
113
123
|
try {
|
|
114
124
|
const content = await readFile2(CONFIG_PATH, "utf-8");
|
|
115
|
-
return
|
|
125
|
+
return mergeConfig(DEFAULT_CONFIG, JSON.parse(content));
|
|
116
126
|
} catch {
|
|
117
127
|
return DEFAULT_CONFIG;
|
|
118
128
|
}
|
|
@@ -120,6 +130,7 @@ async function readConfig() {
|
|
|
120
130
|
async function writeConfig(config2) {
|
|
121
131
|
await mkdir2(CONFIG_DIR, { recursive: true });
|
|
122
132
|
await writeFile2(CONFIG_PATH, JSON.stringify(config2, null, 2));
|
|
133
|
+
await chmod(CONFIG_PATH, 384);
|
|
123
134
|
}
|
|
124
135
|
function prompt(question) {
|
|
125
136
|
const rl = createInterface({
|
|
@@ -811,6 +822,7 @@ var CHECKPOINT_PATTERNS = [
|
|
|
811
822
|
{ pattern: /\.ssh/i, type: "file_sensitive", description: "SSH directory access" },
|
|
812
823
|
{ pattern: /\.aws/i, type: "file_sensitive", description: "AWS credentials access" },
|
|
813
824
|
{ pattern: /credentials/i, type: "file_sensitive", description: "Credentials file access" },
|
|
825
|
+
{ pattern: /CLAUDE\.md/i, type: "file_sensitive", description: "CLAUDE.md modification" },
|
|
814
826
|
// Sensitive file copy/move (indirect path bypass)
|
|
815
827
|
{ pattern: /(cp|mv)\s+.*\.ssh\//i, type: "file_sensitive", description: "Copying/moving SSH files" },
|
|
816
828
|
{ pattern: /(cp|mv)\s+.*\.aws\//i, type: "file_sensitive", description: "Copying/moving AWS credentials" },
|
|
@@ -1280,6 +1292,13 @@ var WRITE_SENSITIVE_PATHS = [
|
|
|
1280
1292
|
legitimateUses: ["Configuring PyPI", "Publishing packages"]
|
|
1281
1293
|
},
|
|
1282
1294
|
// Claude Code config - Critical (could disable security)
|
|
1295
|
+
{
|
|
1296
|
+
pattern: /CLAUDE\.md$/i,
|
|
1297
|
+
description: "Claude instructions file",
|
|
1298
|
+
severity: "critical",
|
|
1299
|
+
risk: "Can modify AI behavior and disable security rules",
|
|
1300
|
+
legitimateUses: ["Updating project instructions", "Configuring Claude behavior"]
|
|
1301
|
+
},
|
|
1283
1302
|
{
|
|
1284
1303
|
pattern: /^~?\/?\.claude\//i,
|
|
1285
1304
|
description: "Claude config directory",
|
|
@@ -1535,7 +1554,7 @@ function sanitizeForPrompt(command) {
|
|
|
1535
1554
|
if (sanitized.length > MAX_COMMAND_LENGTH) {
|
|
1536
1555
|
sanitized = sanitized.slice(0, MAX_COMMAND_LENGTH) + "... [truncated]";
|
|
1537
1556
|
}
|
|
1538
|
-
sanitized = sanitized.replace(
|
|
1557
|
+
sanitized = sanitized.replace(/]]>/g, "]]>");
|
|
1539
1558
|
sanitized = sanitized.replace(/\n{3,}/g, "\n\n");
|
|
1540
1559
|
return sanitized;
|
|
1541
1560
|
}
|
|
@@ -1582,7 +1601,7 @@ function shouldForceEscalate(command) {
|
|
|
1582
1601
|
}
|
|
1583
1602
|
|
|
1584
1603
|
// src/guard/haiku-triage.ts
|
|
1585
|
-
var
|
|
1604
|
+
var DEFAULT_HAIKU_MODEL = "claude-haiku-4-20250514";
|
|
1586
1605
|
var API_TIMEOUT_MS = 3e4;
|
|
1587
1606
|
var TRIAGE_SYSTEM_PROMPT = `You are a security triage agent for an autonomous coding system.
|
|
1588
1607
|
Your ONLY job is to classify commands as SELF_HANDLE, ESCALATE, or BLOCK.
|
|
@@ -1629,7 +1648,7 @@ var FORCE_ESCALATE_TYPES = [
|
|
|
1629
1648
|
"package_install"
|
|
1630
1649
|
// Supply chain attacks via postinstall scripts
|
|
1631
1650
|
];
|
|
1632
|
-
async function triageWithHaiku(client, checkpoint) {
|
|
1651
|
+
async function triageWithHaiku(client, checkpoint, model) {
|
|
1633
1652
|
if (FORCE_ESCALATE_TYPES.includes(checkpoint.type)) {
|
|
1634
1653
|
return {
|
|
1635
1654
|
classification: "ESCALATE",
|
|
@@ -1644,7 +1663,7 @@ async function triageWithHaiku(client, checkpoint) {
|
|
|
1644
1663
|
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
1645
1664
|
const response = await client.messages.create(
|
|
1646
1665
|
{
|
|
1647
|
-
model:
|
|
1666
|
+
model: model ?? DEFAULT_HAIKU_MODEL,
|
|
1648
1667
|
max_tokens: 500,
|
|
1649
1668
|
system: TRIAGE_SYSTEM_PROMPT,
|
|
1650
1669
|
messages: [{ role: "user", content: userPrompt }]
|
|
@@ -1706,7 +1725,7 @@ async function triageWithHaiku(client, checkpoint) {
|
|
|
1706
1725
|
}
|
|
1707
1726
|
|
|
1708
1727
|
// src/guard/sonnet-review.ts
|
|
1709
|
-
var
|
|
1728
|
+
var DEFAULT_SONNET_MODEL = "claude-sonnet-4-20250514";
|
|
1710
1729
|
var API_TIMEOUT_MS2 = 6e4;
|
|
1711
1730
|
var REVIEW_SYSTEM_PROMPT = `You are a senior security engineer reviewing potentially risky operations.
|
|
1712
1731
|
Your job is to analyze commands and determine if they are safe to execute.
|
|
@@ -1778,7 +1797,7 @@ BLOCK - Do not allow:
|
|
|
1778
1797
|
"user_message": "Concise message explaining the security risk to the user (2-3 sentences max). Do NOT include timing or instructions - those are added automatically."
|
|
1779
1798
|
}
|
|
1780
1799
|
</response_format>`;
|
|
1781
|
-
async function reviewWithSonnet(client, checkpoint, triage) {
|
|
1800
|
+
async function reviewWithSonnet(client, checkpoint, triage, model) {
|
|
1782
1801
|
const sanitizedCommand = sanitizeForPrompt(checkpoint.command);
|
|
1783
1802
|
const userPrompt = REVIEW_USER_PROMPT.replace("{command}", escapeXml(sanitizedCommand)).replace("{checkpoint_type}", escapeXml(checkpoint.type)).replace("{context}", escapeXml(checkpoint.description)).replace("{triage_reason}", escapeXml(triage.reason)).replace("{risk_indicators}", escapeXml(triage.riskIndicators.join(", ") || "none"));
|
|
1784
1803
|
try {
|
|
@@ -1786,7 +1805,7 @@ async function reviewWithSonnet(client, checkpoint, triage) {
|
|
|
1786
1805
|
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS2);
|
|
1787
1806
|
const response = await client.messages.create(
|
|
1788
1807
|
{
|
|
1789
|
-
model:
|
|
1808
|
+
model: model ?? DEFAULT_SONNET_MODEL,
|
|
1790
1809
|
max_tokens: 1e3,
|
|
1791
1810
|
system: REVIEW_SYSTEM_PROMPT,
|
|
1792
1811
|
messages: [{ role: "user", content: userPrompt }]
|
|
@@ -1855,6 +1874,7 @@ var TIMEOUT_SECONDS = 7;
|
|
|
1855
1874
|
var PLAN_MODE_TIMEOUT_SECONDS = 72 * 60 * 60;
|
|
1856
1875
|
var SAFE_NON_BASH_TOOLS = ["WebFetch", "WebSearch", "Task", "Glob", "Grep", "LS", "TodoRead", "TodoWrite", "NotebookRead"];
|
|
1857
1876
|
async function processPermissionRequest(input, anthropicClient) {
|
|
1877
|
+
const config2 = await readConfig();
|
|
1858
1878
|
if (input.tool_name === "Write" || input.tool_name === "Edit" || input.tool_name === "Read") {
|
|
1859
1879
|
const fileCheck = checkFileTool(input.tool_name, input.tool_input);
|
|
1860
1880
|
if (fileCheck.blocked) {
|
|
@@ -1917,8 +1937,7 @@ This will auto-reject if not approved.`,
|
|
|
1917
1937
|
};
|
|
1918
1938
|
}
|
|
1919
1939
|
if (input.tool_name.startsWith("mcp__")) {
|
|
1920
|
-
const
|
|
1921
|
-
const isAllowed = config3.allowedMCPTools.some((pattern) => {
|
|
1940
|
+
const isAllowed = config2.allowedMCPTools.some((pattern) => {
|
|
1922
1941
|
if (pattern.endsWith("*")) {
|
|
1923
1942
|
const prefix = pattern.slice(0, -1);
|
|
1924
1943
|
return input.tool_name.startsWith(prefix);
|
|
@@ -1962,7 +1981,6 @@ Auto-reject in ${TIMEOUT_SECONDS}s.`
|
|
|
1962
1981
|
};
|
|
1963
1982
|
}
|
|
1964
1983
|
const command = input.tool_input.command;
|
|
1965
|
-
const config2 = await readConfig();
|
|
1966
1984
|
for (const pattern of config2.customPatterns.allow) {
|
|
1967
1985
|
try {
|
|
1968
1986
|
if (new RegExp(pattern, "i").test(command)) {
|
|
@@ -2043,7 +2061,7 @@ Only proceed if you know what you're doing.`
|
|
|
2043
2061
|
};
|
|
2044
2062
|
}
|
|
2045
2063
|
process.stderr.write("\x1B[90m[vibesafu] Assessing security risks...\x1B[0m\n");
|
|
2046
|
-
const triage = await triageWithHaiku(anthropicClient, checkpoint);
|
|
2064
|
+
const triage = await triageWithHaiku(anthropicClient, checkpoint, config2.models.triage);
|
|
2047
2065
|
if (triage.classification === "BLOCK") {
|
|
2048
2066
|
return {
|
|
2049
2067
|
decision: "deny",
|
|
@@ -2059,7 +2077,7 @@ Only proceed if you know what you're doing.`
|
|
|
2059
2077
|
};
|
|
2060
2078
|
}
|
|
2061
2079
|
process.stderr.write("\x1B[90m[vibesafu] Escalating to deep analysis...\x1B[0m\n");
|
|
2062
|
-
const review = await reviewWithSonnet(anthropicClient, checkpoint, triage);
|
|
2080
|
+
const review = await reviewWithSonnet(anthropicClient, checkpoint, triage, config2.models.review);
|
|
2063
2081
|
if (review.verdict === "BLOCK") {
|
|
2064
2082
|
const result2 = {
|
|
2065
2083
|
decision: "deny",
|
package/package.json
CHANGED