Skip to content

release: clide v1–v3 — security hardening, Codex support, observability docs#49

Merged
itscooleric merged 13 commits intomainfrom
dev
Mar 8, 2026
Merged

release: clide v1–v3 — security hardening, Codex support, observability docs#49
itscooleric merged 13 commits intomainfrom
dev

Conversation

@itscooleric
Copy link
Owner

Summary

Merges the dev integration branch into main, shipping all work from the v1 (Developer Experience), v2 (Security Hardening), and v3 (Documentation) milestones.

What's included

PR What
#33 ttyd auth required by default, gh auth setup-git in entrypoint, Dockerfile HEALTHCHECK
#34 Gosu privilege-drop fix for firewall + compose hardening (cap_drop, no-new-privs, mem/cpu/pids limits)
#35 Codex CLI (OpenAI) support via claude-entrypoint.sh + new codex compose service
#36 Fix argument passthrough for ./clide codex command
#37 Architecture diagram (Mermaid), threat model (SECURITY.md), operational runbook (RUNBOOK.md), compatibility matrix
#38 CI: run PR checks on PRs targeting dev branch
#39 Chore: sync CI workflow fix from main into dev

Milestone status after this merge

  • ✅ v1: Developer Experience — complete
  • ✅ v2: Security Hardening — complete
  • ✅ v3: Documentation — complete
  • ⬜ v4: Agent Observability — planned (Mar 28)
  • ⬜ v5: Container Monitoring — planned (Apr 10)

Test plan

  • Build image from merged main — no Dockerfile errors
  • docker compose up clide — container starts healthy (HEALTHCHECK passes within 30s)
  • docker compose up clide without credentials → container errors at startup (ttyd auth required by default)
  • docker compose up clide with TTYD_USER/TTYD_PASS set → web terminal accessible with auth
  • docker compose up codex → Codex CLI available in container (requires OPENAI_API_KEY)
  • ./clide codex -- --help → passes args through correctly
  • Outbound connections to non-allowlisted hosts are rejected by firewall
  • docker inspect <container> --format '{{.State.Health.Status}}'healthy

🤖 Generated with Claude Code

itscooleric and others added 13 commits March 6, 2026 18:49
…CK (#33)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK

- (#4) Flip ttyd auth logic: credentials now required by default; container
  exits with a clear error if neither creds nor TTYD_NO_AUTH=true are set.
  Added TTYD_NO_AUTH=true as the explicit opt-out for unauthenticated access.
- (#14) Add `gh auth setup-git` to entrypoint so git push/fetch works
  seamlessly via GH_TOKEN without embedding tokens in remote URLs.
- (#10) Add HEALTHCHECK to Dockerfile — curls ttyd every 30s with a 10s
  startup grace period and 3 retries before marking the container unhealthy.
- Updated .env.example to document new ttyd auth defaults and TTYD_NO_AUTH.

Closes #4, #10, #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback on PR #33

- gh auth setup-git is now best-effort: skips gracefully if GH_TOKEN is
  absent or gh is unauthenticated, so the web terminal can still start
  without GitHub credentials.
- Fix TTYD auth precedence: credentials now take priority over
  TTYD_NO_AUTH=true; setting both is treated as a hard config error
  rather than silently disabling auth.
- HEALTHCHECK now respects TTYD_BASE_PATH so it doesn't produce false
  unhealthy status when a non-root base path is configured.
- HEALTHCHECK validates TTYD_PORT is numeric before use to prevent
  shell injection via environment variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: gosu privilege drop for firewall, compose hardening

Fixes the silent firewall no-op (#30) caused by iptables requiring
geteuid()==0 even with CAP_NET_ADMIN when the container runs as the
non-root clide user.

- (#30) Install gosu; set Dockerfile back to USER root so entrypoints
  start as root. All entrypoints (firewall.sh, claude-entrypoint.sh,
  entrypoint.sh) now apply any root-only setup first (iptables, config
  writes) then exec gosu clide to drop privileges before the workload.
  The firewall is now always effective when NET_ADMIN is present.
- (#30) Hardcode HOME_DIR=/home/clide in claude-entrypoint.sh to avoid
  resolving to /root when running as root; chown .claude.json to clide
  after writing.
- (#30) gosu clide gh auth setup-git in entrypoint.sh so gh config
  writes to clide's home, not root's.
- (#6) Add cap_drop: ALL, security_opt: no-new-privileges:true, and
  resource guardrails (mem_limit: 4g, cpus: 4.0, pids_limit: 512) to
  the docker-compose.yml base anchor. cap_add: NET_ADMIN is preserved
  for the firewall.

Closes #30, #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress DL3002 for intentional USER root before gosu drop

The last USER is root by design — entrypoints apply iptables rules as
root then exec gosu clide to drop privileges before the workload starts.
Suppress hadolint DL3002 with a pragma rather than papering over the
intentional pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
chore: sync main into dev (CI workflow fix)
* Initial plan

* feat: add Codex CLI (OpenAI) support

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

* fix: forward args to codex wrapper command (#36)

* fix: suppress DL3059 for consecutive npm RUN instructions

hadolint flags consecutive RUN instructions; suppressed with a pragma
since consolidating the npm installs would hurt layer caching.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: trigger checks after rebase

* chore: run PR checks on PRs targeting dev (#38)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK (#33)

* fix: ttyd auth default-on, gh credential helper, Dockerfile HEALTHCHECK

- (#4) Flip ttyd auth logic: credentials now required by default; container
  exits with a clear error if neither creds nor TTYD_NO_AUTH=true are set.
  Added TTYD_NO_AUTH=true as the explicit opt-out for unauthenticated access.
- (#14) Add `gh auth setup-git` to entrypoint so git push/fetch works
  seamlessly via GH_TOKEN without embedding tokens in remote URLs.
- (#10) Add HEALTHCHECK to Dockerfile — curls ttyd every 30s with a 10s
  startup grace period and 3 retries before marking the container unhealthy.
- Updated .env.example to document new ttyd auth defaults and TTYD_NO_AUTH.

Closes #4, #10, #14

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address Copilot review feedback on PR #33

- gh auth setup-git is now best-effort: skips gracefully if GH_TOKEN is
  absent or gh is unauthenticated, so the web terminal can still start
  without GitHub credentials.
- Fix TTYD auth precedence: credentials now take priority over
  TTYD_NO_AUTH=true; setting both is treated as a hard config error
  rather than silently disabling auth.
- HEALTHCHECK now respects TTYD_BASE_PATH so it doesn't produce false
  unhealthy status when a non-root base path is configured.
- HEALTHCHECK validates TTYD_PORT is numeric before use to prevent
  shell injection via environment variable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: gosu privilege drop for firewall + compose hardening (#34)

* fix: gosu privilege drop for firewall, compose hardening

Fixes the silent firewall no-op (#30) caused by iptables requiring
geteuid()==0 even with CAP_NET_ADMIN when the container runs as the
non-root clide user.

- (#30) Install gosu; set Dockerfile back to USER root so entrypoints
  start as root. All entrypoints (firewall.sh, claude-entrypoint.sh,
  entrypoint.sh) now apply any root-only setup first (iptables, config
  writes) then exec gosu clide to drop privileges before the workload.
  The firewall is now always effective when NET_ADMIN is present.
- (#30) Hardcode HOME_DIR=/home/clide in claude-entrypoint.sh to avoid
  resolving to /root when running as root; chown .claude.json to clide
  after writing.
- (#30) gosu clide gh auth setup-git in entrypoint.sh so gh config
  writes to clide's home, not root's.
- (#6) Add cap_drop: ALL, security_opt: no-new-privileges:true, and
  resource guardrails (mem_limit: 4g, cpus: 4.0, pids_limit: 512) to
  the docker-compose.yml base anchor. cap_add: NET_ADMIN is preserved
  for the firewall.

Closes #30, #6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress DL3002 for intentional USER root before gosu drop

The last USER is root by design — entrypoints apply iptables rules as
root then exec gosu clide to drop privileges before the workload starts.
Suppress hadolint DL3002 with a pragma rather than papering over the
intentional pattern.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: run PR checks on PRs targeting dev branch

Adds dev to the pull_request branch filter so CI runs on feature
branches merging into dev, not just main.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-trigger checks after workflow update

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
Co-authored-by: eric is cool <e@ericiscool.net>
Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ix (#37)

* docs: architecture diagram, threat model, runbook, compatibility matrix

- (#12) Add Mermaid architecture diagram to README showing host/container
  trust boundary, services, and allowlisted internet endpoints.
- (#8)  Add SECURITY.md — trust boundaries, attack surface breakdown,
  threat scenario table, and deployment hardening recommendations.
- (#11) Add RUNBOOK.md — health checks, start/stop, logs, rebuild
  procedures, credential rotation, firewall and web terminal
  troubleshooting, Docker health state reference.
- (#9)  Add compatibility matrix to README covering host OS, Docker
  version, CPU architecture, browser support, and firewall requirements.
- Link SECURITY.md and RUNBOOK.md from README "Additional docs" table.

Closes #8, #9, #11, #12

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci: re-trigger checks after workflow update

* fix: resolve markdown-lint failures in docs PR

- Rename duplicate 'Architecture' heading to 'CPU architecture' (MD024)
- Rename duplicate 'Egress firewall' heading to 'Egress firewall support' (MD024)
- Add 'text' language to ASCII art fenced block in SECURITY.md (MD040)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… 24.04 built-in ubuntu user

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
useradd -m in the Dockerfile already creates /home/clide with correct
ownership. The chown was also failing at runtime due to cap_drop: ALL
removing the CHOWN capability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cap_drop: ALL removes CAP_DAC_OVERRIDE, meaning root inside the container
is subject to normal file permission checks and cannot write to /home/clide.
Running the node script via gosu clide ensures it writes the config as the
correct user.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
cap_drop: ALL removes CAP_SETUID and CAP_SETGID, which gosu needs to
switch from root to the clide user in entrypoint scripts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
git clone/fetch/pull serve pack objects via objects.githubusercontent.com,
raw file access goes through raw.githubusercontent.com, and gh release
uploads use uploads.github.com — all previously blocked by the egress filter.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Closes #40. Adds rate-limited LOG target (10/min, burst 5) before REJECT rules for both IPv4 and IPv6. Logs appear under CLIDE-REJECT: prefix in dmesg/kern.log.
* fix: make pyenv writable by clide + pre-configure git safe.directory

Two quality-of-life improvements for agent dev sessions:

**1. Writable Python venv (/opt/pyenv)**
Transfer /opt/pyenv ownership to the clide user so the agent can
`pip install -r /workspace/<repo>/requirements.txt` on demand without a
container rebuild. Previously the venv was root-owned, causing permission
errors when installing workspace project dependencies (e.g. clem's PyYAML,
Flask, pydantic). pytest + ruff remain pre-installed as a baseline; all other
deps are installed per-session as needed.

**2. git safe.directory = * (eliminate per-session config noise)**
Volume-mounted workspace repos are often owned by the host UID, which
differs from clide (UID 1000). Git refuses to operate on these repos unless
`safe.directory` is configured, causing wasted tokens and boilerplate at the
start of every session. Setting `safe.directory = *` once at build time in
the clide user's gitconfig eliminates this entirely. This is appropriate for
a single-user dev sandbox.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: README — clide-owned venv, pip install pattern, git safe.directory, codex in diagram (#56)

* Initial plan

* docs: update README for clide-owned venv, pip install pattern, git safe.directory section

Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>

---------

Co-authored-by: itscooleric <itscooleric@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: itscooleric <25490177+itscooleric@users.noreply.github.com>
* feat: add persistent session memory across container restarts

- Add named volume (claude-data) for ~/.claude so auto memory, settings,
  and project learnings survive container restarts
- Seed CLAUDE.md from a template on first run if none exists in /workspace
- Template search order: project .claude/CLAUDE.md.template → bundled default
- Bundle a sensible default CLAUDE.md.template in the image

https://claude.ai/code/session_012jgpaUaGUgmGyoH9edLbyg

* fix: disable MD060 lint rule and remove trailing blank line in README

The markdownlint-cli2 action upgraded to markdownlint v0.40.0 which
introduced MD060 (table-column-style). Disable it to match existing
compact table style, and fix one MD012 violation (double blank line).

https://claude.ai/code/session_012jgpaUaGUgmGyoH9edLbyg

---------

Co-authored-by: Claude <noreply@anthropic.com>
@itscooleric itscooleric merged commit b82cd79 into main Mar 8, 2026
4 checks passed
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