@lazyingart/agintiflow 0.20.77 → 0.20.78
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/package.json +1 -1
- package/scripts/smoke-coding-tools.js +12 -0
- package/src/command-policy.js +34 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.78",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Low-cost, project-aware Web and CLI agents with DeepSeek/Venice/OpenAI routing, visible tool calls, durable sessions, scouts, AAPS, SCS, and guarded local execution.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -263,6 +263,18 @@ try {
|
|
|
263
263
|
assert(clonePolicy.allowed, "git clone should be allowed in docker-workspace allow mode");
|
|
264
264
|
assert(clonePolicy.needsNetwork, "git clone was not classified as network");
|
|
265
265
|
assert(clonePolicy.writesWorkspace, "git clone was not classified as workspace-writing");
|
|
266
|
+
const quotedDangerSearchPolicy = evaluateCommandPolicy(
|
|
267
|
+
'grep -nE "rm -rf|git reset --hard|git clean -fd|find . -delete" reports/destructive-command-policy.md',
|
|
268
|
+
dockerWorkspacePolicy
|
|
269
|
+
);
|
|
270
|
+
assert(quotedDangerSearchPolicy.allowed, "read-only grep for destructive command strings should be allowed");
|
|
271
|
+
assert(
|
|
272
|
+
quotedDangerSearchPolicy.category !== "destructive",
|
|
273
|
+
"quoted destructive strings in grep pattern should not classify as a destructive command"
|
|
274
|
+
);
|
|
275
|
+
const actualDangerAfterQuotePolicy = evaluateCommandPolicy('echo "rm -rf is text" && rm -rf reports', dockerWorkspacePolicy);
|
|
276
|
+
assert(!actualDangerAfterQuotePolicy.allowed, "actual destructive command after quoted text should still be blocked");
|
|
277
|
+
assert(actualDangerAfterQuotePolicy.category === "destructive", "actual destructive command after quoted text was not classified as destructive");
|
|
266
278
|
const unsafeCloneTarget = evaluateCommandPolicy("git clone https://github.com/lazyingart/AgInTiFlow.git ../AgInTiFlow", dockerWorkspacePolicy);
|
|
267
279
|
assert(!unsafeCloneTarget.allowed, "git clone outside the workspace should be blocked");
|
|
268
280
|
const blockedClonePolicy = evaluateCommandPolicy("git clone https://github.com/lazyingart/AgInTiFlow.git", {
|
package/src/command-policy.js
CHANGED
|
@@ -11,6 +11,7 @@ const READ_ONLY_PATTERNS = [
|
|
|
11
11
|
/^ls(?:\s+[-\w./~*]+)*$/,
|
|
12
12
|
/^find(?:\s+[./~\w-]+)*(?:\s+-maxdepth\s+\d+)?(?:\s+-type\s+[fd])?$/,
|
|
13
13
|
/^rg(?:\s+.+)?$/,
|
|
14
|
+
/^grep(?:\s+.+)?$/,
|
|
14
15
|
/^cat(?:\s+[-\w./~*]+)+$/,
|
|
15
16
|
/^head(?:\s+.+)?$/,
|
|
16
17
|
/^tail(?:\s+.+)?$/,
|
|
@@ -151,6 +152,36 @@ function matchAny(patterns, command) {
|
|
|
151
152
|
return patterns.some((pattern) => pattern.test(command));
|
|
152
153
|
}
|
|
153
154
|
|
|
155
|
+
function stripQuotedSegments(command = "") {
|
|
156
|
+
let output = "";
|
|
157
|
+
let quote = "";
|
|
158
|
+
let escaped = false;
|
|
159
|
+
for (const char of String(command || "")) {
|
|
160
|
+
if (escaped) {
|
|
161
|
+
escaped = false;
|
|
162
|
+
if (!quote) output += " ";
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (char === "\\") {
|
|
166
|
+
escaped = true;
|
|
167
|
+
if (!quote) output += char;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (quote) {
|
|
171
|
+
if (char === quote) quote = "";
|
|
172
|
+
output += " ";
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if (char === "'" || char === '"') {
|
|
176
|
+
quote = char;
|
|
177
|
+
output += " ";
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
output += char;
|
|
181
|
+
}
|
|
182
|
+
return output;
|
|
183
|
+
}
|
|
184
|
+
|
|
154
185
|
function isSafeRelativeDir(value) {
|
|
155
186
|
const normalized = String(value || "").trim();
|
|
156
187
|
if (!normalized || normalized.startsWith("/") || normalized.startsWith("~")) return false;
|
|
@@ -231,7 +262,8 @@ function classifySimpleCommand(normalized) {
|
|
|
231
262
|
const gitCloneClassification = classifyGitClone(normalized);
|
|
232
263
|
if (gitCloneClassification) return gitCloneClassification;
|
|
233
264
|
|
|
234
|
-
const
|
|
265
|
+
const unquoted = stripQuotedSegments(normalized);
|
|
266
|
+
const lowered = ` ${unquoted.toLowerCase()} `;
|
|
235
267
|
if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
|
|
236
268
|
return {
|
|
237
269
|
category: "destructive",
|
|
@@ -240,7 +272,7 @@ function classifySimpleCommand(normalized) {
|
|
|
240
272
|
reason: `Command contains a write-capable or destructive token: ${normalized}`,
|
|
241
273
|
};
|
|
242
274
|
}
|
|
243
|
-
if (BLOCKED_SHELL_TOKENS.some((part) =>
|
|
275
|
+
if (BLOCKED_SHELL_TOKENS.some((part) => unquoted.includes(part))) {
|
|
244
276
|
return {
|
|
245
277
|
category: "general-shell",
|
|
246
278
|
needsNetwork: true,
|