Terminal::stopping did child.kill() (error ignored) and then a blocking
child.wait() on the actix worker thread. Since the 0.3.11 privilege drop,
sessions run as `demo` and the server had no CAP_KILL, so kill() failed
with EPERM and wait() blocked forever. With two workers, exactly every
other request to :8082 then timed out — 5 days of readiness/liveness
flapping on socktop.io and orphaned restricted-shell/socktop processes
piling up in the pods.
- Signal the session's whole process group (portable-pty setsid()s the
child), not just the shell: a non-interactive bash defers signals while
a foreground command runs, so the old SIGHUP/SIGKILL to the shell alone
orphaned socktop anyway.
- Reap on a dedicated thread: SIGHUP, 2 s grace, SIGKILL, 10 s deadline,
then a blocking wait on that thread only. Signal failures are logged.
- Drop the pty handles before handing off the child.
- kubernetes/03-deployment.yaml: add CAP_KILL (sessions still lose every
cap via setpriv). CI never applies the manifest — it was patched live.
- tests/reaper_tests.rs pins the non-blocking return, process-group kill,
and SIGHUP→SIGKILL escalation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- CI now builds the image locally on the (arm64) runner and runs
scripts/verify-image-socktop-flags.sh before pushing: every --flag the
restricted/session shells pass must be documented by the socktop
binary actually installed in the image. Catches the 0.3.9 failure
class (cached apt layer shipping a pre-flag socktop) at build time.
- Manifest adds CHOWN/DAC_OVERRIDE/FOWNER alongside SETUID/SETGID:
with ALL dropped, uid 0 has no implicit file privilege and
prepare_demo_home crash-looped on the demo-owned 700 home dir.
Sessions still run with zero capabilities via setpriv.
- Agent liveness probe uses /proc instead of kill -0: without CAP_KILL
even root gets EPERM signalling the socktop-user agent, so the old
check false-alarmed in the pod logs.
- 0.3.11
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Image 0.3.9 baked socktop 1.60.1 because CI's registry layer cache reused
the apt-install layer from before the 1.60.2 release. 1.60.1 has no
--no-kill flag, so the restricted shell's invocation parsed it as the
positional websocket URL, breaking (and on overwrite, corrupting) the
local profile. Pinning the package version busts the cache and ties the
installed binary to the flags the restricted shell uses.
Also point the manifest's webterm container at the current tag so a
manual kubectl apply cannot roll the image back to 0.2.2.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Add init container to set up config files with correct ownership
- Run main container as socktop user (UID 100, GID 101) from the start
- Use fsGroup to ensure proper volume permissions
- Add emptyDir volume for /var/lib/socktop to avoid permission issues
- Create docker-entrypoint.sh wrapper to detect root vs non-root execution
- Root mode: uses init-config.sh for Docker/docker-compose
- Non-root mode: directly runs entrypoint.sh for K8s
- Update deployment command format to work with new entrypoint
This resolves 'Operation not permitted' errors when running in K8s
with security contexts that restrict user switching and ownership changes.