@lazyingart/agintiflow 0.20.233 → 0.20.234

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.233",
3
+ "version": "0.20.234",
4
4
  "type": "module",
5
5
  "description": "AgInTiFlow is a project-aware agent workspace for hybrid wet-dry R&D, hardware-aware intelligence, software automation, and industrial workflows.",
6
6
  "license": "Apache-2.0",
@@ -1062,6 +1062,24 @@ try {
1062
1062
  assert(safeChmodAndRunPolicy.allowed, "safe workspace chmod + script run sequence should be allowed in docker-workspace allow mode");
1063
1063
  const unsafeChmodPolicy = evaluateCommandPolicy("chmod +x /etc/passwd", dockerWorkspacePolicy);
1064
1064
  assert(!unsafeChmodPolicy.allowed, "chmod outside the workspace should be blocked");
1065
+ const documentBuildSequencePolicy = evaluateCommandPolicy(
1066
+ "sha256sum README.md PROJECT_NOTES.md source/budget.csv source/meeting-notes.txt source/style-notes.md > /tmp/src-before.sha256 && cat /tmp/src-before.sha256 && echo '---' && chmod +x build.sh scripts/*.py && ./build.sh",
1067
+ dockerWorkspacePolicy
1068
+ );
1069
+ assert(
1070
+ documentBuildSequencePolicy.allowed,
1071
+ "bounded document build sequence with workspace-local chmod glob should be allowed in trusted Docker mode"
1072
+ );
1073
+ assert(
1074
+ documentBuildSequencePolicy.category === "general-shell",
1075
+ "document build sequence should remain broad trusted shell, not destructive"
1076
+ );
1077
+ const hostGlobChmodPolicy = evaluateCommandPolicy("chmod +x scripts/*.py", hostWorkspacePolicy);
1078
+ assert(!hostGlobChmodPolicy.allowed, "host workspace chmod globs should require explicit trusted host access");
1079
+ const recursiveChmodPolicy = evaluateCommandPolicy("chmod -R +x scripts", dockerWorkspacePolicy);
1080
+ assert(!recursiveChmodPolicy.allowed, "recursive chmod should remain outside the bounded permission-change policy");
1081
+ const parentTraversalChmodPolicy = evaluateCommandPolicy("chmod +x scripts/../outside.py", dockerWorkspacePolicy);
1082
+ assert(!parentTraversalChmodPolicy.allowed, "chmod parent traversal should remain blocked");
1065
1083
  const hostWorkspaceChmodPolicy = evaluateCommandPolicy("chmod +x android-app/gradlew && echo \"CHMOD_OK\"", hostWorkspacePolicy);
1066
1084
  assert(hostWorkspaceChmodPolicy.allowed, "host mode should allow workspace-local chmod without full-host destructive access");
1067
1085
  assert(
@@ -732,7 +732,8 @@ function classifyBackgroundShell(normalized = "") {
732
732
  }
733
733
 
734
734
  const SAFE_WORKSPACE_WRITE_PATTERNS = [/^mkdir\s+-p\s+[-\w./]+$/];
735
- const PERMISSION_CHANGE_PATTERNS = [/^(?:sudo\s+)?chmod\s+[-+=,rwxugoXst0-7]+\s+[-\w./]+$/];
735
+ const SAFE_CHMOD_MODE_PATTERN = /^[-+=,rwxugoXst0-7]+$/;
736
+ const SAFE_CHMOD_TARGET_LIMIT = 64;
736
737
  const SAFE_ENV_ASSIGNMENT_NAMES = new Set(["ANDROID_HOME", "ANDROID_SDK_ROOT", "JAVA_HOME", "GRADLE_USER_HOME", "PATH"]);
737
738
  const SAFE_ENV_VALUE_PATTERN = /^[-\w./:@+,%]+$/;
738
739
 
@@ -983,6 +984,52 @@ function isSafeWorkspacePath(value) {
983
984
  return isSafeRelativeDir(value) || isSafeVirtualWorkspacePath(value);
984
985
  }
985
986
 
987
+ function isSafeWorkspaceChmodTarget(value = "") {
988
+ const normalized = String(value || "").trim();
989
+ if (!normalized || normalized.startsWith("-") || normalized.startsWith("~")) return false;
990
+ const relative = normalized.startsWith("/workspace/")
991
+ ? normalized.replace(/^\/workspace\//, "")
992
+ : normalized;
993
+ if (!relative || relative.startsWith("/") || !/^[A-Za-z0-9_@%+=,:.*\/-]+$/.test(relative)) {
994
+ return false;
995
+ }
996
+ const parts = relative.split("/");
997
+ if (parts.some((part) => !part || part === "." || part === "..")) return false;
998
+ const globParts = parts.filter((part) => part.includes("*"));
999
+ if (!globParts.length) return isSafeWorkspacePath(normalized);
1000
+ if (globParts.length !== 1 || !parts.at(-1)?.includes("*")) return false;
1001
+ const leaf = parts.at(-1) || "";
1002
+ return !leaf.includes("**") && /^[A-Za-z0-9_.-]*\*[A-Za-z0-9_.-]*$/.test(leaf);
1003
+ }
1004
+
1005
+ function classifyWorkspacePermissionChange(normalized = "") {
1006
+ if (hasActiveShellExpansion(normalized)) return null;
1007
+ const tokens = tokenizeShellWords(normalized);
1008
+ let index = 0;
1009
+ if (tokens[index] === "sudo") index += 1;
1010
+ if (tokens[index] !== "chmod") return null;
1011
+ const mode = String(tokens[index + 1] || "");
1012
+ const targets = tokens.slice(index + 2);
1013
+ if (!SAFE_CHMOD_MODE_PATTERN.test(mode) || !targets.length || targets.length > SAFE_CHMOD_TARGET_LIMIT) {
1014
+ return null;
1015
+ }
1016
+ const unsafeTarget = targets.find((target) => !isSafeWorkspaceChmodTarget(target));
1017
+ if (unsafeTarget) {
1018
+ return {
1019
+ category: "blocked",
1020
+ reason: `chmod target must be a bounded workspace-relative path: ${unsafeTarget}`,
1021
+ };
1022
+ }
1023
+ return {
1024
+ category: "permission-change",
1025
+ needsNetwork: false,
1026
+ writesWorkspace: true,
1027
+ virtualWorkspacePath: targets.some((target) => target.startsWith("/workspace/")),
1028
+ permissionTargetsContainGlob: targets.some((target) => target.includes("*")),
1029
+ reason: `Command changes workspace file mode for ${targets.length} bounded target${targets.length === 1 ? "" : "s"}.`,
1030
+ };
1031
+ }
1032
+
986
1033
  function isInsideDirectory(root, candidate) {
987
1034
  const relative = path.relative(root, candidate);
988
1035
  return relative === "" || (relative && !relative.startsWith("..") && !path.isAbsolute(relative));
@@ -1378,20 +1425,8 @@ function classifySimpleCommand(normalized) {
1378
1425
  }
1379
1426
  return { category: "workspace-write", needsNetwork: false, writesWorkspace: true, virtualWorkspacePath };
1380
1427
  }
1381
- if (matchAny(PERMISSION_CHANGE_PATTERNS, normalized)) {
1382
- const target = normalized.split(/\s+/).at(-1) || "";
1383
- const virtualWorkspacePath = isSafeVirtualWorkspacePath(target);
1384
- if (!isSafeWorkspacePath(target)) {
1385
- return { category: "blocked", reason: `chmod target must be a safe workspace-relative path: ${target}` };
1386
- }
1387
- return {
1388
- category: "permission-change",
1389
- needsNetwork: false,
1390
- writesWorkspace: true,
1391
- virtualWorkspacePath,
1392
- reason: `Command changes workspace file mode: ${normalized}`,
1393
- };
1394
- }
1428
+ const permissionChangeClassification = classifyWorkspacePermissionChange(normalized);
1429
+ if (permissionChangeClassification) return permissionChangeClassification;
1395
1430
  const gitCloneClassification = classifyGitClone(normalized);
1396
1431
  if (gitCloneClassification) return gitCloneClassification;
1397
1432
  const envExportClassification = classifySafeEnvExport(normalized);
@@ -2553,7 +2588,11 @@ export function evaluateCommandPolicy(command, config = {}) {
2553
2588
  };
2554
2589
  }
2555
2590
 
2556
- if (classification.category === "permission-change" && sandboxMode !== "host" && !trustedDockerShell && !trustedHostShell) {
2591
+ if (
2592
+ classification.category === "permission-change" &&
2593
+ ((sandboxMode !== "host" && !trustedDockerShell && !trustedHostShell) ||
2594
+ (sandboxMode === "host" && classification.permissionTargetsContainGlob && !trustedHostShell))
2595
+ ) {
2557
2596
  return {
2558
2597
  allowed: false,
2559
2598
  ...classification,