@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 +1 -1
- package/scripts/smoke-coding-tools.js +34 -0
- package/src/command-policy.js +84 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lazyingart/agintiflow",
|
|
3
|
-
"version": "0.20.
|
|
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(
|
package/src/command-policy.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
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 (
|
|
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,
|