Skip to content

security: harden production Docker image#1921

Closed
Michaelyklam wants to merge 1 commit into
nesquena:masterfrom
Michaelyklam:fix/issue-1908-docker-hardening
Closed

security: harden production Docker image#1921
Michaelyklam wants to merge 1 commit into
nesquena:masterfrom
Michaelyklam:fix/issue-1908-docker-hardening

Conversation

@Michaelyklam
Copy link
Copy Markdown
Contributor

Thinking Path

  • Issue security(docker): drop sudo+NOPASSWD from production image, replace chmod 777 with 700 #1908 identified two production Docker hardening gaps: passwordless sudo escalation and world-writable init scratch paths.
  • The existing image used a sudo-capable staging user so runtime init could remap UID/GID and prepare bind mounts.
  • Preserving UID/GID remapping still matters for Docker bind mounts, so this keeps a narrow root init phase but removes sudo from the production image and drops to hermeswebui before starting the server.
  • The init scratch state now uses owner-only permissions, and the Docker guide documents the resulting single-tenant production threat model.

What Changed

  • Removed sudo, %sudo ALL=(ALL) NOPASSWD:ALL, and the hermeswebuitoo sudo-capable staging user from Dockerfile.
  • Reworked docker_init.bash so privileged setup runs directly in an explicit root init block, prepares /app, /workspace, /uv_cache, then re-execs as hermeswebui without sudo.
  • Replaced world-writable /tmp/hermeswebui_init handling with umask 0077, 0700 scratch directory permissions, and 0600 scratch files.
  • Updated Docker structural tests for the no-sudo root-init model and added issue security(docker): drop sudo+NOPASSWD from production image, replace chmod 777 with 700 #1908 regression tests.
  • Added docs/docker.md production image security model notes.

Why It Matters

  • A shell gained through the WebUI runtime no longer has a passwordless sudo path to root inside the production container.
  • Init scratch files no longer allow unrelated container users to modify UID/GID/env handoff state.
  • Existing bind-mount UID/GID alignment behavior is preserved without shipping sudo in production.

Verification

  • python -m pytest tests/test_issue1908_docker_hardening.py tests/test_issue357.py tests/test_issue569_579.py tests/test_v050260_docker_invariants.py tests/test_issue926_hindsight_docker_dependency.py -q → 45 passed
  • bash -n docker_init.bash
  • git diff --check
  • env -u HERMES_CONFIG_PATH -u HERMES_WEBUI_HOST /home/michael/.hermes/hermes-agent/venv/bin/python -m pytest tests/ -q → 4887 passed, 4 skipped, 3 xpassed, 1 warning, 8 subtests passed
  • docker run --rm python:3.12-slim sh -c 'command -v su && command -v groupmod && command -v usermod && command -v chown' → confirmed the base image provides the non-sudo primitives used by init

Risks / Follow-ups

  • The container still starts docker_init.bash as root for UID/GID and bind-mount ownership preparation, then starts the WebUI server as hermeswebui. This keeps existing Docker permission behavior while removing runtime sudo escalation.
  • A local docker build could not complete on this host because the installed Docker lacks the buildx/BuildKit component required by the existing COPY --chmod Dockerfile instruction.
  • No browser/UI media included because this is Docker/security hardening only.

Closes #1908

Model Used

OpenAI Codex gpt-5.5 via Hermes CLI, with terminal/file tools for implementation and verification.

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Shipped in v0.51.29 via the Release F batch (release PR #1934, merge SHA 596c6b3). Your work is now live on master.

🚀 Release notes: https://github.com/nesquena/hermes-webui/releases/tag/v0.51.29

Thanks for the contribution!

pull Bot pushed a commit to soitun/hermes-webui that referenced this pull request May 8, 2026
pull Bot pushed a commit to soitun/hermes-webui that referenced this pull request May 8, 2026
…persistence + scroll/lineage fixes + i18n cleanup)

Six-PR contributor batch:
- PR nesquena#1919 (franksong2702): Persist login rate limit attempts (closes nesquena#1910)
- PR nesquena#1920 (franksong2702): Remove dead Kanban start i18n key
- PR nesquena#1921 (Michaelyklam): Production Docker image hardening (closes nesquena#1908)
- PR nesquena#1926 (ai-ag2026): Prevent chat scroll resets after final render
- PR nesquena#1927 (ai-ag2026): Preserve viewport when loading older messages
- PR nesquena#1930 (ai-ag2026): Collapse stale compression sidebar segments

Tests: 4947 → 4960 (+13 net new). Browser API harness all-green.
Opus advisor: SHIP-READY. CHANGELOG conflict on nesquena#1919 auto-resolved
during stage rebase (CHANGELOG took ours strategy).
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.

security(docker): drop sudo+NOPASSWD from production image, replace chmod 777 with 700

2 participants