From 1f79bd8653b0d293dfd3b77a37b956f0d4113995 Mon Sep 17 00:00:00 2001 From: Ashfaq <105435085+Ashfaqbs@users.noreply.github.com> Date: Wed, 9 Sep 2026 09:57:07 +0530 Subject: [PATCH] fix(docker): actually run all seven servers as their non-root user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The three Python images (fetch, git, time) create an app user and --chown the venv to it, but never switch to it with USER, so the useradd/--chown lines were dead and every process still ran as root. The four Node images inherit root from node:22-alpine despite that base image already shipping a non-root node user. Adding USER app/USER node alone isn't enough, though: /root defaults to 700, and each Python venv's interpreter is a symlink into /root/.local/share/uv/python/.../bin, resolved on every startup, not just at build time — so switching users without loosening that permission breaks the interpreter outright. Fixed by chmod o+rx /root before the USER switch, verified by actually building and running all three Python images (git also needed a wildcard safe.directory, since git now runs as a different uid than whatever host directory gets bind-mounted in). Verified by building and exercising each image (id, plus a real MCP initialize handshake; for git, a real git_status against a bind-mounted repo owned by a different uid). The four Node images could not be build-tested locally due to an unrelated, pre-existing npm install failure reproducible on main (filed as #4782); the USER node addition mirrors the exact fix already verified working on the Python images and matches the pattern Node's own official images document, and for memory specifically also chowns dist/ first since that's where the knowledge graph JSONL persists by default. Fixes #4741 --- src/everything/Dockerfile | 2 ++ src/fetch/Dockerfile | 8 ++++++++ src/filesystem/Dockerfile | 2 ++ src/git/Dockerfile | 14 ++++++++++++++ src/memory/Dockerfile | 8 ++++++++ src/sequentialthinking/Dockerfile | 2 ++ src/time/Dockerfile | 8 ++++++++ 7 files changed, 44 insertions(+) diff --git a/src/everything/Dockerfile b/src/everything/Dockerfile index 6729298b06..605e3b7b90 100644 --- a/src/everything/Dockerfile +++ b/src/everything/Dockerfile @@ -19,4 +19,6 @@ ENV NODE_ENV=production RUN npm ci --ignore-scripts --omit-dev +USER node + CMD ["node", "dist/index.js"] \ No newline at end of file diff --git a/src/fetch/Dockerfile b/src/fetch/Dockerfile index d2a4504931..c0db52b5e0 100644 --- a/src/fetch/Dockerfile +++ b/src/fetch/Dockerfile @@ -26,6 +26,12 @@ FROM python:3.12-slim-bookworm WORKDIR /app COPY --from=uv /root/.local /root/.local +# /root defaults to 700, which blocks any non-root user from even traversing +# into /root/.local — and the venv's own python binary is a symlink into +# /root/.local/share/uv/python/.../bin/python3.11, resolved at every startup, +# not just at build time. Without this, switching to a non-root USER below +# breaks the interpreter itself, not just file access. +RUN chmod o+rx /root RUN if ! id -u app >/dev/null 2>&1; then \ useradd -rUM -s /usr/sbin/nologin app; \ @@ -35,5 +41,7 @@ COPY --from=uv --chown=app:app /app/.venv /app/.venv # Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" +USER app + # when running the container, add --db-path and a bind mount to the host's db file ENTRYPOINT ["mcp-server-fetch"] diff --git a/src/filesystem/Dockerfile b/src/filesystem/Dockerfile index 418b140056..c8d3e0caeb 100644 --- a/src/filesystem/Dockerfile +++ b/src/filesystem/Dockerfile @@ -22,4 +22,6 @@ ENV NODE_ENV=production RUN npm ci --ignore-scripts --omit-dev +USER node + ENTRYPOINT ["node", "/app/dist/index.js"] \ No newline at end of file diff --git a/src/git/Dockerfile b/src/git/Dockerfile index 4af41612c7..ae7c6e1804 100644 --- a/src/git/Dockerfile +++ b/src/git/Dockerfile @@ -29,6 +29,12 @@ RUN apt-get update && apt-get install -y git git-lfs && rm -rf /var/lib/apt/list WORKDIR /app COPY --from=uv /root/.local /root/.local +# /root defaults to 700, which blocks any non-root user from even traversing +# into /root/.local — and the venv's own python binary is a symlink into +# /root/.local/share/uv/python/.../bin/python3.11, resolved at every startup, +# not just at build time. Without this, switching to a non-root USER below +# breaks the interpreter itself, not just file access. +RUN chmod o+rx /root RUN if ! id -u app >/dev/null 2>&1; then \ useradd -rUM -s /usr/sbin/nologin app; \ @@ -38,5 +44,13 @@ COPY --from=uv --chown=app:app /app/.venv /app/.venv # Place executables in the environment at the front of the path ENV PATH="/app/.venv/bin:$PATH" +# The container's job is exclusively to operate on whichever repo is bind-mounted +# in, so there's no meaningful "wrong owner" for git to protect against here the +# way there is on a shared host; without this, git refuses to touch a repo owned +# by a different uid than the app user once we stop running as root below. +RUN git config --system --add safe.directory '*' + +USER app + # when running the container, add --db-path and a bind mount to the host's db file ENTRYPOINT ["mcp-server-git"] diff --git a/src/memory/Dockerfile b/src/memory/Dockerfile index 2f85d0cfa2..1490f31e64 100644 --- a/src/memory/Dockerfile +++ b/src/memory/Dockerfile @@ -21,4 +21,12 @@ WORKDIR /app RUN npm ci --ignore-scripts --omit-dev +# Without MEMORY_FILE_PATH set, the server persists its knowledge graph next +# to its own compiled entrypoint (dist/memory.jsonl) — dist was just copied in +# as root, so the app user needs ownership of it before it can create/update +# that file, including when a named volume (see README) mounts over dist/. +RUN chown -R node:node /app/dist + +USER node + ENTRYPOINT ["node", "dist/index.js"] \ No newline at end of file diff --git a/src/sequentialthinking/Dockerfile b/src/sequentialthinking/Dockerfile index f1a88195bc..db9afb7eb8 100644 --- a/src/sequentialthinking/Dockerfile +++ b/src/sequentialthinking/Dockerfile @@ -21,4 +21,6 @@ WORKDIR /app RUN npm ci --ignore-scripts --omit-dev +USER node + ENTRYPOINT ["node", "dist/index.js"] diff --git a/src/time/Dockerfile b/src/time/Dockerfile index c92873277d..f8ede3f8cc 100644 --- a/src/time/Dockerfile +++ b/src/time/Dockerfile @@ -26,6 +26,12 @@ FROM python:3.12-slim-bookworm WORKDIR /app COPY --from=uv /root/.local /root/.local +# /root defaults to 700, which blocks any non-root user from even traversing +# into /root/.local — and the venv's own python binary is a symlink into +# /root/.local/share/uv/python/.../bin/python3.11, resolved at every startup, +# not just at build time. Without this, switching to a non-root USER below +# breaks the interpreter itself, not just file access. +RUN chmod o+rx /root RUN if ! id -u app >/dev/null 2>&1; then \ useradd -rUM -s /usr/sbin/nologin app; \ @@ -38,5 +44,7 @@ ENV PATH="/app/.venv/bin:$PATH" # Set the LOCAL_TIMEZONE environment variable ENV LOCAL_TIMEZONE=${LOCAL_TIMEZONE:-"UTC"} +USER app + # when running the container, add --local-timezone and a bind mount to the host's db file ENTRYPOINT ["mcp-server-time", "--local-timezone", "${LOCAL_TIMEZONE}"]