From 577f2fa54080d3accaef5f40fa54dc38d4750694 Mon Sep 17 00:00:00 2001 From: edincampara <142477787+edincampara@users.noreply.github.com> Date: Mon, 2 Mar 2026 00:45:21 +0100 Subject: [PATCH] fix(docker): harden /app/extensions permissions to 755 (#30191) * fix(docker): harden /app/extensions permissions to 755 Bundled extension directories shipped as world-writable (mode 777) in the Docker image. The plugin security scanner blocks any world- writable path with: WARN: blocked plugin candidate: world-writable path (/app/extensions/memory-core, mode=777) Add chmod -R 755 /app/extensions in the final USER root RUN step so all bundled extensions are readable but not world-writable. This runs as root before switching back to the node user, matching the pattern already used for chmod 755 /app/openclaw.mjs. Fixes #30139 * fix(docker): normalize plugin and agent path permissions * docs(changelog): add docker permissions entry for #30191 * Update CHANGELOG.md --------- Co-authored-by: Vincent Koc --- CHANGELOG.md | 1 + Dockerfile | 8 ++++++++ src/dockerfile.test.ts | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03bd82a8ec3..fb084a87f4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -75,6 +75,7 @@ Docs: https://docs.openclaw.ai - Android/Nodes notification wake flow: enable Android `system.notify` default allowlist, emit `notifications.changed` events for posted/removed notifications (excluding OpenClaw app-owned notifications), canonicalize notification session keys before enqueue/wake routing, and skip heartbeat wakes when consecutive notification summaries dedupe. (#29440) Thanks @obviyus. - Android/Gateway canvas capability refresh: send `node.canvas.capability.refresh` with object `params` (`{}`) from Android node runtime so gateway object-schema validation accepts refresh retries and A2UI host recovery works after scoped capability expiry. (#28413) Thanks @obviyus. - Daemon/macOS TLS certs: default LaunchAgent service env `NODE_EXTRA_CA_CERTS` to `/etc/ssl/cert.pem` (while preserving explicit overrides) so HTTPS clients no longer fail with local-issuer errors under launchd. (#27915) Thanks @Lukavyi. +- Docker/Image permissions: normalize `/app/extensions`, `/app/.agent`, and `/app/.agents` to directory mode `755` and file mode `644` during image build so plugin discovery does not block inherited world-writable paths. (#30191) Fixes #30139. Thanks @edincampara. - Update/Global npm: fallback to `--omit=optional` when global `npm update` fails so optional dependency install failures no longer abort update flows. (#24896) Thanks @xinhuagu and @vincentkoc. - Plugins/NPM spec install: fix npm-spec plugin installs when `npm pack` output is empty by detecting newly created `.tgz` archives in the pack directory. (#21039) Thanks @graysurf and @vincentkoc. - Plugins/Install: clear stale install errors when an npm package is not found so follow-up install attempts report current state correctly. (#25073) Thanks @dalefrieswthat. diff --git a/Dockerfile b/Dockerfile index 73343a64624..48d4baf6a06 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,6 +46,14 @@ RUN if [ -n "$OPENCLAW_INSTALL_BROWSER" ]; then \ USER node COPY --chown=node:node . . +# Normalize copied plugin/agent paths so plugin safety checks do not reject +# world-writable directories inherited from source file modes. +RUN for dir in /app/extensions /app/.agent /app/.agents; do \ + if [ -d "$dir" ]; then \ + find "$dir" -type d -exec chmod 755 {} +; \ + find "$dir" -type f -exec chmod 644 {} +; \ + fi; \ + done RUN pnpm build # Force pnpm for UI build (Bun may fail on ARM/Synology architectures) ENV OPENCLAW_PREFER_PNPM=1 diff --git a/src/dockerfile.test.ts b/src/dockerfile.test.ts index 5cd55d9b53f..325987e2b5a 100644 --- a/src/dockerfile.test.ts +++ b/src/dockerfile.test.ts @@ -20,4 +20,11 @@ describe("Dockerfile", () => { ); expect(dockerfile).toContain("apt-get install -y --no-install-recommends xvfb"); }); + + it("normalizes plugin and agent paths permissions in image layers", async () => { + const dockerfile = await readFile(dockerfilePath, "utf8"); + expect(dockerfile).toContain("for dir in /app/extensions /app/.agent /app/.agents"); + expect(dockerfile).toContain('find "$dir" -type d -exec chmod 755 {} +'); + expect(dockerfile).toContain('find "$dir" -type f -exec chmod 644 {} +'); + }); });