@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 +70 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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);
|