fix(docker): keep setup chown from following symlinks (#107847)

* Fix Docker setup chown symlink handling

* fix(docker): harden setup ownership repair

Co-authored-by: luyifan <al3060388206@gmail.com>

* fix(docker): pin ownership repair path

* test(docker): assert ownership setup ordering

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
This commit is contained in:
ooiuuii
2026-07-16 15:55:54 +08:00
committed by GitHub
parent 47ffb49f71
commit 59a21e3429
2 changed files with 29 additions and 6 deletions
+10 -4
View File
@@ -792,13 +792,19 @@ echo "==> Fixing data-directory permissions"
# Use -xdev to restrict chown to the config-dir mount only — without it,
# the recursive chown would cross into the workspace bind mount and rewrite
# ownership of all user project files on Linux hosts.
# Run a no-dereference chown from each entry's directory. This keeps ownership
# repair for sockets/FIFOs while preventing a swapped symlink leaf from
# redirecting the root operation outside the mounted tree.
# After fixing the config dir, only the OpenClaw metadata subdirectory
# (.openclaw/) inside the workspace gets chowned, not the user's project files.
run_prestart_gateway --user root --entrypoint sh openclaw-gateway -c \
'find /home/node/.openclaw -xdev -exec chown node:node {} +; \
chown node:node /home/node/.config; \
find /home/node/.config/openclaw -xdev -exec chown node:node {} +; \
[ -d /home/node/.openclaw/workspace/.openclaw ] && chown -R node:node /home/node/.openclaw/workspace/.openclaw || true'
'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin; export PATH; \
/usr/bin/find -P /home/node/.openclaw -xdev -execdir /usr/bin/chown -h node:node {} +; \
/usr/bin/chown -h node:node /home/node/.config; \
/usr/bin/find -P /home/node/.config/openclaw -xdev -execdir /usr/bin/chown -h node:node {} +; \
if [ -d /home/node/.openclaw/workspace/.openclaw ] && [ ! -L /home/node/.openclaw/workspace/.openclaw ]; then \
/usr/bin/find -P /home/node/.openclaw/workspace/.openclaw -xdev -execdir /usr/bin/chown -h node:node {} +; \
fi || true'
echo ""
if [[ -n "$SKIP_ONBOARDING" ]]; then
+19 -2
View File
@@ -179,6 +179,10 @@ const prestartContainerEnvFlags = [
"-e OPENCLAW_WORKSPACE_DIR=/home/node/.openclaw/workspace",
].join(" ");
const noFollowOwnershipRepair = (root: string) =>
`/usr/bin/find -P ${root} -xdev -execdir /usr/bin/chown -h node:node {} +`;
const prestartSafePath = "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
function createEnv(
sandbox: DockerSetupSandbox,
overrides: Record<string, string | undefined> = {},
@@ -847,11 +851,24 @@ describe("scripts/docker/setup.sh", () => {
// Verify that a root-user chown step runs before setup.
const log = await readDockerLog(activeSandbox);
const chownIdx = log.indexOf("--user root");
const safePathIdx = log.indexOf(`${prestartSafePath}; export PATH`);
const stateRepairIdx = log.indexOf(noFollowOwnershipRepair("/home/node/.openclaw"));
const onboardIdx = log.indexOf("onboard");
expect(chownIdx).toBeGreaterThanOrEqual(0);
expect(safePathIdx).toBeGreaterThan(chownIdx);
expect(stateRepairIdx).toBeGreaterThan(safePathIdx);
expect(onboardIdx).toBeGreaterThan(chownIdx);
expect(log).toContain("run --rm --no-deps --user root --entrypoint sh openclaw-gateway -c");
expect(log).toContain("chown node:node /home/node/.config");
expect(log).toContain("/usr/bin/chown -h node:node /home/node/.config");
expect(log).toContain(noFollowOwnershipRepair("/home/node/.openclaw"));
expect(log).toContain(noFollowOwnershipRepair("/home/node/.config/openclaw"));
expect(log).toContain("[ ! -L /home/node/.openclaw/workspace/.openclaw ]");
expect(log).toContain(noFollowOwnershipRepair("/home/node/.openclaw/workspace/.openclaw"));
expect(log).toContain("fi || true");
expect(log).not.toContain("-type d -o -type f");
expect(log).not.toContain("-exec chown");
expect(log).not.toContain(" chown node:node");
expect(log).not.toContain("chown -R node:node /home/node/.openclaw/workspace/.openclaw");
});
it("precreates auth profile secret key dir outside the mounted state dir", async () => {
@@ -872,7 +889,7 @@ describe("scripts/docker/setup.sh", () => {
expect(secretDir.startsWith(`${configDir}/`)).toBe(false);
const log = await readDockerLog(activeSandbox);
expect(log).toContain("find /home/node/.config/openclaw -xdev");
expect(log).toContain(noFollowOwnershipRepair("/home/node/.config/openclaw"));
});
it("reuses existing config token when OPENCLAW_GATEWAY_TOKEN is unset", async () => {