Skip to content

fix(webspaces): keep the capabilities package installs need when dropping capabilities - #11

Merged
NaysKutzu merged 1 commit into
MythicalLTD:masterfrom
redstonerthebest:fix/webspace-capabilities
Oct 7, 2026
Merged

NaysKutzu merged 1 commit into
MythicalLTD:masterfrom
redstonerthebest:fix/webspace-capabilities

Conversation

@redstonerthebest

@redstonerthebest redstonerthebest commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

What breaks

3123204 starts every WebSpace container with CapDrop = ["ALL"] and
SecurityOpt = ["no-new-privileges:true"]. Plates whose startup installs packages before serving
can no longer start: the built-in PHP bootstrap (Utils/WebSpaces/WebSpacePhpExtensions.cs) runs
apt-get install $PHPIZE_DEPS … + docker-php-ext-install as root and then apache2-foreground.
apt drops privileges to _apt and re-owns its list directories, so the container dies with

E: setgroups 0 failed - setgroups (1: Operation not permitted)
E: setegid 65534 failed ... E: seteuid 42 failed ...
W: chown to _apt:root of directory /var/lib/apt/lists/partial failed - SetupAPTPartialDirectory (1: Operation not permitted)
E: The repository 'http://deb.debian.org/debian trixie-updates Release' no longer has a Release file.

and restarts in a loop (Restarting (100)), while the reverse proxy answers 502.

Why this hits every node, not one installation

  • the bootstrap is daemon code, generated for any PHP WebSpace - nothing node-specific about it;
  • the shipped PHP plates (WordPress, Drupal, Joomla, Laravel, WHMCS) all use the plain
    php:8.3-apache image, which does not ship mysqli, pdo_mysql or opcache, so the
    NEED_INSTALL branch is always taken and apt is mandatory;
  • there is no config switch and no plate setting that avoids it - the hardening is unconditional.

Evidence

Live node, same image, the plate's startup script run verbatim in a throwaway container:

docker run --rm --entrypoint /bin/bash php:8.3-apache -c 'apt-get update -qq && echo APT_OK'
  -> APT_OK
docker run --rm --cap-drop ALL --security-opt no-new-privileges:true \
  --entrypoint /bin/bash php:8.3-apache -c 'apt-get update -qq && echo APT_OK'
  -> APT_FAIL
docker run --rm --cap-drop ALL --cap-add CHOWN --cap-add DAC_OVERRIDE --cap-add FOWNER \
  --cap-add KILL --cap-add NET_BIND_SERVICE --cap-add SETGID --cap-add SETUID --cap-add SYS_CHROOT \
  --security-opt no-new-privileges:true … bootstrap.sh
  -> APACHE_OK nach ~30s, extensions=2 (mysqli, pdo_mysql)

On the recreated PHP WebSpace after the change (defaults, no node-specific config):

docker inspect 6ac61ab3-… --format 'CapDrop={{json .HostConfig.CapDrop}} CapAdd={{json .HostConfig.CapAdd}} SecurityOpt={{json .HostConfig.SecurityOpt}}'
CapDrop=["ALL"] CapAdd=["CHOWN","DAC_OVERRIDE","FOWNER","KILL","NET_BIND_SERVICE","SETGID","SETUID","SYS_CHROOT"] SecurityOpt=["no-new-privileges:true"]

The change

Hardening stays on, but the container only loses the capabilities that no plate startup needs:

docker:
  webspace_security:
    drop_capabilities: true    # false -> Docker's default capability set
    capabilities: [...]        # kept while dropping; empty = drop everything (strongest)
    no_new_privileges: true

The capability set is resolved in a testable helper, WebSpaceRuntime.ApplyContainerSecurity,
so the behaviour is pinned by unit tests instead of living inside the container-creation code.

Verification

  • dotnet test -> Failed: 0, Passed: 408, Skipped: 0 (8 new tests in
    FeatherQuilld.Tests/Docker/WebSpaceContainerSecurityTests.cs)
  • deployed on a live node with 8 WebSpaces (5 static, 1 node, 1 astro, 1 PHP/WordPress):
    every domain and backend port back at its pre-update baseline, the PHP WebSpace serves again
    (20001 -> 301), SFTP matrix 4/4 on password-only, publickey-listed and publickey-with-key,
    zero error lines in the daemon log.

Alternatives considered

  • keep CapDrop=["ALL"] and ship a prebuilt PHP image - works, but the daemon's own bootstrap and
    every shipped PHP plate use the official image, so all of them would have to change, and the
    image has to be rebuilt per PHP version.
  • expose only a boolean escape hatch with the unsafe behaviour as default - does not fix the
    shipped plates on other installations.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…ping capabilities

Since 3123204 every WebSpace container starts with CapDrop=ALL and
no-new-privileges. That breaks plates whose startup installs packages before
serving: the built-in PHP bootstrap (WebSpacePhpExtensions) runs
`apt-get install $PHPIZE_DEPS ...` + `docker-php-ext-install` as root and then
Apache. apt drops privileges to _apt and re-owns its list directories, so the
container dies with

    E: setgroups 0 failed - setgroups (1: Operation not permitted)
    W: chown to _apt:root of directory /var/lib/apt/lists/partial failed
    E: The repository '... trixie-updates Release' no longer has a Release file.

and restarts in a loop (exit code 100, the proxy answers 502). Every PHP
WebSpace on the official php:8.3-apache image is affected - that is the shipped
WordPress, Drupal, Joomla, Laravel and WHMCS plates - on every node, with no
way to turn the hardening off.

Keep the hardening, but drop down to the capabilities those startups actually
need instead of everything, and make the whole thing configurable:

    docker:
      webspace_security:
        drop_capabilities: true    # false = Docker's default capability set
        capabilities: [...]        # capabilities kept; empty = drop all
        no_new_privileges: true

The capability selection lives in a testable helper
(WebSpaceRuntime.ApplyContainerSecurity) and is covered by unit tests.

Measured on a live node with a throwaway container of the same image running
the plate's startup script verbatim: with no capabilities apt fails and Apache
never binds; with the default set the container serves after ~30 s with mysqli
and pdo_mysql loaded.
@coderabbitai

coderabbitai Bot commented Oct 7, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 53cb3e68-055c-4e1e-81e7-04e09b190eed
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@NaysKutzu

Copy link
Copy Markdown
Member

Thank you for your contribution

@NaysKutzu
NaysKutzu merged commit 1a87045 into MythicalLTD:master Oct 7, 2026
1 check 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