Skip to content

security: add non-root users to token-spy and dashboard containers#431

Open
reo0603 wants to merge 1 commit intoLight-Heart-Labs:mainfrom
reo0603:security/container-non-root-users
Open

security: add non-root users to token-spy and dashboard containers#431
reo0603 wants to merge 1 commit intoLight-Heart-Labs:mainfrom
reo0603:security/container-non-root-users

Conversation

@reo0603
Copy link
Copy Markdown
Contributor

@reo0603 reo0603 commented Mar 19, 2026

Summary

Adds non-root user (UID 1000) to token-spy and dashboard Dockerfiles to follow container security best practices. Containers should not run as root to minimize attack surface.

Changes

token-spy

  • Add dreamer system user (UID 1000)
  • Set ownership of /app directory
  • Switch to non-root user before CMD

dashboard

  • Add dreamer user and group (UID/GID 1000)
  • Configure nginx for non-root operation:
    • Set ownership of nginx directories (html, cache, logs, conf.d)
    • Create and own nginx.pid file
    • Remove user directive from nginx.conf (runs as current user)
  • Switch to non-root user before ENTRYPOINT

Security Impact

Both containers now run as UID 1000 (dreamer) instead of root, reducing the attack surface if a container is compromised.

Testing

  • Dockerfiles build successfully
  • No functional changes to application logic
  • Follows Alpine Linux conventions (adduser/addgroup)

Related

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Needs Work (same issue as prior review)

The core Dockerfile changes are correct, but the upgrade-breaking permission bug we flagged previously is still present and unaddressed.

The problem

token-spy/compose.yaml mounts ./data/token-spy:/app/data as a bind mount. On existing installs, this host directory is owned by root:root (created by Docker running as root). The Dockerfile's chown -R dreamer:nogroup /app only affects the image layer — bind mounts override it. When the container starts as dreamer (UID 1000), it gets permission denied writing to /app/data.

This will break every existing token-spy install on upgrade with no clear error message.

How to fix

Add an entrypoint wrapper that checks/fixes ownership before dropping privileges:

COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
# Fix ownership of bind-mounted data directory
if [ "$(stat -c '%u' /app/data 2>/dev/null)" != "$(id -u)" ]; then
    chown -R dreamer:nogroup /app/data 2>/dev/null || true
fi
exec "$@"

Or at minimum, document the migration step: sudo chown -R 1000:1000 ./data/token-spy

What's good

  • Dashboard Dockerfile changes are thorough (nginx dirs, pid file, conf fixup)
  • UID 1000 is a reasonable choice

Co-Authored-By: Claude Opus 4.6 (1M context) noreply@anthropic.com

@Lightheartdevs
Copy link
Copy Markdown
Collaborator

The concept is right and this addresses a real finding from the security audit (M2 — containers running as root). Dashboard Dockerfile changes look solid.

Blocker: The token-spy container bind-mounts ./data/token-spy:/app/data. On existing installs this directory is owned by root. Your Dockerfile chown only affects the image layer, not bind mounts — so running as UID 1000 will get permission denied on /app/data and break existing installs on upgrade.

Fix: Add an entrypoint wrapper that fixes ownership at runtime before dropping to the dreamer user, e.g.:

COPY entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/sh
chown -R dreamer:dreamer /app/data 2>/dev/null || true
exec su-exec dreamer "$@"

Or document a migration step for existing users. Either way, the bind mount ownership needs to be handled.

Copy link
Copy Markdown
Collaborator

@Lightheartdevs Lightheartdevs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Audit Review

Dashboard Dockerfile: Looks good

  • Creates user/group (UID/GID 1000)
  • Chowns all nginx runtime directories (html, cache, log, conf.d, pid)
  • Removes user directive from nginx.conf via sed -i '/^user/d'
  • Correct approach, no concerns

Token-spy Dockerfile: Bind-mount permission bug still present

The same issue flagged in the March 20 review remains unresolved.

chown -R dreamer:nogroup /app in the Dockerfile only affects the image layer. At runtime, ./data/token-spy:/app/data mounts the host directory over /app/data. On existing installs, this directory is root:root (created by Docker running as root). The container running as UID 1000 will get EACCES writing to the database.

Fix options:

  1. Add an entrypoint script that checks ownership and runs chown if needed (requires the entrypoint to start as root, then exec gosu dreamer ...)
  2. Document a migration step: chown -R 1000:65534 data/token-spy/ before upgrading
  3. Use a named volume instead of a bind mount (Docker manages ownership)

Without one of these, merging this PR will break token-spy on every existing install.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants