@gaberrb/polypus 0.4.4 → 0.4.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/index.js CHANGED
@@ -291,7 +291,10 @@ var en = {
291
291
  // @-mentions
292
292
  "mentions.injectedHeader": "Referenced files (@-mentions)",
293
293
  "mentions.dirHeader": "@{path} (directory listing)",
294
- "mentions.notFound": "(could not resolve @{path}: not found or outside the allow-list)"
294
+ "mentions.notFound": "(could not resolve @{path}: not found or outside the allow-list)",
295
+ // safety policy
296
+ "policy.blockedCommand": "blocked by safety policy ({reason}) \u2014 refusing in all modes",
297
+ "policy.secretFound": "write blocked: a possible secret ({kind}) was found on line {line}. Remove it or load it from an environment variable instead of hard-coding it."
295
298
  };
296
299
  var ptBR = {
297
300
  "common.default": "padr\xE3o",
@@ -490,6 +493,9 @@ var ptBR = {
490
493
  "mentions.injectedHeader": "Arquivos referenciados (@-mentions)",
491
494
  "mentions.dirHeader": "@{path} (conte\xFAdo do diret\xF3rio)",
492
495
  "mentions.notFound": "(n\xE3o foi poss\xEDvel resolver @{path}: n\xE3o encontrado ou fora da allow-list)",
496
+ // safety policy
497
+ "policy.blockedCommand": "bloqueado pela pol\xEDtica de seguran\xE7a ({reason}) \u2014 recusado em todos os modos",
498
+ "policy.secretFound": "escrita bloqueada: poss\xEDvel segredo ({kind}) encontrado na linha {line}. Remova-o ou carregue de uma vari\xE1vel de ambiente em vez de fix\xE1-lo no c\xF3digo.",
493
499
  "models.fetching": "Buscando modelos do OpenRouter\u2026",
494
500
  "models.fetchError": "N\xE3o foi poss\xEDvel buscar modelos: {msg}",
495
501
  "models.none": "Nenhum modelo corresponde aos filtros.",
@@ -1005,6 +1011,53 @@ function isCommandPreApproved(allowedCommands, command) {
1005
1011
  return allowedCommands.some((prefix) => c === prefix || c.startsWith(prefix.trim() + " "));
1006
1012
  }
1007
1013
 
1014
+ // src/core/permissions/policy.ts
1015
+ var DANGEROUS_COMMANDS = [
1016
+ { re: /--no-preserve-root/i, reason: "rm --no-preserve-root" },
1017
+ { re: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:/, reason: "fork bomb" },
1018
+ { re: /\bmkfs(\.\w+)?\b/i, reason: "filesystem format (mkfs)" },
1019
+ { re: /\bdd\b[^\n]*\bof=\/dev\/(sd|nvme|hd|disk)/i, reason: "dd writing to a raw disk device" },
1020
+ { re: />\s*\/dev\/(sd|nvme|hd|disk)/i, reason: "redirect to a raw disk device" },
1021
+ { re: /\bchmod\s+(-[a-z]*\s+)*-?R?\s*777\s+\//i, reason: "chmod 777 on /" },
1022
+ { re: /\b(curl|wget)\b[^\n|]*\|\s*(sudo\s+)?(sh|bash|zsh)\b/i, reason: "piping a downloaded script straight into a shell" }
1023
+ ];
1024
+ function isDangerousRm(command) {
1025
+ if (!/\brm\b/i.test(command)) return false;
1026
+ const recursive = /(?:^|\s)-[a-z]*r[a-z]*f/i.test(command) || /(?:^|\s)-[a-z]*f[a-z]*r/i.test(command) || /(?:^|\s)-[a-z]*r\b/i.test(command) && /(?:^|\s)-[a-z]*f\b/i.test(command);
1027
+ if (!recursive) return false;
1028
+ return /(?:\s|^)(?:\/\*|\/|~|\$HOME|\*)(?:\s|$)/.test(command);
1029
+ }
1030
+ function screenCommand(command) {
1031
+ if (isDangerousRm(command)) {
1032
+ return { blocked: true, reason: "recursive force-delete of / ~ or *" };
1033
+ }
1034
+ for (const { re, reason } of DANGEROUS_COMMANDS) {
1035
+ if (re.test(command)) return { blocked: true, reason };
1036
+ }
1037
+ return { blocked: false };
1038
+ }
1039
+ var SECRET_PATTERNS = [
1040
+ { re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/, kind: "private key block" },
1041
+ { re: /\bAKIA[0-9A-Z]{16}\b/, kind: "AWS access key id" },
1042
+ { re: /\bgh[pousr]_[A-Za-z0-9]{36,}\b/, kind: "GitHub token" },
1043
+ { re: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/, kind: "Slack token" },
1044
+ { re: /\bsk-[A-Za-z0-9]{32,}\b/, kind: "OpenAI-style secret key" },
1045
+ { re: /\bAIza[0-9A-Za-z_-]{35}\b/, kind: "Google API key" }
1046
+ ];
1047
+ function scanSecrets(text2) {
1048
+ const findings = [];
1049
+ const lines = text2.split("\n");
1050
+ for (let i = 0; i < lines.length; i++) {
1051
+ for (const { re, kind } of SECRET_PATTERNS) {
1052
+ if (re.test(lines[i])) {
1053
+ findings.push({ line: i + 1, kind });
1054
+ break;
1055
+ }
1056
+ }
1057
+ }
1058
+ return findings;
1059
+ }
1060
+
1008
1061
  // src/core/permissions/modes.ts
1009
1062
  var PermissionEngine = class {
1010
1063
  constructor(opts) {
@@ -1019,9 +1072,17 @@ var PermissionEngine = class {
1019
1072
  const d = checkPath(this.opts.policy, target);
1020
1073
  return d.allowed ? { allowed: true } : { allowed: false, reason: d.reason };
1021
1074
  }
1022
- async authorizeWrite(target, preview) {
1075
+ async authorizeWrite(target, preview, content) {
1023
1076
  const d = checkPath(this.opts.policy, target);
1024
1077
  if (!d.allowed) return { allowed: false, reason: d.reason };
1078
+ const findings = scanSecrets(content ?? preview ?? "");
1079
+ if (findings.length > 0) {
1080
+ const first = findings[0];
1081
+ return {
1082
+ allowed: false,
1083
+ reason: t("policy.secretFound", { kind: first.kind, line: first.line })
1084
+ };
1085
+ }
1025
1086
  if (this.opts.mode === "plan") {
1026
1087
  return { allowed: false, reason: "plan mode: file modifications are disabled" };
1027
1088
  }
@@ -1030,6 +1091,10 @@ var PermissionEngine = class {
1030
1091
  return ok ? { allowed: true } : { allowed: false, reason: "rejected by user" };
1031
1092
  }
1032
1093
  async authorizeCommand(command) {
1094
+ const screen = screenCommand(command);
1095
+ if (screen.blocked) {
1096
+ return { allowed: false, reason: t("policy.blockedCommand", { reason: screen.reason ?? "" }) };
1097
+ }
1033
1098
  if (this.opts.mode === "plan") {
1034
1099
  return { allowed: false, reason: "plan mode: running commands is disabled" };
1035
1100
  }
@@ -1317,7 +1382,8 @@ var editFileTool = {
1317
1382
  const decision = await ctx.permissions.authorizeWrite(
1318
1383
  args.data.path,
1319
1384
  `- ${firstLine(args.data.search)}
1320
- + ${firstLine(args.data.replace)}`
1385
+ + ${firstLine(args.data.replace)}`,
1386
+ args.data.replace
1321
1387
  );
1322
1388
  if (!decision.allowed) return { ok: false, output: `Edit denied: ${decision.reason}` };
1323
1389
  try {
@@ -1599,7 +1665,7 @@ var writeFileTool = {
1599
1665
  };
1600
1666
  }
1601
1667
  const preview = previewContent(args.data.content);
1602
- const decision = await ctx.permissions.authorizeWrite(args.data.path, preview);
1668
+ const decision = await ctx.permissions.authorizeWrite(args.data.path, preview, args.data.content);
1603
1669
  if (!decision.allowed) return { ok: false, output: `Write denied: ${decision.reason}` };
1604
1670
  try {
1605
1671
  const abs = resolve6(ctx.workspace, args.data.path);