@lazyingart/agintiflow 0.20.233 → 0.20.235

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.235",
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,40 @@ 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 documentIntegrityBuildPolicy = evaluateCommandPolicy(
1078
+ "set -e; mkdir -p output .verification; echo '== source hashes BEFORE build =='; sha256sum README.md PROJECT_NOTES.md source/budget.csv source/meeting-notes.txt source/style-notes.md | tee .verification/src-before.sha256; echo '== chmod + build =='; chmod +x build.sh scripts/*.py; ./build.sh; echo '== source hashes AFTER build (must match BEFORE) =='; sha256sum README.md PROJECT_NOTES.md source/budget.csv source/meeting-notes.txt source/style-notes.md | tee .verification/src-after.sha256; diff .verification/src-before.sha256 .verification/src-after.sha256 && echo 'SOURCE FILES UNCHANGED (byte-for-byte preserved)'",
1079
+ dockerWorkspacePolicy
1080
+ );
1081
+ assert(
1082
+ documentIntegrityBuildPolicy.allowed,
1083
+ "document integrity build with bounded workspace tee targets should be allowed in trusted Docker mode"
1084
+ );
1085
+ assert(
1086
+ documentIntegrityBuildPolicy.category === "general-shell",
1087
+ "document integrity build should remain broad trusted shell, not destructive"
1088
+ );
1089
+ const externalTeePolicy = evaluateCommandPolicy("tee /etc/aginti-test", dockerWorkspacePolicy);
1090
+ assert(!externalTeePolicy.allowed, "tee outside the workspace should remain blocked");
1091
+ const globTeePolicy = evaluateCommandPolicy("tee reports/*.txt", dockerWorkspacePolicy);
1092
+ assert(!globTeePolicy.allowed, "tee wildcard targets should remain blocked");
1093
+ const hostGlobChmodPolicy = evaluateCommandPolicy("chmod +x scripts/*.py", hostWorkspacePolicy);
1094
+ assert(!hostGlobChmodPolicy.allowed, "host workspace chmod globs should require explicit trusted host access");
1095
+ const recursiveChmodPolicy = evaluateCommandPolicy("chmod -R +x scripts", dockerWorkspacePolicy);
1096
+ assert(!recursiveChmodPolicy.allowed, "recursive chmod should remain outside the bounded permission-change policy");
1097
+ const parentTraversalChmodPolicy = evaluateCommandPolicy("chmod +x scripts/../outside.py", dockerWorkspacePolicy);
1098
+ assert(!parentTraversalChmodPolicy.allowed, "chmod parent traversal should remain blocked");
1065
1099
  const hostWorkspaceChmodPolicy = evaluateCommandPolicy("chmod +x android-app/gradlew && echo \"CHMOD_OK\"", hostWorkspacePolicy);
1066
1100
  assert(hostWorkspaceChmodPolicy.allowed, "host mode should allow workspace-local chmod without full-host destructive access");
1067
1101
  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_WORKSPACE_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,79 @@ 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_WORKSPACE_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
+
1033
+ function classifyWorkspaceTee(normalized = "") {
1034
+ if (hasActiveShellExpansion(normalized)) return null;
1035
+ const tokens = tokenizeShellWords(normalized);
1036
+ if (tokens[0] !== "tee") return null;
1037
+ let index = 1;
1038
+ if (["-a", "--append"].includes(tokens[index])) index += 1;
1039
+ if (tokens[index] === "--") index += 1;
1040
+ const targets = tokens.slice(index);
1041
+ if (!targets.length || targets.length > SAFE_WORKSPACE_TARGET_LIMIT) return null;
1042
+ const unsafeTarget = targets.find(
1043
+ (target) => target.includes("*") || !isSafeWorkspaceChmodTarget(target)
1044
+ );
1045
+ if (unsafeTarget) {
1046
+ return {
1047
+ category: "blocked",
1048
+ reason: `tee target must be a bounded literal workspace-relative path: ${unsafeTarget}`,
1049
+ };
1050
+ }
1051
+ return {
1052
+ category: "workspace-write",
1053
+ needsNetwork: false,
1054
+ writesWorkspace: true,
1055
+ virtualWorkspacePath: targets.some((target) => target.startsWith("/workspace/")),
1056
+ reason: `Command writes standard input to ${targets.length} bounded workspace target${targets.length === 1 ? "" : "s"}.`,
1057
+ };
1058
+ }
1059
+
986
1060
  function isInsideDirectory(root, candidate) {
987
1061
  const relative = path.relative(root, candidate);
988
1062
  return relative === "" || (relative && !relative.startsWith("..") && !path.isAbsolute(relative));
@@ -1378,20 +1452,10 @@ function classifySimpleCommand(normalized) {
1378
1452
  }
1379
1453
  return { category: "workspace-write", needsNetwork: false, writesWorkspace: true, virtualWorkspacePath };
1380
1454
  }
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
- }
1455
+ const permissionChangeClassification = classifyWorkspacePermissionChange(normalized);
1456
+ if (permissionChangeClassification) return permissionChangeClassification;
1457
+ const teeClassification = classifyWorkspaceTee(normalized);
1458
+ if (teeClassification) return teeClassification;
1395
1459
  const gitCloneClassification = classifyGitClone(normalized);
1396
1460
  if (gitCloneClassification) return gitCloneClassification;
1397
1461
  const envExportClassification = classifySafeEnvExport(normalized);
@@ -2553,7 +2617,11 @@ export function evaluateCommandPolicy(command, config = {}) {
2553
2617
  };
2554
2618
  }
2555
2619
 
2556
- if (classification.category === "permission-change" && sandboxMode !== "host" && !trustedDockerShell && !trustedHostShell) {
2620
+ if (
2621
+ classification.category === "permission-change" &&
2622
+ ((sandboxMode !== "host" && !trustedDockerShell && !trustedHostShell) ||
2623
+ (sandboxMode === "host" && classification.permissionTargetsContainGlob && !trustedHostShell))
2624
+ ) {
2557
2625
  return {
2558
2626
  allowed: false,
2559
2627
  ...classification,