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.
Files changed (2) hide show
  1. package/dist/index.js +24 -14
  2. 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 { ...DEFAULT_CONFIG, ...JSON.parse(content) };
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(/</g, "&lt;").replace(/>/g, "&gt;");
1557
+ sanitized = sanitized.replace(/]]>/g, "]]&gt;");
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 HAIKU_MODEL = "claude-haiku-4-20250514";
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: HAIKU_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 SONNET_MODEL = "claude-sonnet-4-20250514";
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: SONNET_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 config3 = await readConfig();
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibesafu",
3
- "version": "0.1.22",
3
+ "version": "0.1.25",
4
4
  "description": "Better Claude Code workflow with smart safety checks. Safe YOLO mode without --dangerously-skip-permission",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",