Skip to content

Drop privileges to www-data by default for serve and run#246

Merged
diolektor merged 1 commit into
mainfrom
feature/default-www-data-privdrop
Jun 28, 2026
Merged

Drop privileges to www-data by default for serve and run#246
diolektor merged 1 commit into
mainfrom
feature/default-www-data-privdrop

Conversation

@diolektor

Copy link
Copy Markdown
Contributor

Summary

Makes oxphp serve and oxphp run drop OS privileges to www-data by default when no --user is given, so the official image no longer serves traffic as root out of the box. Explicit --user keeps its fail-fast semantics; --user=root is the opt-out to stay root.

Behavior

The drop field changes from Option<DropTarget> to a DropIntent enum (Explicit(DropTarget) | DefaultWwwData).

Start Default (no --user) Explicit --user=<spec>
root drop to www-data (or warn + stay root if www-data is absent) drop to target (or resolve error)
non-root skip silently — no switch hard error at startup — no switch
  • The default is best-effort: it never aborts startup, resolves www-data at drop time (not parse time), and skips when already non-root.
  • An explicit --user is fail-fast: it must start as root and resolves the spec at parse time.
  • The user-switching path runs only when the process started as root — geteuid() is checked in apply_drop for the default and again in drop_to for the explicit flag.
  • Applies to serve, explicit run, and implicit run. Timing (serve: after bind, before workers; run: before MINIT) is unchanged.

Breaking change

The official image (which ships www-data) now serves as www-data rather than root. Pass --user=root to restore the prior behavior, or --user=<name|uid[:gid]> to drop to a different account. Orchestrator-level drops (docker run --user, Compose user:, Kubernetes securityContext) are unaffected and take precedence — a non-root start makes the self-drop a no-op. Documented as a breaking entry in the CHANGELOG.

Tests

  • Parser: default-to-www-data for serve/run/implicit, --user=root opt-out, existing explicit cases.
  • Pure plan_default_drop: non-root skips, root-without-user stays root, root-with-user drops.

886 tests pass on host (cargo fmt/clippy --no-default-features/test --no-default-features).

Note: the real setuid path is exercised only in a root-capable Docker environment, not the host unit suite — worth a container smoke-test before release.

Security:
  - serve/run now drop OS privileges to www-data by default when started as root and the user resolves — the no-flag path no longer keeps serving as root. Best-effort: skips silently when already non-root, warns and stays root when www-data is absent, and never aborts startup. The switch is gated on geteuid()==0 at both the default (apply_drop) and explicit (drop_to) paths, so user-switching only happens from root.
  - Explicit --user keeps fail-fast semantics (must start as root, resolve at parse time, drop or abort). --user=root is the opt-out to stay root.

API:
  - Replace the drop field's Option<DropTarget> with a DropIntent enum (Explicit(DropTarget) | DefaultWwwData) on ServeOptions.drop_to and RunOptions.user; default is DefaultWwwData. resolve_user resolves a present --user eagerly and defers www-data resolution to drop time.
  - New privdrop::apply_drop(&DropIntent) replaces the direct drop_to calls in main; pure plan_default_drop(is_root, resolved) drives the decision and is unit-testable without a real setuid.

Docs:
  - CHANGELOG: breaking-change entry — serve/run default to www-data; --user=root restores prior behavior, explicit --user overrides.
  - cli.md / docker.md / installation.md (en/ru/zh): document the default best-effort drop, the three outcomes, the --user=root opt-out, and that the image starts as root but serve/run self-drop before serving; bare php CLI invocations do not self-drop. Regenerate llms-full.txt.

Tests:
  - Parser: default-to-www-data for serve/run/implicit, --user=root opt-out.
  - Planner: non-root skips, root-without-user stays root, root-with-user drops.

7 tests (7 unit).
@diolektor diolektor force-pushed the feature/default-www-data-privdrop branch from 0c89ba9 to 7f81780 Compare June 28, 2026 22:54
@diolektor diolektor merged commit 5ba5004 into main Jun 28, 2026
6 checks passed
@diolektor diolektor deleted the feature/default-www-data-privdrop branch June 28, 2026 22:57
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.

1 participant