Gateway container runs its internet-facing server as root
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 1-2 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- docker, docker-compose, node.js, typescript
- Domain
- infrastructure, security
Research direction
Start with apps/gateway/Dockerfile:27-37, apps/gateway/scripts/run.sh, and the gateway bind mount in docker-compose.yaml; compare the API image's USER node setting. Check how the gateway's SQLite directory and template database are created and writable, then verify the built image with docker compose exec gateway id. Done means it runs unprivileged and a remote assignment still completes with both a pre-existing root-owned ./sqlite and no ./sqlite directory.
Written by the indexing model from the issue text.
Description
The gateway image runs its Node server as root. Its runner stage has no USER instruction, so run.sh and node ./dist/main.js run as uid 0. The api image drops to USER node. The gateway is the one service that faces the public internet directly: compose publishes it on GATEWAY_PORT, and anonymous participants open its links. Its own logs in #1203 show scanners probing it. Any code-execution bug in the gateway or its dependencies (Express, the SSR renderer, Prisma's engine, Cap.js) would therefore run as root in the container. As root it can write anywhere in the image, including /app/dist, which it serves. It also owns the bind-mounted ./sqlite on the host and gets root's default capabilities. USER node was removed in 7c404229e ("run gateway docker as root", Feb 2024), most likely because a root-owned ./sqlite bind mount was not writable by node. That was a workaround, and nothing in apps/gateway/AGENTS.md records it as a decision.
Where
apps/gateway/Dockerfile:27-37:
# RUN SERVER
FROM base AS runner
COPY apps/gateway/scripts/run.sh run.sh
COPY --from=installer /app/sqlite/gateway.db /app/gateway.tmpl.db
# ...
RUN echo '{ "type": "module" }' > package.json
RUN echo '{ "type": "module" }' > /runtime/package.json
CMD [ "./run.sh" ]
Compare apps/api/Dockerfile:32, which has USER node. The gateway runs as root because docker-compose.yaml bind-mounts ./sqlite:/app/sqlite, and Docker creates that directory as root:root on a fresh Linux host.
Reproduce
- Run
docker compose up -d gateway. - Run
docker compose exec gateway id.
Actual: uid=0(root) gid=0(root) groups=0(root)…. The published image confirms it. docker buildx imagetools inspect ghcr.io/douglasneuroinformatics/open-data-capture-gateway:latest --format '{{json (index .Image "linux/amd64").Config.User}}' prints "", meaning root, while the same command for open-data-capture-api:latest prints "node".
Expected: the gateway server runs as an unprivileged user, as the api does.
Tests
No unit or Playwright test runs the production image. The e2e suite starts the gateway's dev:test directly. Verify the fix in the built image. docker compose exec gateway id should report node, a remote assignment should still complete end to end, which proves the server can still write /app/sqlite/gateway.db, and this should hold both with a pre-existing root-owned ./sqlite and with no ./sqlite at all.
Suggested fix
Keep the entrypoint as root only long enough to fix ownership, then drop privileges. In run.sh, run chown -R node:node /app/sqlite and the template copy, then exec setpriv --reuid=node --regid=node --init-groups node ./dist/main.js. setpriv ships in the Debian base. Alternatively, add a one-shot init service in compose that chowns ./sqlite, and set USER node in the image. Port 80 stays bindable without root, as it already is for the api.
- Dominant language
- TypeScript
- Stars
- 119
- Forks
- 19
- Avg merge
- 1d 2h
- Merged PRs (30d)
- 56
Getting set up
- Ships a Dockerfile or Docker Compose file
- No pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from DouglasNeuroInformatics/OpenDataCapture
-
Area: Playground Bug Difficulty: Low Good First Issue Priority: Low
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
DouglasNeuroInformatics/OpenDataCapture#1805 ·
Maintainers usually reply within 1 day
-
Area: Instruments Bug Difficulty: Low Priority: Low
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
DouglasNeuroInformatics/OpenDataCapture#1801 ·
Maintainers usually reply within 1 day
-
Area: Instruments Bug Difficulty: Low Good First Issue Priority: Low
Difficulty 2/5 1-3 hours Newbie friendliness 85/100
DouglasNeuroInformatics/OpenDataCapture#1800 ·
Maintainers usually reply within 1 day
-
Area: Instruments Bug Difficulty: Low Good First Issue Priority: Low
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
DouglasNeuroInformatics/OpenDataCapture#1799 ·
Maintainers usually reply within 1 day
-
Area: Instruments Bug Difficulty: Low Performance Priority: Medium
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
DouglasNeuroInformatics/OpenDataCapture#1795 ·
Maintainers usually reply within 1 day
All issues in DouglasNeuroInformatics/OpenDataCapture
Similar issues
-
[Bug]: Server git tests sign fixture commits with the developer's key when run from the repo rootOpen
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
melgarafael/DeskcommCRM#2657 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 Under an hour Newbie friendliness 85/100
MystenLabs/MemWal#1163 · 2 comments ·
Maintainers usually reply within 1 day
-
Mondriaan
Difficulty 1/5 Under an hour Newbie friendliness 88/100
knaw-huc/textannoviz#709 ·
Maintainers usually reply within 1 day
-
billion-context-pi
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
ranxianglei/billion-context#2521 · 3 comments ·
Maintainers usually reply within 1 day