@lazyingart/agintiflow 0.20.88 → 0.20.90
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 +28 -0
- package/src/command-policy.js +86 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.90",
|
|
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",
|
|
@@ -276,6 +276,30 @@ try {
|
|
|
276
276
|
);
|
|
277
277
|
assert(readonlyProbePolicy.allowed, "read-only toolchain probe sequence should not require package-install-policy=allow");
|
|
278
278
|
assert(readonlyProbePolicy.category === "read-only", "read-only toolchain probe sequence should be classified as read-only");
|
|
279
|
+
const readonlyVersionPipelinePolicy = evaluateCommandPolicy(
|
|
280
|
+
"which pdflatex latexmk python3 2>&1; pdflatex --version 2>&1 | head -2; latexmk --version 2>&1 | head -2",
|
|
281
|
+
dockerWorkspaceNoInstallsPolicy
|
|
282
|
+
);
|
|
283
|
+
assert(readonlyVersionPipelinePolicy.allowed, "read-only version probe pipelines should not require package-install-policy=allow");
|
|
284
|
+
assert(readonlyVersionPipelinePolicy.category === "read-only", "read-only version probe pipelines should be classified as read-only");
|
|
285
|
+
const readonlyTestEchoPolicy = evaluateCommandPolicy(
|
|
286
|
+
'test -f /usr/bin/pdflatex && echo "pdflatex: FOUND" || echo "pdflatex: NOT FOUND"; test -f /usr/bin/latexmk && echo "latexmk: FOUND" || echo "latexmk: NOT FOUND"; python3 --version 2>&1',
|
|
287
|
+
dockerWorkspaceNoInstallsPolicy
|
|
288
|
+
);
|
|
289
|
+
assert(readonlyTestEchoPolicy.allowed, "read-only test/echo probe sequence should not require package-install-policy=allow");
|
|
290
|
+
assert(readonlyTestEchoPolicy.category === "read-only", "read-only test/echo probe sequence should be classified as read-only");
|
|
291
|
+
const pdflatexCompilePolicy = evaluateCommandPolicy(
|
|
292
|
+
'cd profile-latex-20260506 && pdflatex -interaction=nonstopmode -halt-on-error main.tex 2>&1; echo "PDFLATEX_EXIT:$?"',
|
|
293
|
+
dockerWorkspaceNoInstallsPolicy
|
|
294
|
+
);
|
|
295
|
+
assert(pdflatexCompilePolicy.allowed, "workspace-local pdflatex compile should be allowed without package installs");
|
|
296
|
+
assert(pdflatexCompilePolicy.category === "toolchain", "workspace-local pdflatex compile should be classified as toolchain");
|
|
297
|
+
const latexmkCompilePolicy = evaluateCommandPolicy(
|
|
298
|
+
'cd profile-latex-20260506 && latexmk -pdf main.tex 2>&1; echo "LATEXMK_EXIT:$?"',
|
|
299
|
+
dockerWorkspaceNoInstallsPolicy
|
|
300
|
+
);
|
|
301
|
+
assert(latexmkCompilePolicy.allowed, "workspace-local latexmk compile should be allowed without package installs");
|
|
302
|
+
assert(latexmkCompilePolicy.category === "toolchain", "workspace-local latexmk compile should be classified as toolchain");
|
|
279
303
|
const curlPolicy = evaluateCommandPolicy("curl -s -o /dev/null -w '%{http_code}' https://github.com/lazyingart/AgInTiFlow.git", dockerWorkspacePolicy);
|
|
280
304
|
assert(curlPolicy.allowed, "curl URL probe with flags should be allowed in docker-workspace allow mode");
|
|
281
305
|
assert(curlPolicy.needsNetwork, "curl URL probe with flags was not classified as network");
|
|
@@ -736,6 +760,10 @@ try {
|
|
|
736
760
|
"auto_system_pro_route",
|
|
737
761
|
"auto_engineering_guidance",
|
|
738
762
|
"command_policy_readonly_probe_sequence_no_installs",
|
|
763
|
+
"command_policy_readonly_version_pipeline_no_installs",
|
|
764
|
+
"command_policy_readonly_test_echo_no_installs",
|
|
765
|
+
"command_policy_pdflatex_compile_no_installs",
|
|
766
|
+
"command_policy_latexmk_compile_no_installs",
|
|
739
767
|
"command_policy_git_clone_network",
|
|
740
768
|
"command_policy_safe_chmod_sequence",
|
|
741
769
|
"command_policy_cd_workspace",
|
package/src/command-policy.js
CHANGED
|
@@ -23,6 +23,10 @@ const READ_ONLY_PATTERNS = [
|
|
|
23
23
|
/^python(?:3)?\s+--version$/,
|
|
24
24
|
/^pip(?:3)?\s+--version$/,
|
|
25
25
|
/^conda\s+--version$/,
|
|
26
|
+
/^(?:pdflatex|latexmk)\s+--version$/,
|
|
27
|
+
/^test\s+-[efdx]\s+[-\w./~]+$/,
|
|
28
|
+
/^true$/,
|
|
29
|
+
/^false$/,
|
|
26
30
|
/^echo(?:\s+.+)?$/,
|
|
27
31
|
];
|
|
28
32
|
|
|
@@ -261,7 +265,7 @@ function classifyGitClone(normalized) {
|
|
|
261
265
|
}
|
|
262
266
|
|
|
263
267
|
function classifySimpleCommand(normalized) {
|
|
264
|
-
const
|
|
268
|
+
const benignRedirectCommand = stripBenignRedirections(normalized);
|
|
265
269
|
if (ALWAYS_BLOCKED_PATTERNS.some((pattern) => pattern.test(normalized))) {
|
|
266
270
|
return { category: "blocked", reason: "Command is blocked because it may expose secrets or publish packages." };
|
|
267
271
|
}
|
|
@@ -314,7 +318,7 @@ function classifySimpleCommand(normalized) {
|
|
|
314
318
|
const gitCloneClassification = classifyGitClone(normalized);
|
|
315
319
|
if (gitCloneClassification) return gitCloneClassification;
|
|
316
320
|
|
|
317
|
-
const unquoted = stripQuotedSegments(
|
|
321
|
+
const unquoted = stripQuotedSegments(benignRedirectCommand);
|
|
318
322
|
const lowered = ` ${unquoted.toLowerCase()} `;
|
|
319
323
|
if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
|
|
320
324
|
return {
|
|
@@ -333,25 +337,25 @@ function classifySimpleCommand(normalized) {
|
|
|
333
337
|
};
|
|
334
338
|
}
|
|
335
339
|
|
|
336
|
-
if (matchAny(READ_ONLY_PATTERNS,
|
|
340
|
+
if (matchAny(READ_ONLY_PATTERNS, benignRedirectCommand) || isReadOnlyFindCommand(normalized)) {
|
|
337
341
|
return { category: "read-only", needsNetwork: false, writesWorkspace: false };
|
|
338
342
|
}
|
|
339
|
-
if (matchAny(TEST_PATTERNS,
|
|
343
|
+
if (matchAny(TEST_PATTERNS, benignRedirectCommand)) {
|
|
340
344
|
return { category: "test", needsNetwork: false, writesWorkspace: false };
|
|
341
345
|
}
|
|
342
|
-
if (matchAny(TOOLCHAIN_PATTERNS,
|
|
346
|
+
if (matchAny(TOOLCHAIN_PATTERNS, benignRedirectCommand)) {
|
|
343
347
|
return { category: "toolchain", needsNetwork: false, writesWorkspace: true };
|
|
344
348
|
}
|
|
345
|
-
if (matchAny(NETWORK_FETCH_PATTERNS,
|
|
346
|
-
return { category: "network-fetch", needsNetwork: true, writesWorkspace: /(\s-o\s|\s-O\s)/.test(
|
|
349
|
+
if (matchAny(NETWORK_FETCH_PATTERNS, benignRedirectCommand)) {
|
|
350
|
+
return { category: "network-fetch", needsNetwork: true, writesWorkspace: /(\s-o\s|\s-O\s)/.test(benignRedirectCommand) };
|
|
347
351
|
}
|
|
348
|
-
if (matchAny(SYSTEM_PACKAGE_INSTALL_PATTERNS,
|
|
352
|
+
if (matchAny(SYSTEM_PACKAGE_INSTALL_PATTERNS, benignRedirectCommand)) {
|
|
349
353
|
return { category: "system-package-install", needsNetwork: true, writesWorkspace: false, requiresDockerRoot: true };
|
|
350
354
|
}
|
|
351
|
-
if (matchAny(PACKAGE_INSTALL_PATTERNS,
|
|
355
|
+
if (matchAny(PACKAGE_INSTALL_PATTERNS, benignRedirectCommand)) {
|
|
352
356
|
return { category: "package-install", needsNetwork: true, writesWorkspace: true };
|
|
353
357
|
}
|
|
354
|
-
if (matchAny(ENV_SETUP_PATTERNS,
|
|
358
|
+
if (matchAny(ENV_SETUP_PATTERNS, benignRedirectCommand)) {
|
|
355
359
|
return { category: "env-setup", needsNetwork: false, writesWorkspace: true };
|
|
356
360
|
}
|
|
357
361
|
|
|
@@ -392,13 +396,13 @@ function splitTopLevelShellSequence(command = "") {
|
|
|
392
396
|
quote = char;
|
|
393
397
|
continue;
|
|
394
398
|
}
|
|
395
|
-
if (char === ";" || (char === "&" && text[index + 1] === "&")) {
|
|
399
|
+
if (char === ";" || (char === "&" && text[index + 1] === "&") || (char === "|" && text[index + 1] === "|")) {
|
|
396
400
|
const part = current.trim();
|
|
397
401
|
if (!part) return null;
|
|
398
402
|
parts.push(part);
|
|
399
403
|
current = "";
|
|
400
404
|
hadSeparator = true;
|
|
401
|
-
if (char === "&") index += 1;
|
|
405
|
+
if (char === "&" || char === "|") index += 1;
|
|
402
406
|
continue;
|
|
403
407
|
}
|
|
404
408
|
current += char;
|
|
@@ -412,7 +416,7 @@ function splitTopLevelShellSequence(command = "") {
|
|
|
412
416
|
function classifyShellSequence(normalized) {
|
|
413
417
|
const parts = splitTopLevelShellSequence(normalized);
|
|
414
418
|
if (!parts) return null;
|
|
415
|
-
const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
|
|
419
|
+
const classifications = parts.map((part) => classifyCdCommand(part) || classifyPipelineSequence(part) || classifySimpleCommand(part));
|
|
416
420
|
const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
|
|
417
421
|
if (blocked) return blocked;
|
|
418
422
|
const broad = classifications.find((classification) => classification.category === "general-shell");
|
|
@@ -446,6 +450,73 @@ function classifyShellSequence(normalized) {
|
|
|
446
450
|
};
|
|
447
451
|
}
|
|
448
452
|
|
|
453
|
+
function splitTopLevelPipeline(command = "") {
|
|
454
|
+
const parts = [];
|
|
455
|
+
let current = "";
|
|
456
|
+
let quote = "";
|
|
457
|
+
let escaped = false;
|
|
458
|
+
let hadPipe = false;
|
|
459
|
+
const text = String(command || "");
|
|
460
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
461
|
+
const char = text[index];
|
|
462
|
+
if (escaped) {
|
|
463
|
+
current += char;
|
|
464
|
+
escaped = false;
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
if (char === "\\") {
|
|
468
|
+
current += char;
|
|
469
|
+
escaped = true;
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
if (quote) {
|
|
473
|
+
current += char;
|
|
474
|
+
if (char === quote) quote = "";
|
|
475
|
+
continue;
|
|
476
|
+
}
|
|
477
|
+
if (char === "'" || char === '"') {
|
|
478
|
+
current += char;
|
|
479
|
+
quote = char;
|
|
480
|
+
continue;
|
|
481
|
+
}
|
|
482
|
+
if (char === "|" && text[index + 1] !== "|") {
|
|
483
|
+
const part = current.trim();
|
|
484
|
+
if (!part) return null;
|
|
485
|
+
parts.push(part);
|
|
486
|
+
current = "";
|
|
487
|
+
hadPipe = true;
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
current += char;
|
|
491
|
+
}
|
|
492
|
+
const finalPart = current.trim();
|
|
493
|
+
if (finalPart) parts.push(finalPart);
|
|
494
|
+
if (!hadPipe || parts.length < 2) return null;
|
|
495
|
+
return parts;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function classifyPipelineSequence(normalized) {
|
|
499
|
+
const parts = splitTopLevelPipeline(normalized);
|
|
500
|
+
if (!parts) return null;
|
|
501
|
+
const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
|
|
502
|
+
const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
|
|
503
|
+
if (blocked) return blocked;
|
|
504
|
+
if (classifications.every((classification) => classification.category === "read-only")) {
|
|
505
|
+
return {
|
|
506
|
+
category: "read-only",
|
|
507
|
+
needsNetwork: false,
|
|
508
|
+
writesWorkspace: false,
|
|
509
|
+
reason: `Read-only shell pipeline: ${normalized}`,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
return {
|
|
513
|
+
category: "general-shell",
|
|
514
|
+
needsNetwork: classifications.some((classification) => classification.needsNetwork),
|
|
515
|
+
writesWorkspace: classifications.some((classification) => classification.writesWorkspace),
|
|
516
|
+
reason: `Shell pipeline includes a broad segment and requires trusted shell policy: ${normalized}`,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
449
520
|
function classifyCdCommand(normalized) {
|
|
450
521
|
const match = normalized.match(/^cd\s+([-\w./]+)\s+&&\s+(.+)$/);
|
|
451
522
|
if (!match) return null;
|
|
@@ -454,7 +525,7 @@ function classifyCdCommand(normalized) {
|
|
|
454
525
|
if (!isSafeRelativeDir(dir) && !virtualWorkspacePath) {
|
|
455
526
|
return { category: "blocked", reason: `cd target must be a safe workspace-relative directory: ${dir}` };
|
|
456
527
|
}
|
|
457
|
-
const innerClassification = classifySimpleCommand(inner.trim());
|
|
528
|
+
const innerClassification = classifyShellSequence(inner.trim()) || classifyPipelineSequence(inner.trim()) || classifySimpleCommand(inner.trim());
|
|
458
529
|
if (innerClassification.category === "blocked") return innerClassification;
|
|
459
530
|
return { ...innerClassification, cdDir: dir, virtualWorkspacePath };
|
|
460
531
|
}
|
|
@@ -463,7 +534,7 @@ export function classifyCommand(command) {
|
|
|
463
534
|
const normalized = String(command || "").trim();
|
|
464
535
|
if (!normalized) return { category: "blocked", reason: "Command is empty." };
|
|
465
536
|
|
|
466
|
-
return classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifySimpleCommand(normalized);
|
|
537
|
+
return classifyCdCommand(normalized) || classifyShellSequence(normalized) || classifyPipelineSequence(normalized) || classifySimpleCommand(normalized);
|
|
467
538
|
}
|
|
468
539
|
|
|
469
540
|
export function evaluateCommandPolicy(command, config) {
|