From d1cac600aad4bc155f5215997660f0f9989ac768 Mon Sep 17 00:00:00 2001 From: "Strittmatter, Stephan" Date: Thu, 26 Feb 2026 19:11:04 +0100 Subject: [PATCH 1/8] fix: docker permissions and db path configuration - Install gosu to drop privileges safely - Start container as root to fix volume permissions in entrypoint.sh - Set default HOME and OPENCLAW_HOME to ensure write access to persistent data - Add support for FLEET_DB_PATH environment variable --- Dockerfile | 8 +++++++- entrypoint.sh | 27 ++++++++++++++++++++++++++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index a6cdecb..ccf2d5e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,6 +19,11 @@ COPY --from=builder /venv /venv # Create a non-root user to run the application. RUN useradd -m -u 1000 clawmetry +# Install gosu to drop privileges safely. +RUN apt-get update && \ + apt-get install -y --no-install-recommends gosu && \ + rm -rf /var/lib/apt/lists/* + # Create the default OpenClaw data directory so the container starts # without requiring a volume mount (clawmetry auto-detects ~/.openclaw). RUN mkdir -p /home/clawmetry/.openclaw && \ @@ -35,7 +40,8 @@ COPY wsgi.py . COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -USER clawmetry +# The container will start as root to allow entrypoint.sh to fix permissions. +# It will then use 'gosu' to step down to the 'clawmetry' user. EXPOSE 8900 diff --git a/entrypoint.sh b/entrypoint.sh index 8fef2be..1b343ef 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -31,6 +31,8 @@ while [ $# -gt 0 ]; do export MC_URL="$2"; shift 2 ;; --fleet-api-key) export CLAWMETRY_FLEET_KEY="$2"; shift 2 ;; + --fleet-db-path) + export FLEET_DB_PATH="$2"; shift 2 ;; --no-debug|--debug) # Debug mode is not applicable under gunicorn; silently ignore. shift ;; @@ -40,7 +42,30 @@ while [ $# -gt 0 ]; do esac done -exec /venv/bin/gunicorn \ +# Ensure HOME is set correctly for the non-root user. +export HOME=/home/clawmetry +export OPENCLAW_HOME="${OPENCLAW_HOME:-/home/clawmetry/.openclaw}" + +# The container starts as root. We ensure the data directory exists and +# has the correct permissions before dropping privileges. +DATA_DIR="${OPENCLAW_DATA_DIR:-/home/clawmetry/.openclaw}" +echo "Fixing permissions for $DATA_DIR..." +mkdir -p "$DATA_DIR" +chown -R clawmetry:clawmetry "$DATA_DIR" +ls -ld "$DATA_DIR" +ls -la "$DATA_DIR" + +# Also ensure the fleet DB directory exists if FLEET_DB_PATH is set. +if [ -n "$FLEET_DB_PATH" ]; then + echo "Fixing permissions for fleet DB at $FLEET_DB_PATH..." + DB_DIR=$(dirname "$FLEET_DB_PATH") + mkdir -p "$DB_DIR" + chown -R clawmetry:clawmetry "$DB_DIR" + ls -ld "$DB_DIR" +fi + +echo "Starting gunicorn with gosu clawmetry..." +exec gosu clawmetry /venv/bin/gunicorn \ --bind "${HOST}:${PORT}" \ --workers 1 \ --threads 16 \ From 92c62326a07afdbdff6b4d0556838923dc742c3c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:27:37 +0000 Subject: [PATCH 2/8] Initial plan From a611a0410a40b34cd74c80484303bb0c935a3046 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 18:29:20 +0000 Subject: [PATCH 3/8] fix: avoid recursive chown on host bind-mounted data directories Co-authored-by: stritti <184547+stritti@users.noreply.github.com> --- entrypoint.sh | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 1b343ef..f4b0ba1 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -48,20 +48,25 @@ export OPENCLAW_HOME="${OPENCLAW_HOME:-/home/clawmetry/.openclaw}" # The container starts as root. We ensure the data directory exists and # has the correct permissions before dropping privileges. +# We intentionally do NOT recurse into existing content to avoid mutating +# host-side ownership when DATA_DIR is a bind mount where the host user's +# UID differs from the container's 'clawmetry' user (UID 1000). DATA_DIR="${OPENCLAW_DATA_DIR:-/home/clawmetry/.openclaw}" -echo "Fixing permissions for $DATA_DIR..." mkdir -p "$DATA_DIR" -chown -R clawmetry:clawmetry "$DATA_DIR" -ls -ld "$DATA_DIR" -ls -la "$DATA_DIR" +# Only fix ownership when the directory is still owned by root (e.g. just +# created above or a brand-new Docker-managed volume). This preserves the +# original ownership on pre-existing bind mounts. +if [ "$(stat -c '%u' "$DATA_DIR")" = "0" ]; then + chown clawmetry:clawmetry "$DATA_DIR" +fi # Also ensure the fleet DB directory exists if FLEET_DB_PATH is set. if [ -n "$FLEET_DB_PATH" ]; then - echo "Fixing permissions for fleet DB at $FLEET_DB_PATH..." DB_DIR=$(dirname "$FLEET_DB_PATH") mkdir -p "$DB_DIR" - chown -R clawmetry:clawmetry "$DB_DIR" - ls -ld "$DB_DIR" + if [ "$(stat -c '%u' "$DB_DIR")" = "0" ]; then + chown clawmetry:clawmetry "$DB_DIR" + fi fi echo "Starting gunicorn with gosu clawmetry..." From 22b27df74b613f98e8e8b07e28b34a56a1a94438 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 20:08:31 +0000 Subject: [PATCH 4/8] fix: add safety guards against dangerous path chown in entrypoint Co-authored-by: stritti <184547+stritti@users.noreply.github.com> --- entrypoint.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index f4b0ba1..2e55a01 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -56,7 +56,9 @@ mkdir -p "$DATA_DIR" # Only fix ownership when the directory is still owned by root (e.g. just # created above or a brand-new Docker-managed volume). This preserves the # original ownership on pre-existing bind mounts. -if [ "$(stat -c '%u' "$DATA_DIR")" = "0" ]; then +# Safety guard: never chown '/' or an empty path. +if [ -n "$DATA_DIR" ] && [ "$DATA_DIR" != "/" ] && \ + [ "$(stat -c '%u' "$DATA_DIR")" = "0" ]; then chown clawmetry:clawmetry "$DATA_DIR" fi @@ -64,7 +66,10 @@ fi if [ -n "$FLEET_DB_PATH" ]; then DB_DIR=$(dirname "$FLEET_DB_PATH") mkdir -p "$DB_DIR" - if [ "$(stat -c '%u' "$DB_DIR")" = "0" ]; then + # Safety guard: never chown '/' (e.g. FLEET_DB_PATH='/fleet.db'), + # '.' (relative path), or an empty string. + if [ -n "$DB_DIR" ] && [ "$DB_DIR" != "/" ] && [ "$DB_DIR" != "." ] && \ + [ "$(stat -c '%u' "$DB_DIR")" = "0" ]; then chown clawmetry:clawmetry "$DB_DIR" fi fi From cfb6321829902650f1ed210f6214ddcaab6b090f Mon Sep 17 00:00:00 2001 From: Stephan Strittmatter Date: Fri, 27 Feb 2026 12:38:23 +0100 Subject: [PATCH 5/8] Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index 2e55a01..5cc49d8 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -74,7 +74,7 @@ if [ -n "$FLEET_DB_PATH" ]; then fi fi -echo "Starting gunicorn with gosu clawmetry..." +echo "Dropping privileges to clawmetry user and starting gunicorn..." exec gosu clawmetry /venv/bin/gunicorn \ --bind "${HOST}:${PORT}" \ --workers 1 \ From 3cfd75c32974eeba69efc96a6f261b06704267a7 Mon Sep 17 00:00:00 2001 From: Stephan Strittmatter Date: Fri, 27 Feb 2026 12:38:41 +0100 Subject: [PATCH 6/8] Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- entrypoint.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/entrypoint.sh b/entrypoint.sh index 5cc49d8..61181d3 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -65,10 +65,15 @@ fi # Also ensure the fleet DB directory exists if FLEET_DB_PATH is set. if [ -n "$FLEET_DB_PATH" ]; then DB_DIR=$(dirname "$FLEET_DB_PATH") + # Normalise DB_DIR to an absolute path when FLEET_DB_PATH is relative. + case "$DB_DIR" in + /*) ;; + *) DB_DIR="$HOME/$DB_DIR" ;; + esac mkdir -p "$DB_DIR" - # Safety guard: never chown '/' (e.g. FLEET_DB_PATH='/fleet.db'), - # '.' (relative path), or an empty string. - if [ -n "$DB_DIR" ] && [ "$DB_DIR" != "/" ] && [ "$DB_DIR" != "." ] && \ + # Safety guard: never chown '/' (e.g. FLEET_DB_PATH='/fleet.db') + # or an empty string. + if [ -n "$DB_DIR" ] && [ "$DB_DIR" != "/" ] && \ [ "$(stat -c '%u' "$DB_DIR")" = "0" ]; then chown clawmetry:clawmetry "$DB_DIR" fi From 0fdd257cf6af6a9c544c947aee7d56bf5b5c0fb7 Mon Sep 17 00:00:00 2001 From: Stephan Strittmatter Date: Fri, 27 Feb 2026 12:39:28 +0100 Subject: [PATCH 7/8] Update Dockerfile Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ccf2d5e..be37b98 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,8 +40,15 @@ COPY wsgi.py . COPY entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -# The container will start as root to allow entrypoint.sh to fix permissions. -# It will then use 'gosu' to step down to the 'clawmetry' user. +# SECURITY NOTE: +# The container intentionally starts as root so that entrypoint.sh can perform any +# required permission and ownership fixes (e.g., chown/chmod on mounted volumes) +# before the application runs. The script is expected to call 'gosu' as early as +# possible to drop privileges to the non-root 'clawmetry' user for the actual +# application process. +# This increases the attack surface if a vulnerability exists in the portion of +# entrypoint.sh that runs before 'gosu'. Keep that logic minimal, avoid parsing +# untrusted input there, and review any changes to entrypoint.sh carefully. EXPOSE 8900 From 82fa7fdbdf4306b7d758c926db4a2cc55ef9134b Mon Sep 17 00:00:00 2001 From: Stephan Strittmatter Date: Fri, 27 Feb 2026 12:40:50 +0100 Subject: [PATCH 8/8] Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/entrypoint.sh b/entrypoint.sh index 61181d3..7aded3f 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -43,7 +43,7 @@ while [ $# -gt 0 ]; do done # Ensure HOME is set correctly for the non-root user. -export HOME=/home/clawmetry +export HOME="${HOME:-/home/clawmetry}" export OPENCLAW_HOME="${OPENCLAW_HOME:-/home/clawmetry/.openclaw}" # The container starts as root. We ensure the data directory exists and