From 3d8c3ce6a353d6f75a14fff2760dec89249d136e Mon Sep 17 00:00:00 2001 From: Jesse_Chen Date: Thu, 30 Jul 2026 12:21:37 +0800 Subject: [PATCH] fix: harden staging ownership recovery --- deploy/sync-staging-tree.sh | 5 ++-- .../tests/staging-backend-workflows.test.ts | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/deploy/sync-staging-tree.sh b/deploy/sync-staging-tree.sh index b67623fc..ccda6697 100755 --- a/deploy/sync-staging-tree.sh +++ b/deploy/sync-staging-tree.sh @@ -7,11 +7,12 @@ if [ "$#" -ne 2 ] || [ ! -d "$1" ] || [ ! -d "$2" ]; then fi destination_deploy="$2/deploy" -if [ -d "$destination_deploy" ] && [ ! -w "$destination_deploy" ]; then - docker run --rm --network none --read-only --user 0:0 \ +if [ -d "$destination_deploy" ]; then + docker run --rm --pull never --network none --read-only --user 0:0 \ --cap-drop ALL --cap-add CHOWN --security-opt no-new-privileges \ -v "$destination_deploy:/destination" postgres:17-alpine \ chown -R "$(id -u):$(id -g)" /destination + chmod -R u+rwX "$destination_deploy" fi rsync -az --delete --no-owner --no-group \ diff --git a/frontend/tests/staging-backend-workflows.test.ts b/frontend/tests/staging-backend-workflows.test.ts index a5fc8777..2e17807c 100644 --- a/frontend/tests/staging-backend-workflows.test.ts +++ b/frontend/tests/staging-backend-workflows.test.ts @@ -153,7 +153,7 @@ test("live staging sync preserves env, state, incoming files, and encrypted back } }); -test("live staging sync repairs a non-writable deploy tree without preserving foreign ownership", () => { +test("live staging sync repairs nested deploy-tree drift without preserving foreign ownership", () => { const root = mkdtempSync(join(tmpdir(), "jyotisha-live-sync-permissions-")); const source = join(root, "source"); const destination = join(root, "destination"); @@ -163,20 +163,21 @@ test("live staging sync repairs a non-writable deploy tree without preserving fo mkdirSync(join(source, "deploy"), { recursive: true }); mkdirSync(destinationDeploy, { recursive: true }); mkdirSync(mockBin); - writeFileSync(join(source, "deploy", "current.txt"), "new\n"); - writeFileSync(join(destinationDeploy, "stale.txt"), "old\n"); + mkdirSync(join(source, "deploy", "postgres"), { recursive: true }); + mkdirSync(join(destinationDeploy, "postgres"), { recursive: true }); + writeFileSync(join(source, "deploy", "postgres", "current.txt"), "new\n"); + writeFileSync(join(destinationDeploy, "postgres", "stale.txt"), "old\n"); writeFileSync( join(mockBin, "docker"), [ "#!/usr/bin/env bash", "set -euo pipefail", "printf '%s\\n' \"$*\" >\"$DOCKER_LOG\"", - 'chmod -R u+rwX "$REPAIR_DESTINATION"', "", ].join("\n"), ); chmodSync(join(mockBin, "docker"), 0o755); - chmodSync(destinationDeploy, 0o555); + chmodSync(join(destinationDeploy, "postgres"), 0o555); try { const result = spawnSync( @@ -188,23 +189,28 @@ test("live staging sync repairs a non-writable deploy tree without preserving fo ...process.env, PATH: `${mockBin}:${process.env.PATH ?? ""}`, DOCKER_LOG: dockerLog, - REPAIR_DESTINATION: destinationDeploy, }, }, ); assert.equal(result.status, 0, result.stderr); - assert.equal(existsSync(join(destinationDeploy, "stale.txt")), false); assert.equal( - readFileSync(join(destinationDeploy, "current.txt"), "utf8"), + existsSync(join(destinationDeploy, "postgres", "stale.txt")), + false, + ); + assert.equal( + readFileSync(join(destinationDeploy, "postgres", "current.txt"), "utf8"), "new\n", ); const invocation = readFileSync(dockerLog, "utf8"); - assert.match(invocation, /--network none --read-only --user 0:0/); + assert.match(invocation, /--pull never --network none --read-only --user 0:0/); assert.match(invocation, /--cap-drop ALL --cap-add CHOWN/); assert.match(invocation, /postgres:17-alpine chown -R/); + assert.match(read(syncScript), /chmod -R u\+rwX/); assert.match(read(syncScript), /--no-owner --no-group/); } finally { - if (existsSync(destinationDeploy)) chmodSync(destinationDeploy, 0o755); + if (existsSync(join(destinationDeploy, "postgres"))) { + chmodSync(join(destinationDeploy, "postgres"), 0o755); + } rmSync(root, { recursive: true, force: true }); } });