@lazyingart/agintiflow 0.20.87 → 0.20.88

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lazyingart/agintiflow",
3
- "version": "0.20.87",
3
+ "version": "0.20.88",
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",
@@ -266,6 +266,16 @@ try {
266
266
  packageInstallPolicy: "allow",
267
267
  commandCwd: workspace,
268
268
  };
269
+ const dockerWorkspaceNoInstallsPolicy = {
270
+ ...dockerWorkspacePolicy,
271
+ packageInstallPolicy: "block",
272
+ };
273
+ const readonlyProbePolicy = evaluateCommandPolicy(
274
+ 'which pdflatex 2>&1; which latexmk 2>&1; echo "---"; find /workspace -name \'*.tex\' -maxdepth 3 2>/dev/null; echo "exit: $?"',
275
+ dockerWorkspaceNoInstallsPolicy
276
+ );
277
+ assert(readonlyProbePolicy.allowed, "read-only toolchain probe sequence should not require package-install-policy=allow");
278
+ assert(readonlyProbePolicy.category === "read-only", "read-only toolchain probe sequence should be classified as read-only");
269
279
  const curlPolicy = evaluateCommandPolicy("curl -s -o /dev/null -w '%{http_code}' https://github.com/lazyingart/AgInTiFlow.git", dockerWorkspacePolicy);
270
280
  assert(curlPolicy.allowed, "curl URL probe with flags should be allowed in docker-workspace allow mode");
271
281
  assert(curlPolicy.needsNetwork, "curl URL probe with flags was not classified as network");
@@ -725,6 +735,7 @@ try {
725
735
  "large_profile_pro_route",
726
736
  "auto_system_pro_route",
727
737
  "auto_engineering_guidance",
738
+ "command_policy_readonly_probe_sequence_no_installs",
728
739
  "command_policy_git_clone_network",
729
740
  "command_policy_safe_chmod_sequence",
730
741
  "command_policy_cd_workspace",
@@ -26,6 +26,25 @@ const READ_ONLY_PATTERNS = [
26
26
  /^echo(?:\s+.+)?$/,
27
27
  ];
28
28
 
29
+ function stripBenignRedirections(command = "") {
30
+ return String(command || "")
31
+ .replace(/\s+2>&1\b/g, "")
32
+ .replace(/\s+1>&2\b/g, "")
33
+ .replace(/\s+2>\/dev\/null\b/g, "")
34
+ .replace(/\s+1>\/dev\/null\b/g, "")
35
+ .replace(/\s+>\/dev\/null\b/g, "")
36
+ .trim();
37
+ }
38
+
39
+ function isReadOnlyFindCommand(command = "") {
40
+ const normalized = stripBenignRedirections(command);
41
+ if (!/^find\s+/.test(normalized)) return false;
42
+ if (/(^|\s)(-delete|-exec|-execdir|-ok|-okdir|-fprint|-fprintf|-fls)\b/.test(normalized)) return false;
43
+ const unquoted = stripQuotedSegments(normalized);
44
+ if (/[|<>;&`$]/.test(unquoted)) return false;
45
+ return /^find\s+(?:[-./~\w]+|\/workspace)(?:\s+[-\w]+(?:\s+(?:"[^"\n]*"|'[^'\n]*'|[^\s|<>;&`$]+))?)*$/.test(normalized);
46
+ }
47
+
29
48
  const TEST_PATTERNS = [
30
49
  /^npm\s+(run\s+)?(check|test|build|lint)(?:\s+--\s+[-\w./:=]+)*$/,
31
50
  /^npm\s+--prefix\s+[-\w./]+\s+(run\s+)?(check|test|build|lint)(?:\s+--\s+[-\w./:=]+)*$/,
@@ -242,6 +261,7 @@ function classifyGitClone(normalized) {
242
261
  }
243
262
 
244
263
  function classifySimpleCommand(normalized) {
264
+ const readOnlyCommand = stripBenignRedirections(normalized);
245
265
  if (ALWAYS_BLOCKED_PATTERNS.some((pattern) => pattern.test(normalized))) {
246
266
  return { category: "blocked", reason: "Command is blocked because it may expose secrets or publish packages." };
247
267
  }
@@ -294,7 +314,7 @@ function classifySimpleCommand(normalized) {
294
314
  const gitCloneClassification = classifyGitClone(normalized);
295
315
  if (gitCloneClassification) return gitCloneClassification;
296
316
 
297
- const unquoted = stripQuotedSegments(normalized);
317
+ const unquoted = stripQuotedSegments(readOnlyCommand);
298
318
  const lowered = ` ${unquoted.toLowerCase()} `;
299
319
  if (BLOCKED_WRITE_TOKENS.some((part) => lowered.includes(part))) {
300
320
  return {
@@ -313,7 +333,7 @@ function classifySimpleCommand(normalized) {
313
333
  };
314
334
  }
315
335
 
316
- if (matchAny(READ_ONLY_PATTERNS, normalized)) {
336
+ if (matchAny(READ_ONLY_PATTERNS, readOnlyCommand) || isReadOnlyFindCommand(normalized)) {
317
337
  return { category: "read-only", needsNetwork: false, writesWorkspace: false };
318
338
  }
319
339
  if (matchAny(TEST_PATTERNS, normalized)) {
@@ -395,14 +415,35 @@ function classifyShellSequence(normalized) {
395
415
  const classifications = parts.map((part) => classifyCdCommand(part) || classifySimpleCommand(part));
396
416
  const blocked = classifications.find((classification) => classification.category === "blocked" || classification.category === "destructive");
397
417
  if (blocked) return blocked;
398
- return {
399
- category: "general-shell",
418
+ const broad = classifications.find((classification) => classification.category === "general-shell");
419
+ if (broad) {
420
+ return {
421
+ ...broad,
422
+ reason: `Command sequence includes a broad shell segment and requires trusted shell policy: ${normalized}`,
423
+ };
424
+ }
425
+ const categories = new Set(classifications.map((classification) => classification.category));
426
+ const aggregate = {
400
427
  needsNetwork: classifications.some((classification) => classification.needsNetwork),
401
428
  writesWorkspace: classifications.some((classification) => classification.writesWorkspace),
402
429
  requiresDockerRoot: classifications.some((classification) => classification.requiresDockerRoot),
403
430
  virtualWorkspacePath: classifications.some((classification) => classification.virtualWorkspacePath),
404
431
  reason: `Command sequence uses shell separators with individually classified safe segments: ${normalized}`,
405
432
  };
433
+ if (categories.has("system-package-install")) return { category: "system-package-install", ...aggregate };
434
+ if (categories.has("package-install")) return { category: "package-install", ...aggregate };
435
+ if (categories.has("env-setup")) return { category: "env-setup", ...aggregate };
436
+ if (categories.has("permission-change")) return { category: "permission-change", ...aggregate };
437
+ if (categories.has("git-remote")) return { category: "git-remote", ...aggregate };
438
+ if (categories.has("network-fetch")) return { category: "network-fetch", ...aggregate };
439
+ if (categories.has("workspace-write")) return { category: "workspace-write", ...aggregate };
440
+ if (categories.has("toolchain")) return { category: "toolchain", ...aggregate };
441
+ if (categories.has("test")) return { category: "test", ...aggregate };
442
+ if (categories.has("git-workflow")) return { category: "git-workflow", ...aggregate };
443
+ return {
444
+ category: "read-only",
445
+ ...aggregate,
446
+ };
406
447
  }
407
448
 
408
449
  function classifyCdCommand(normalized) {