vibesafu 0.1.22 → 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 +24 -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({
|
|
@@ -1543,7 +1554,7 @@ function sanitizeForPrompt(command) {
|
|
|
1543
1554
|
if (sanitized.length > MAX_COMMAND_LENGTH) {
|
|
1544
1555
|
sanitized = sanitized.slice(0, MAX_COMMAND_LENGTH) + "... [truncated]";
|
|
1545
1556
|
}
|
|
1546
|
-
sanitized = sanitized.replace(
|
|
1557
|
+
sanitized = sanitized.replace(/]]>/g, "]]>");
|
|
1547
1558
|
sanitized = sanitized.replace(/\n{3,}/g, "\n\n");
|
|
1548
1559
|
return sanitized;
|
|
1549
1560
|
}
|
|
@@ -1590,7 +1601,7 @@ function shouldForceEscalate(command) {
|
|
|
1590
1601
|
}
|
|
1591
1602
|
|
|
1592
1603
|
// src/guard/haiku-triage.ts
|
|
1593
|
-
var
|
|
1604
|
+
var DEFAULT_HAIKU_MODEL = "claude-haiku-4-20250514";
|
|
1594
1605
|
var API_TIMEOUT_MS = 3e4;
|
|
1595
1606
|
var TRIAGE_SYSTEM_PROMPT = `You are a security triage agent for an autonomous coding system.
|
|
1596
1607
|
Your ONLY job is to classify commands as SELF_HANDLE, ESCALATE, or BLOCK.
|
|
@@ -1637,7 +1648,7 @@ var FORCE_ESCALATE_TYPES = [
|
|
|
1637
1648
|
"package_install"
|
|
1638
1649
|
// Supply chain attacks via postinstall scripts
|
|
1639
1650
|
];
|
|
1640
|
-
async function triageWithHaiku(client, checkpoint) {
|
|
1651
|
+
async function triageWithHaiku(client, checkpoint, model) {
|
|
1641
1652
|
if (FORCE_ESCALATE_TYPES.includes(checkpoint.type)) {
|
|
1642
1653
|
return {
|
|
1643
1654
|
classification: "ESCALATE",
|
|
@@ -1652,7 +1663,7 @@ async function triageWithHaiku(client, checkpoint) {
|
|
|
1652
1663
|
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS);
|
|
1653
1664
|
const response = await client.messages.create(
|
|
1654
1665
|
{
|
|
1655
|
-
model:
|
|
1666
|
+
model: model ?? DEFAULT_HAIKU_MODEL,
|
|
1656
1667
|
max_tokens: 500,
|
|
1657
1668
|
system: TRIAGE_SYSTEM_PROMPT,
|
|
1658
1669
|
messages: [{ role: "user", content: userPrompt }]
|
|
@@ -1714,7 +1725,7 @@ async function triageWithHaiku(client, checkpoint) {
|
|
|
1714
1725
|
}
|
|
1715
1726
|
|
|
1716
1727
|
// src/guard/sonnet-review.ts
|
|
1717
|
-
var
|
|
1728
|
+
var DEFAULT_SONNET_MODEL = "claude-sonnet-4-20250514";
|
|
1718
1729
|
var API_TIMEOUT_MS2 = 6e4;
|
|
1719
1730
|
var REVIEW_SYSTEM_PROMPT = `You are a senior security engineer reviewing potentially risky operations.
|
|
1720
1731
|
Your job is to analyze commands and determine if they are safe to execute.
|
|
@@ -1786,7 +1797,7 @@ BLOCK - Do not allow:
|
|
|
1786
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."
|
|
1787
1798
|
}
|
|
1788
1799
|
</response_format>`;
|
|
1789
|
-
async function reviewWithSonnet(client, checkpoint, triage) {
|
|
1800
|
+
async function reviewWithSonnet(client, checkpoint, triage, model) {
|
|
1790
1801
|
const sanitizedCommand = sanitizeForPrompt(checkpoint.command);
|
|
1791
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"));
|
|
1792
1803
|
try {
|
|
@@ -1794,7 +1805,7 @@ async function reviewWithSonnet(client, checkpoint, triage) {
|
|
|
1794
1805
|
const timeoutId = setTimeout(() => controller.abort(), API_TIMEOUT_MS2);
|
|
1795
1806
|
const response = await client.messages.create(
|
|
1796
1807
|
{
|
|
1797
|
-
model:
|
|
1808
|
+
model: model ?? DEFAULT_SONNET_MODEL,
|
|
1798
1809
|
max_tokens: 1e3,
|
|
1799
1810
|
system: REVIEW_SYSTEM_PROMPT,
|
|
1800
1811
|
messages: [{ role: "user", content: userPrompt }]
|
|
@@ -1863,6 +1874,7 @@ var TIMEOUT_SECONDS = 7;
|
|
|
1863
1874
|
var PLAN_MODE_TIMEOUT_SECONDS = 72 * 60 * 60;
|
|
1864
1875
|
var SAFE_NON_BASH_TOOLS = ["WebFetch", "WebSearch", "Task", "Glob", "Grep", "LS", "TodoRead", "TodoWrite", "NotebookRead"];
|
|
1865
1876
|
async function processPermissionRequest(input, anthropicClient) {
|
|
1877
|
+
const config2 = await readConfig();
|
|
1866
1878
|
if (input.tool_name === "Write" || input.tool_name === "Edit" || input.tool_name === "Read") {
|
|
1867
1879
|
const fileCheck = checkFileTool(input.tool_name, input.tool_input);
|
|
1868
1880
|
if (fileCheck.blocked) {
|
|
@@ -1925,8 +1937,7 @@ This will auto-reject if not approved.`,
|
|
|
1925
1937
|
};
|
|
1926
1938
|
}
|
|
1927
1939
|
if (input.tool_name.startsWith("mcp__")) {
|
|
1928
|
-
const
|
|
1929
|
-
const isAllowed = config3.allowedMCPTools.some((pattern) => {
|
|
1940
|
+
const isAllowed = config2.allowedMCPTools.some((pattern) => {
|
|
1930
1941
|
if (pattern.endsWith("*")) {
|
|
1931
1942
|
const prefix = pattern.slice(0, -1);
|
|
1932
1943
|
return input.tool_name.startsWith(prefix);
|
|
@@ -1970,7 +1981,6 @@ Auto-reject in ${TIMEOUT_SECONDS}s.`
|
|
|
1970
1981
|
};
|
|
1971
1982
|
}
|
|
1972
1983
|
const command = input.tool_input.command;
|
|
1973
|
-
const config2 = await readConfig();
|
|
1974
1984
|
for (const pattern of config2.customPatterns.allow) {
|
|
1975
1985
|
try {
|
|
1976
1986
|
if (new RegExp(pattern, "i").test(command)) {
|
|
@@ -2051,7 +2061,7 @@ Only proceed if you know what you're doing.`
|
|
|
2051
2061
|
};
|
|
2052
2062
|
}
|
|
2053
2063
|
process.stderr.write("\x1B[90m[vibesafu] Assessing security risks...\x1B[0m\n");
|
|
2054
|
-
const triage = await triageWithHaiku(anthropicClient, checkpoint);
|
|
2064
|
+
const triage = await triageWithHaiku(anthropicClient, checkpoint, config2.models.triage);
|
|
2055
2065
|
if (triage.classification === "BLOCK") {
|
|
2056
2066
|
return {
|
|
2057
2067
|
decision: "deny",
|
|
@@ -2067,7 +2077,7 @@ Only proceed if you know what you're doing.`
|
|
|
2067
2077
|
};
|
|
2068
2078
|
}
|
|
2069
2079
|
process.stderr.write("\x1B[90m[vibesafu] Escalating to deep analysis...\x1B[0m\n");
|
|
2070
|
-
const review = await reviewWithSonnet(anthropicClient, checkpoint, triage);
|
|
2080
|
+
const review = await reviewWithSonnet(anthropicClient, checkpoint, triage, config2.models.review);
|
|
2071
2081
|
if (review.verdict === "BLOCK") {
|
|
2072
2082
|
const result2 = {
|
|
2073
2083
|
decision: "deny",
|
package/package.json
CHANGED