|
6 | 6 | chmodSync, |
7 | 7 | existsSync, |
8 | 8 | lstatSync, |
| 9 | + readlinkSync, |
9 | 10 | readFileSync, |
10 | 11 | readdirSync, |
11 | 12 | realpathSync, |
@@ -861,6 +862,9 @@ const runnerRootfsPermissionContract = Object.freeze([ |
861 | 862 | { path: "/usr/local", kind: "directory", mode: 0o755 }, |
862 | 863 | { path: "/usr/local/bin", kind: "directory", mode: 0o755 }, |
863 | 864 | { path: "/usr/local/bin/node", kind: "file", mode: 0o555 }, |
| 865 | + { path: "/usr/local/bin/python", kind: "symlink", mode: 0o777 }, |
| 866 | + { path: "/usr/local/bin/python3", kind: "symlink", mode: 0o777 }, |
| 867 | + { path: "/usr/local/bin/python3.13", kind: "file", mode: 0o755 }, |
864 | 868 | { path: "/usr/bin", kind: "directory", mode: 0o755 }, |
865 | 869 | { path: "/usr/bin/setpriv", kind: "file", mode: 0o555 }, |
866 | 870 | { path: "/usr/bin/bash", kind: "file", mode: 0o555 }, |
@@ -908,8 +912,8 @@ const runnerRootfsPermissionContract = Object.freeze([ |
908 | 912 | }, |
909 | 913 | { |
910 | 914 | path: "/opt/counterlab-venv/bin/python", |
911 | | - kind: "file", |
912 | | - mode: 0o550, |
| 915 | + kind: "symlink", |
| 916 | + mode: 0o777, |
913 | 917 | gid: 10001, |
914 | 918 | }, |
915 | 919 | { path: "/opt/codex", kind: "directory", mode: 0o550, gid: 10002 }, |
@@ -946,6 +950,12 @@ const runnerRootfsPermissionContract = Object.freeze([ |
946 | 950 | { path: "/counterlab-runtime", kind: "directory", mode: 0o755 }, |
947 | 951 | ]); |
948 | 952 |
|
| 953 | +const runnerRootfsSymlinkContract = new Map([ |
| 954 | + ["/usr/local/bin/python", "python3"], |
| 955 | + ["/usr/local/bin/python3", "python3.13"], |
| 956 | + ["/opt/counterlab-venv/bin/python", "/usr/local/bin/python"], |
| 957 | +]); |
| 958 | + |
949 | 959 | export function validateContainedRunnerRootfsPermissions(entries) { |
950 | 960 | if ( |
951 | 961 | !Array.isArray(entries) || |
@@ -988,18 +998,31 @@ function verifyContainedRunnerRootfsPermissions(plan, imageRootfsPath) { |
988 | 998 | if (!plan.image.startsWith("counterlab-runner:git-")) return; |
989 | 999 | validateContainedRunnerRootfsPermissions( |
990 | 1000 | runnerRootfsPermissionContract.map((expected) => { |
991 | | - const metadata = statSync( |
| 1001 | + const path = |
992 | 1002 | expected.path === "/" |
993 | 1003 | ? imageRootfsPath |
994 | | - : resolve(imageRootfsPath, expected.path.slice(1)), |
995 | | - ); |
| 1004 | + : resolve(imageRootfsPath, expected.path.slice(1)); |
| 1005 | + const metadata = lstatSync(path); |
| 1006 | + const expectedLinkTarget = runnerRootfsSymlinkContract.get(expected.path); |
| 1007 | + if ( |
| 1008 | + (expectedLinkTarget === undefined && metadata.isSymbolicLink()) || |
| 1009 | + (expectedLinkTarget !== undefined && |
| 1010 | + (!metadata.isSymbolicLink() || |
| 1011 | + readlinkSync(path) !== expectedLinkTarget)) |
| 1012 | + ) { |
| 1013 | + throw new Error( |
| 1014 | + `contained runner rootfs symlink changed at ${expected.path}`, |
| 1015 | + ); |
| 1016 | + } |
996 | 1017 | return { |
997 | 1018 | path: expected.path, |
998 | 1019 | kind: metadata.isDirectory() |
999 | 1020 | ? "directory" |
1000 | 1021 | : metadata.isFile() |
1001 | 1022 | ? "file" |
1002 | | - : "other", |
| 1023 | + : metadata.isSymbolicLink() |
| 1024 | + ? "symlink" |
| 1025 | + : "other", |
1003 | 1026 | mode: metadata.mode & 0o777, |
1004 | 1027 | uid: metadata.uid, |
1005 | 1028 | gid: metadata.gid, |
|
0 commit comments