Skip to main content

Build #18 - Security Configuration: Non-Root User Setup

Date: 2025-10-27 Issue: Current Dockerfile runs all services as root (CRITICAL SECURITY ISSUE) Solution: Create non-root user with zsh/oh-my-zsh and configurable sudo access

Current State Analysis

❌ SECURITY ISSUES (Build #17 and earlier)

# Stage 6: Runtime Image
FROM node:20-slim # ← Runs as root by default

# All services run as root:
RUN apt-get update && apt-get install -y ... # ← root
COPY .claude /app/.claude # ← owned by root
CMD ["/app/start.sh"] # ← runs as root

Problems:

  1. ✗ All processes run with root privileges
  2. ✗ No user isolation
  3. ✗ Violates principle of least privilege
  4. ✗ Security risk if container compromised
  5. ✗ No zsh/oh-my-zsh for developer experience

Proposed Solution

✅ SECURE USER CONFIGURATION

# Stage 6: Runtime Image (NGINX + Node.js + Rust Binaries + Dev Tools)
# ============================================================================
FROM node:20-slim

# Install NGINX, system dependencies, development tools, sudo, and zsh
RUN apt-get update && apt-get install -y \
build-essential jq wget tree htop vim nano \
git-lfs ripgrep fzf tmux rsync zip unzip silversearcher-ag \
nginx curl ca-certificates \
python3 python3-pip python3-venv python3-dev \
sudo zsh \
&& rm -rf /var/lib/apt/lists/*

# Create non-root user 'coditect' with configurable sudo access
# UID/GID 1000 (standard for first user, matches most dev machines)
ARG USER_NAME=coditect
ARG USER_UID=1000
ARG USER_GID=1000
ARG ENABLE_SUDO=true

RUN groupadd --gid $USER_GID $USER_NAME \
&& useradd --uid $USER_UID --gid $USER_GID -m -s /bin/zsh $USER_NAME \
&& if [ "$ENABLE_SUDO" = "true" ]; then \
echo "$USER_NAME ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/$USER_NAME \
&& chmod 0440 /etc/sudoers.d/$USER_NAME; \
fi

# Install oh-my-zsh for coditect user
USER $USER_NAME
WORKDIR /home/$USER_NAME

RUN sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended \
&& echo 'export ZSH="$HOME/.oh-my-zsh"' >> ~/.zshrc \
&& echo 'ZSH_THEME="robbyrussell"' >> ~/.zshrc \
&& echo 'plugins=(git docker kubectl zsh-autosuggestions zsh-syntax-highlighting)' >> ~/.zshrc \
&& echo 'source $ZSH/oh-my-zsh.sh' >> ~/.zshrc \
&& echo 'export PATH="/usr/local/bin:$PATH"' >> ~/.zshrc

# Clone zsh plugins
RUN git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions \
&& git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting

# Switch back to root for installation tasks
USER root
WORKDIR /app

# Copy V5 Frontend build
COPY --from=frontend-builder /build/dist /app/v5-frontend

# Copy complete theia build
COPY --from=theia-builder /build /app/theia

# Copy all Rust binaries from build stages
COPY --from=v5-backend-builder /build/backend/target/release/api-server /usr/local/bin/coditect-v5-api
COPY --from=codi2-builder /build/codi2/target/release/codi2 /usr/local/bin/codi2
COPY --from=monitor-builder /build/file-monitor/target/release/examples/monitor /usr/local/bin/file-monitor

# Make binaries executable
RUN chmod +x /usr/local/bin/coditect-v5-api \
/usr/local/bin/codi2 \
/usr/local/bin/file-monitor

# Copy .claude directory and .coditect configs
COPY .claude /app/.claude
COPY archive/claude-code-initial-setup/.claude /app/.coditect
COPY .claude/agents /app/.coditect/agents-t2
COPY .claude/skills /app/.coditect/skills-t2
COPY .claude/commands /app/.coditect/commands-t2
COPY .claude/hooks /app/.coditect/hooks

# Create log directories and set ownership
RUN mkdir -p /app/.claude/logs /app/.coditect/logs \
/var/log/codi2 /var/log/monitor \
/workspace \
&& chown -R $USER_NAME:$USER_NAME /app /workspace /var/log/codi2 /var/log/monitor

# Install Node.js global packages (as root, accessible to all users)
RUN npm install -g --force \
typescript ts-node @types/node \
eslint prettier \
vite esbuild webpack webpack-cli \
pnpm yarn \
http-server nodemon concurrently \
react-devtools create-react-app create-next-app \
@google/gemini-cli

# Verify critical files
RUN echo "Checking for Coditect branding module..." && \
ls -la /app/theia/lib/browser/coditect-branding-frontend-module.js || \
echo "⚠️ WARNING: Coditect branding module not found!"

RUN echo "Checking for icon theme assets..." && \
find /app/theia -name "*seti*" -o -name "*vscode-icons*" | head -5 || \
echo "⚠️ WARNING: Icon theme assets not found!"

# Verify Rust binaries
RUN echo "Verifying Rust binaries..." && \
coditect-v5-api --version || echo "✓ V5 API binary ready" && \
codi2 --version && \
file-monitor --help || echo "✓ File monitor binary ready"

# Verify .coditect configs
RUN echo "Checking for .coditect configs..." && \
ls -la /app/.coditect/ && \
echo "Base configs:" && ls -1 /app/.coditect/agents/*.md | wc -l && echo "agents" && \
echo "T2 configs:" && ls -1d /app/.coditect/agents-t2/*.md /app/.coditect/skills-t2/*/ /app/.coditect/commands-t2/*.md 2>/dev/null | wc -l && echo "total files" || \
echo "⚠️ WARNING: .coditect configs incomplete!"

# Copy NGINX configuration
COPY nginx-combined.conf /etc/nginx/sites-available/default

# Expose port 80 (NGINX)
EXPOSE 80

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD curl -f http://localhost/health || exit 1

# Copy startup script and set ownership
COPY start-combined.sh /app/start.sh
RUN chmod +x /app/start.sh \
&& chown $USER_NAME:$USER_NAME /app/start.sh

# Switch to non-root user for running services
USER $USER_NAME
WORKDIR /workspace

CMD ["/app/start.sh"]

Updated start-combined.sh

#!/bin/bash
set -e

echo "Starting coditect-combined-v5 as user: $(whoami)"

# Start theia in background (using bundled backend for ESM compatibility)
echo "Starting theia IDE on port 3000..."
cd /app/theia
NODE_ENV=production \
THEIA_DEFAULT_PLUGINS=local-dir:/app/theia/plugins \
node lib/backend/main.js /workspace --hostname=0.0.0.0 --port=3000 &

THEIA_PID=$!
echo "theia started with PID $THEIA_PID"

# Wait for theia to be ready
echo "Waiting for theia to start..."
sleep 3
for i in {1..60}; do
if curl -sf http://localhost:3000/ > /dev/null 2>&1; then
echo "theia is ready!"
break
fi
if [ $i -eq 60 ]; then
echo "ERROR: theia failed to start within 60 seconds"
exit 1
fi
sleep 1
done

# Start CODI2 Monitoring System (as current user, NOT root)
echo "Starting CODI2 monitoring system..."
if [ -f /usr/local/bin/codi2 ]; then
/usr/local/bin/codi2 > /var/log/codi2/codi2.log 2>&1 &
CODI2_PID=$!
echo "CODI2 started with PID $CODI2_PID"
else
echo "WARNING: CODI2 binary not found at /usr/local/bin/codi2"
fi

# Start File Monitor (as current user, NOT root)
echo "Starting file monitor..."
if [ -f /usr/local/bin/file-monitor ]; then
/usr/local/bin/file-monitor > /var/log/monitor/monitor.log 2>&1 &
MONITOR_PID=$!
echo "File monitor started with PID $MONITOR_PID"
else
echo "WARNING: File monitor binary not found at /usr/local/bin/file-monitor"
fi

# Start NGINX in foreground (requires sudo since it binds to port 80)
echo "Starting NGINX (requires sudo for port 80)..."
sudo nginx -g "daemon off;"

Configuration Options

Environment Variables (K8s Deployment)

apiVersion: apps/v1
kind: StatefulSet
metadata:
name: coditect-combined
spec:
template:
spec:
containers:
- name: combined
image: us-central1-docker.pkg.dev/serene-voltage-464305-n2/coditect/coditect-combined:BUILD_ID
env:
# User configuration
- name: USER_NAME
value: "coditect" # Default: coditect
- name: USER_UID
value: "1000" # Default: 1000
- name: USER_GID
value: "1000" # Default: 1000
- name: ENABLE_SUDO
value: "true" # Default: true (set to "false" for production hardening)

# Shell configuration
- name: SHELL
value: "/bin/zsh"
- name: ZSH_THEME
value: "robbyrussell" # Can be changed to powerlevel10k, agnoster, etc.

Build Arguments (Docker Build Time)

# Default build (sudo enabled)
docker build -t coditect-combined .

# Production build (sudo disabled)
docker build \
--build-arg ENABLE_SUDO=false \
-t coditect-combined:prod .

# Custom user
docker build \
--build-arg USER_NAME=myuser \
--build-arg USER_UID=1001 \
--build-arg USER_GID=1001 \
-t coditect-combined:custom .

Security Benefits

✅ Improvements

  1. Principle of Least Privilege

    • Services run as non-root user coditect
    • Only NGINX needs sudo (for port 80)
    • Reduced attack surface
  2. Configurable Sudo Access

    • ENABLE_SUDO=true for development (default)
    • ENABLE_SUDO=false for production hardening
    • Explicit control via environment variable
  3. User Isolation

    • Dedicated UID/GID (1000 by default)
    • Matches standard developer machine UID
    • Proper file ownership
  4. Developer Experience

    • Zsh with oh-my-zsh
    • Auto-suggestions and syntax highlighting
    • Git, Docker, kubectl plugins
  5. Audit Trail

    • Clear user identity in logs
    • whoami shows coditect, not root
    • Better compliance for CODI2 monitoring

Migration Path

Phase 1: Add User Setup (Build #18)

  • Add user creation and zsh/oh-my-zsh installation
  • Keep services running as root temporarily
  • Verify user exists and shell works

Phase 2: Switch Services to User (Build #19)

  • Update start-combined.sh to use USER coditect
  • Test all services (theia, CODI2, MONITOR)
  • Verify NGINX with sudo

Phase 3: Production Hardening (Build #20)

  • Set ENABLE_SUDO=false in K8s deployment
  • Test without sudo access
  • Final security audit

Cross-Check Log

AspectCurrent (Build #17)Proposed (Build #18)Status
Userrootcoditect (UID 1000)✅ Fixed
Shellbashzsh + oh-my-zsh✅ Enhanced
SudoAlways availableConfigurable (ENABLE_SUDO)✅ Controlled
File Ownershiproot:rootcoditect:coditect✅ Isolated
NGINXrootsudo (only for port 80)✅ Minimal privilege
theiarootcoditect✅ Non-root
CODI2rootcoditect✅ Non-root
MONITORrootcoditect✅ Non-root
Logs/app/.claude/logs/var/log/codi2, /var/log/monitor✅ Organized
workspace/app/workspace✅ Dedicated

Testing Checklist

Build #18 Validation

# 1. Build image
gcloud builds submit --config cloudbuild-combined.yaml

# 2. Deploy to test pod
kubectl apply -f k8s-theia-statefulset.yaml

# 3. Verify user setup
kubectl exec -it coditect-combined-0 -- whoami
# Expected: coditect

# 4. Verify shell
kubectl exec -it coditect-combined-0 -- zsh -c 'echo $SHELL'
# Expected: /bin/zsh

# 5. Verify oh-my-zsh
kubectl exec -it coditect-combined-0 -- ls -la ~/.oh-my-zsh
# Expected: Directory exists with themes and plugins

# 6. Verify sudo access (if ENABLE_SUDO=true)
kubectl exec -it coditect-combined-0 -- sudo whoami
# Expected: root

# 7. Verify services running
kubectl exec -it coditect-combined-0 -- ps aux | grep -E "(theia|codi2|file-monitor|nginx)"
# Expected: All running as coditect (except nginx master as root)

# 8. Verify file ownership
kubectl exec -it coditect-combined-0 -- ls -la /app/.claude
# Expected: coditect:coditect

# 9. Test theia access
curl https://coditect.ai/theia
# Expected: 200 OK

# 10. Test terminal in theia
# Open theia → terminal → New terminal
# Expected: zsh prompt with oh-my-zsh theme

Rollback Plan

If issues occur:

# 1. Revert to Build #17
kubectl set image statefulset/coditect-combined \
combined=us-central1-docker.pkg.dev/serene-voltage-464305-n2/coditect/coditect-combined:f1866abe-dbc3-4e14-9d8b-60a0a8fbeed4

# 2. Verify pods restart
kubectl get pods -n coditect-app -w

# 3. Validate Build #17 working
curl https://coditect.ai/theia

Next Steps

  1. ✅ Document security configuration (this file)
  2. ⏳ Update dockerfile.combined-fixed with user setup
  3. ⏳ Update start-combined.sh for non-root execution
  4. ⏳ Update K8s manifests with environment variables
  5. ⏳ Build and deploy Build #18
  6. ⏳ Validate all services running as coditect
  7. ⏳ Test sudo access configuration
  8. ⏳ Document results

Status: Security configuration defined, ready for implementation