hs-php-engine SANDBOX SELF-AUDIT timestamp : 2026-07-09T19:00:14+00:00 hostname : 30c67b7511cf php : 8.4.23 (id=80423) sapi : fpm-fcgi uid:gid : 10001:10001 (effective) cwd : /files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd __DIR__ : /files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd PASS = boundary held / attack blocked = SECURE ; FAIL = boundary broken = VULNERABLE =============================================================================== [1] Identity & Privilege =============================================================================== [PASS] Not running as root (euid) -- euid=10001 (non-zero) [PASS] Effective UID is tenant 10001 -- euid=10001 [PASS] Effective GID is tenant 10001 -- egid=10001 [PASS] No real/effective id drift -- ruid=10001 euid=10001 rgid=10001 egid=10001 [INFO] POSIX identity -- name=app uid=10001 gid=10001 home=/home/app shell=/usr/sbin/nologin [PASS] No privileged supplementary groups -- groups=10001 [INFO] Script owner (get_current_user) -- root [INFO] Capability self-read unavailable -- /proc unreadable (open_basedir jail) and shell disabled — cannot introspect CapEff via /proc; capabilities are ACTIVELY probed below instead [INFO] Low-port bind allowed (expected under gVisor) -- bound 127.0.0.1:80 without CAP_NET_BIND_SERVICE — gVisor/Docker permit unprivileged low ports (no ip_unprivileged_port_start gate); NOT a capability leak. CapDrop is verified via chown/mknod [PASS] CAP_CHOWN dropped -- chown() to uid 1 refused (no CAP_CHOWN) [PASS] CAP_MKNOD dropped -- posix_mknod() of a char device refused (no CAP_MKNOD / nodev mount) [INFO] no-new-privileges -- only exploitable via an exec of a setuid binary; every exec primitive is disabled, so no-new-privileges:true is asserted host-side (docker SecurityOpt) and cannot be positively exercised from tenant userland [PASS] Setuid binaries unreachable -- open_basedir jail blocks stat() of /bin & /usr/bin — tenant cannot inspect, let alone invoke, any setuid binary [PASS] No process-spawn primitive -- exec/system/shell_exec/passthru/proc_open/popen/pcntl_exec all disabled — cannot launch su/sudo/setuid binaries [PASS] sudo -n unavailable -- shell exec disabled — tenant cannot invoke sudo at all [PASS] Shell 'whoami' blocked -- shell_exec disabled — tenant cannot run 'whoami' [PASS] Shell 'id' blocked -- shell_exec disabled — tenant cannot run 'id' [PASS] /etc/shadow unreadable -- open_basedir jail + non-root — cannot read password hashes to crack [PASS] /etc/passwd not writable -- open_basedir jail + read-only rootfs — cannot inject a uid-0 account =============================================================================== [2] PHP Configuration Lockdown (pool www.conf + zz-hardening.ini) =============================================================================== [INFO] disable_functions -- the 13 listed dangerous functions must all be non-callable [INFO] disable_functions ini -- exec,passthru,shell_exec,system,proc_open,popen,proc_nice,proc_get_status,pcntl_exec,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_signal,pcntl_signal_dispatch,pcntl_async_signals,pcntl_alarm,pcntl_setpriority,pcntl_getpriority,posix_kill,posix_setsid,posix_setpgid,posix_mkfifo,dl,mail,symlink,link [PASS] disable_functions: exec blocked -- not callable [PASS] disable_functions: passthru blocked -- not callable [PASS] disable_functions: shell_exec blocked -- not callable [PASS] disable_functions: system blocked -- not callable [PASS] disable_functions: proc_open blocked -- not callable [PASS] disable_functions: popen blocked -- not callable [PASS] disable_functions: proc_nice blocked -- not callable [PASS] disable_functions: proc_get_status blocked -- not callable [PASS] disable_functions: pcntl_exec blocked -- not callable [PASS] disable_functions: dl blocked -- not callable [PASS] disable_functions: mail blocked -- not callable [PASS] disable_functions: symlink blocked -- not callable [PASS] disable_functions: link blocked -- not callable [PASS] disable_functions complete -- all 13 dangerous functions non-callable [PASS] allow_url_fopen = Off -- remote wrappers disabled [PASS] allow_url_include = Off -- remote wrappers disabled [PASS] expose_php = Off -- PHP version banner / X-Powered-By suppressed [PASS] open_basedir set -- /files/sites/7959978163:/tmp [PASS] open_basedir jail -- includes per-tenant /files/sites/ [INFO] open_basedir /tmp -- writable scratch present in jail [INFO] jail permits own dir -- 3 entries in /files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd (open_basedir is not blanket-denying) [INFO] open_basedir enforcement -- reads outside the jail must be blocked (@-suppressed; block == PASS) [PASS] read blocked: /etc/passwd -- open_basedir / read-only-rootfs / gVisor denied access [PASS] read blocked: /etc/shadow -- open_basedir / read-only-rootfs / gVisor denied access [PASS] read blocked: /proc/self/environ -- open_basedir / read-only-rootfs / gVisor denied access [PASS] open_basedir traversal blocked -- '..' escape to /etc/passwd denied [PASS] sibling dir blocked: /files/sites -- cannot enumerate other tenants [INFO] clear_env -- worker env must be minimal; no host/master secrets (clear_env=yes) [INFO] process env vars -- 38 [USER, HOME, HTTP_CF_DEVICE_TYPE, HTTP_X_FORWARDED_PROTO, HTTP_CF_VISITOR, HTTP_CDN_LOOP, HTTP_ACCEPT, HTTP_CF_RAY, HTTP_CF_IPCOUNTRY, HTTP_CF_CONNECTING_IP, HTTP_X_REAL_IP, HTTP_CONNECTION, HTTP_X_FORWARDED_FOR, HTTP_USER_AGENT, HTTP_HOST, PATH_INFO, SCRIPT_FILENAME, REDIRECT_STATUS, SERVER_NAME, SERVER_PORT, SERVER_ADDR, REMOTE_USER, REMOTE_PORT, REMOTE_ADDR, SERVER_SOFTWARE, GATEWAY_INTERFACE, HTTPS, REQUEST_SCHEME, SERVER_PROTOCOL, DOCUMENT_ROOT, DOCUMENT_URI, REQUEST_URI, SCRIPT_NAME, CONTENT_LENGTH, CONTENT_TYPE, REQUEST_METHOD, QUERY_STRING, FCGI_ROLE] [PASS] $_ENV empty -- no host variables imported into $_ENV [PASS] no secret env vars -- no PASSWORD/SECRET/TOKEN-style host vars in the worker environment [PASS] no infra env vars -- no MONGO/DOCKER/AWS-style host vars leaked [PASS] host-only vars absent -- getenv() returns false for known host/secret variable names [INFO] getenv(PATH) -- unset [INFO] resource limits -- per-request caps (the per-container cgroup memory cap is the hard ceiling) [PASS] memory_limit = 128M -- matches expected 128M [PASS] post_max_size = 16M -- matches expected 16M [PASS] upload_max_filesize = 16M -- matches expected 16M [PASS] max_execution_time = 30 -- matches expected 30 [INFO] OPcache -- uploaded tenant code must revalidate; JIT off to shrink the untrusted-code surface [PASS] opcache.jit disabled (ini) -- opcache.jit='disable' [PASS] opcache.validate_timestamps on (ini) -- uploaded tenant code is revalidated [PASS] validate_timestamps (opcache cfg) -- true [INFO] opcache.jit (opcache cfg) -- 'disable' [PASS] opcache JIT inactive (runtime) -- enabled=false on=false [INFO] temp & session paths -- must point at tenant-writable tmpfs, not shared host paths [PASS] session.save_path = /var/lib/php/sessions -- matches expected /var/lib/php/sessions [PASS] upload_tmp_dir = /tmp -- matches expected /tmp [PASS] sys_temp_dir = /tmp -- matches expected /tmp [INFO] security.limit_extensions -- FPM pool directive (.php) — not exposed to ini_get; enforced by FPM, not by this script =============================================================================== [3] Filesystem & Mounts =============================================================================== [INFO] Identity resolved -- siteDir=/files/sites/7959978163 userId=7959978163 (this is our open_basedir jail root) [PASS] rootfs read-only: / -- write blocked (readonly rootfs and/or open_basedir jail) [PASS] rootfs read-only: /etc -- write blocked (readonly rootfs and/or open_basedir jail) [PASS] rootfs read-only: /usr -- write blocked (readonly rootfs and/or open_basedir jail) [PASS] rootfs read-only: /var/www/html -- write blocked (readonly rootfs and/or open_basedir jail) [PASS] Identity mount read-only -- /files/sites/7959978163 rejected write (:ro bind enforced) [INFO] Identity mount readable -- /files/sites/7959978163 is readable (expected — :ro means read-only, not no-access) [PASS] /tmp writable -- tenant scratch present (34 B written) [PASS] /tmp payload cannot be exec'd (funcs) -- exec/shell functions disabled; is_executable()=true [INFO] /tmp noexec expectation -- the mode bit may read executable, but tmpfs is mounted noexec so the kernel denies exec (confirmed via /proc/mounts below if readable) [INFO] /run not userland-writable -- outside open_basedir, so tenant file writes are denied (expected); /run is a rw,noexec tmpfs for runtime state, not tenant scratch [INFO] session dir not userland-writable -- /var/lib/php/sessions — outside open_basedir, so tenant file_put_contents is denied (expected); the tmpfs is rw for PHP's session handler only. NOTE: this tmpfs intentionally omits noexec (A8), tolerable only because every exec primitive is disabled [INFO] /tmp mode -- 1777 (expected 1777) [PASS] /proc/mounts blocked -- open_basedir jail hides /proc (expected) [PASS] No docker socket: /var/run/docker.sock -- absent/blocked (expected — host access is via docker-socket-proxy) [PASS] No docker socket: /run/docker.sock -- absent/blocked (expected — host access is via docker-socket-proxy) [PASS] Cross-tenant listing blocked -- /files/sites unreadable (open_basedir jail — cannot enumerate sibling tenants) [PASS] Jail blocks /etc/passwd -- read denied by open_basedir (expected) [PASS] Jail blocks /etc/shadow -- read denied (open_basedir and/or file perms) =============================================================================== [4] Resource Limits & cgroup Enforcement (memory / swap / cpu / pids) =============================================================================== [INFO] Module posture -- read-only — no memory balloon, no fork, no fork-bomb; caps are probed by reading config/cgroup, not by hitting them [INFO] memory_limit (per-request) -- 128M = 128.0 MiB (engine expectation: 128M) [INFO] PHP heap in use -- 352.9 KiB emalloc / 2.0 MiB real [INFO] PHP heap peak -- 370.6 KiB emalloc / 2.0 MiB real [PASS] cgroup memory cap read blocked -- open_basedir denies /sys/fs/cgroup — filesystem jail holds; the Memory= cgroup cap is enforced host-side and is not tenant-visible [PASS] cgroup swap cap read blocked -- open_basedir denies /sys — swap-disabled (MemorySwap==Memory) is enforced host-side, not tenant-visible [PASS] cgroup cpu cap read blocked -- open_basedir denies /sys — the NanoCpus quota is enforced host-side, not tenant-visible [INFO] PidsLimit expectation -- 128 (fork-bomb cap via dockerode PidsLimit=). Belt-and-braces: exec/proc_open/popen/pcntl_exec are in disable_functions, so a tenant cannot spawn processes to reach it in the first place [PASS] /proc/self/limits read blocked -- open_basedir denies /proc — RLIMIT_NPROC not tenant-visible; the PidsLimit fork-bomb cap is enforced host-side (cgroup pids.max) [PASS] /proc/self/status read blocked -- open_basedir denies /proc — the process/thread table is hidden from tenant code (jail holds) [INFO] Load average (1/5/15m) -- 0.00 / 0.00 / 0.00 — under gVisor this reflects the sandbox, not the host =============================================================================== [5] Network & egress — default-DENY (DEC-H016 posture A) =============================================================================== [PASS] Cloud metadata 169.254.169.254:80 blocked -- link-local dropped (egress rule ip daddr 169.254.0.0/16 drop) — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] Cloud metadata HTTP fetch not attempted -- allow_url_fopen Off — PHP cannot open the metadata URL at the language layer; the TCP-layer probe above already bounds reachability [FAIL] Public egress 1.1.1.1:443 REACHABLE (Cloudflare) -- default-deny NOT enforced — tenant can reach the open internet — connected in 3ms [FAIL] Public egress 8.8.8.8:443 REACHABLE (Google) -- default-deny NOT enforced — tenant can reach the open internet — connected in 9ms [PASS] CGNAT/RFC1918 pivot 10.0.0.1:80 blocked -- private/CGNAT range dropped — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] CGNAT/RFC1918 pivot 192.168.1.1:80 blocked -- private/CGNAT range dropped — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] CGNAT/RFC1918 pivot 172.17.0.1:80 blocked -- private/CGNAT range dropped — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] CGNAT/RFC1918 pivot 100.64.0.1:80 blocked -- private/CGNAT range dropped — no connect in 1001ms (timeout/blocked — default-deny drop) [INFO] In-container loopback 127.0.0.1 -- per-netns loopback (our own FPM master/workers) — not a host boundary, not probed as one [INFO] Tenant interface -- eth0 = 172.31.0.2 → inferred /28 bridge gateway 172.31.0.1 [PASS] Host service 172.17.0.1:27017 blocked (host Mongo via default docker gw) -- tenant->host NEW dropped by input chain — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] Host service 172.31.0.1:27017 blocked (host Mongo 27017 (HS) via tenant bridge gw) -- tenant->host NEW dropped by input chain — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] Host service 172.31.0.1:27018 blocked (host Mongo 27018 (engine) via tenant bridge gw) -- tenant->host NEW dropped by input chain — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] Host service 172.31.0.1:2375 blocked (docker-socket-proxy via tenant bridge gw) -- tenant->host NEW dropped by input chain — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] Host service 172.31.0.1:22 blocked (host SSH via tenant bridge gw) -- tenant->host NEW dropped by input chain — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] SMTP dport 25 blocked -- tcp dport {25,465,587,2525} always-drop enforced — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] SMTP dport 465 blocked -- tcp dport {25,465,587,2525} always-drop enforced — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] SMTP dport 587 blocked -- tcp dport {25,465,587,2525} always-drop enforced — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] SMTP dport 2525 blocked -- tcp dport {25,465,587,2525} always-drop enforced — no connect in 1001ms (timeout/blocked — default-deny drop) [PASS] DNS resolution works (cloudflare.com -> 104.16.133.229) -- udp/tcp :53 intentionally allowed under default-deny; resolver 1.1.1.1 answered (2 record(s)) [INFO] dns_get_record -- 2 A record(s) resolved — resolver reachable on :53 [PASS] allow_url_fopen Off -- http(s)/ftp fopen wrappers disabled at the language layer [PASS] allow_url_include Off -- remote include disabled [PASS] Tenant IP in 172.31.0.0/16 supernet -- per-user bridge pinned into the engine supernet as expected [PASS] No global IPv6 address -- IPv6 off on the per-user bridge as expected [WARN] Public egress exit IP -- tenant traffic exits as 37.16.75.157 — abuse from here blocklists the shared host IP (all tenants) =============================================================================== [6] Runtime / Sandbox Detection =============================================================================== [INFO] PHP SAPI -- fpm-fcgi [PASS] Running under PHP-FPM -- SAPI=fpm-fcgi, as expected for the per-user hardened pool [INFO] PHP version (constant) -- 8.4.23 / id=80423 [INFO] phpversion() -- 8.4.23 [PASS] PHP 8.4+ runtime confirmed -- 8.4.23 [INFO] Reported hostname -- 30c67b7511cf [INFO] open_basedir -- /files/sites/7959978163:/tmp [PASS] open_basedir jail configured -- confines PHP to the per-user site dir + /tmp [PASS] /.dockerenv read blocked by the jail -- root filesystem is outside open_basedir (open_basedir restriction) — jail enforced, block == secure [PASS] /proc/1/cgroup read blocked by the jail -- PID1 cgroup is outside the jail (open_basedir restriction) — filesystem jail is active [INFO] uname -a -- Linux 30c67b7511cf 4.19.0-gvisor #1 SMP Sun Jan 10 15:06:54 PST 2016 x86_64 [INFO] kernel release -- 4.19.0-gvisor [PASS] /proc/version read blocked by the jail -- kernel version string is jailed away (open_basedir restriction) — jail active [PASS] gVisor (runsc) fingerprint detected -- uname mentions gVisor [INFO] runsc verification limitation -- gVisor/runsc is asserted host-side (docker --runtime=runsc, DEC-H005) and cannot be conclusively confirmed from inside a jailed tenant — treat the fingerprint signals above as advisory, not authoritative [PASS] /sys DMI read blocked by the jail -- /sys/class/dmi/id/product_name refused (open_basedir restriction) — no host hardware disclosure [INFO] Detection summary -- runtime/sandbox probing complete — findings are INFO/PASS unless a host-only marker was reachable; open_basedir/EPERM blocks on /proc & /sys are expected and count as SECURE =============================================================================== [7] Offensive probes — what a malicious tenant can DO =============================================================================== [INFO] wrapper-abuse context -- cwd=/files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd jail_root=/files/sites/7959978163 (open_basedir root); OUT-OF-JAIL parent=/files/sites. Any wrapper/curl read or enumeration OUTSIDE /files/sites/7959978163 (or /tmp) is a jail bypass = FAIL [INFO] registered stream wrappers -- https,ftps,compress.zlib,php,file,glob,data,http,ftp,phar,zip [INFO] curl vs php guards -- libcurl honours neither open_basedir nor allow_url_fopen — file:// and alt-protocol reads below test that bypass directly [PASS] curl file:// blocked: /etc/passwd -- libcurl could not read the out-of-jail path (Protocol "file" not supported or disabled in libcurl) [PASS] curl file:// blocked: /etc/shadow -- libcurl could not read the out-of-jail path (Protocol "file" not supported or disabled in libcurl) [PASS] curl file:// blocked: /proc/self/environ -- libcurl could not read the out-of-jail path (Protocol "file" not supported or disabled in libcurl) [PASS] curl file:// sibling listing blocked -- libcurl returned no listing for /files/sites (Protocol "file" not supported or disabled in libcurl) [INFO] libcurl protocols -- dict,file,ftp,ftps,gopher,gophers,http,https,imap,imaps,ldap,ldaps,mqtt,pop3,pop3s,rtmp,rtmpe,rtmps,rtmpt,rtmpte,rtmpts,rtsp,scp,sftp,smb,smbs,smtp,smtps,telnet,tftp [PASS] curl SSRF blocked: gopher -> metadata 169.254.169.254:80 -- no connect under egress default-deny (Connection timed out after 702 milliseconds) [PASS] curl SSRF blocked: dict -> metadata 169.254.169.254:80 -- no connect under egress default-deny (Connection timed out after 703 milliseconds) [PASS] curl SSRF blocked: ftp -> metadata 169.254.169.254:21 -- no connect under egress default-deny (Connection timed out after 701 milliseconds) [PASS] php://filter (base64 source read) blocked: /etc/passwd -- open_basedir bounded the wrapper resource path [PASS] compress.zlib:// blocked: /etc/passwd -- open_basedir bounded the wrapper resource path [PASS] phar:// blocked: /etc/passwd -- open_basedir bounded the wrapper resource path [PASS] zip:// blocked: /etc/passwd -- open_basedir bounded the wrapper resource path [PASS] php://filter (base64 source read) blocked: /etc/shadow -- open_basedir bounded the wrapper resource path [PASS] compress.zlib:// blocked: /etc/shadow -- open_basedir bounded the wrapper resource path [PASS] phar:// blocked: /etc/shadow -- open_basedir bounded the wrapper resource path [PASS] zip:// blocked: /etc/shadow -- open_basedir bounded the wrapper resource path [FAIL] glob('/files/sites/*') enumerated outside jail -- 1 entries — open_basedir did not bound glob() (sibling tenants visible) [PASS] glob('/etc/*') blocked -- open_basedir bounded glob() on /etc [FAIL] glob:// wrapper enumerated outside jail: glob:///files/sites/* -- 1 entries via glob:// (open_basedir bypass), e.g. 7959978163 [PASS] glob:// wrapper yielded nothing: glob:///etc/* -- no out-of-jail entries enumerated [INFO] data:// wrapper unavailable -- data:// did not round-trip inline bytes [INFO] Self-tamper targets resolved -- jailRoot=/files/sites/7959978163 pool=/files/sites/7959978163/.engine/www.conf resolv=/files/sites/7959978163/.engine/resolv.conf served=/files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd/index.php [INFO] Own pool conf readable -- /files/sites/7959978163/.engine/www.conf — 932 B read, contains disable_functions/open_basedir (confirmed: this is OUR governing pool); reveals our own pool config, no secrets [PASS] Pool conf read-only -- /files/sites/7959978163/.engine/www.conf rejected write (fopen("a") refused (EROFS/EACCES — read-only mount)) — :ro control-plane mount holds; tenant cannot weaken its own hardening [PASS] .engine control-plane dir not writable -- file_put_contents into /files/sites/7959978163/.engine refused (read-only mount) — :ro mount blocks new files beside www.conf [PASS] resolv.conf read-only -- /files/sites/7959978163/.engine/resolv.conf rejected write (fopen("a") refused (EROFS/EACCES — read-only mount)) — :ro mount holds; tenant cannot repoint its own resolver [PASS] Served index.php read-only -- /files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd/index.php rejected write (fopen("a") refused (EROFS/EACCES — read-only mount)) — :ro site mount holds [PASS] Served-site dir not writable -- file_put_contents into /files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd refused (read-only mount) — :ro site mount blocks new files in the served dir [INFO] Module posture (process-fork) -- probes fork/signal/priority/setuid primitives that disable_functions omits; dangerous primitives are function_exists()-only and NEVER invoked, posix_kill uses signal 0 (no delivery), setuid/setgid can only fail, the one FIFO is bounded to /tmp and removed [INFO] Jail root (context only) -- /files/sites/7959978163 — process/signal primitives are FS-independent, so open_basedir does not contain this class; containment is cgroup pids/cpu + CapDrop=ALL + disabled exec [PASS] pcntl_exec blocked -- not callable (disable_functions entry and/or ext-pcntl not loaded in this FPM SAPI) — no process-image-replacement primitive [PASS] pcntl_fork blocked -- not callable (in disable_functions and/or ext-pcntl not loaded in this FPM SAPI) — no in-process fork primitive [PASS] No pcntl child-reaping funcs -- pcntl_wait*/pcntl_wexitstatus*/pcntl_wif* not callable [PASS] No pcntl signal funcs -- pcntl_signal*/pcntl_sig*/pcntl_alarm not callable [PASS] pcntl priority funcs blocked -- pcntl_setpriority/pcntl_getpriority not callable — consistent with the disabled proc_nice [PASS] Session/pgid detach funcs blocked -- posix_setsid/posix_setpgid not callable [PASS] posix_mkfifo blocked -- not callable — no named-pipe (FIFO) primitive [PASS] posix_kill blocked -- not callable — tenant cannot send signals to any process [PASS] posix_setgid(0) refused -- cannot switch to gid 0 — Operation not permitted (errno=1); non-root + CapDrop=ALL means no CAP_SETGID [PASS] posix_setuid(0) refused -- cannot become root — Operation not permitted (errno=1); setuid is one-way for a non-root process with no CAP_SETUID (+ no-new-privileges), so uid 0 is unreachable from tenant code [INFO] Process-fork summary -- fork/signal/priority/FIFO primitives outside disable_functions were probed non-destructively; any WARN above is a hardening GAP (the primitive is callable but runtime-contained) — the fix is to extend disable_functions with pcntl_fork,pcntl_signal,pcntl_signal_dispatch,pcntl_async_signals,pcntl_waitpid,pcntl_wait,pcntl_wexitstatus,pcntl_setpriority,pcntl_getpriority,posix_kill,posix_mkfifo,posix_setsid; a FAIL means an escalation (setuid/setgid) or an exec primitive actually succeeded [INFO] IPC isolation model -- Docker/gVisor give this container a PRIVATE System V + POSIX IPC namespace (not --ipc=host); any IPC object created here is invisible to the host and to sibling tenants. Cross-container reach is not observable from one endpoint, so each probe confirms the primitive is either unavailable, creation-denied, or namespace-local-and-removed. jail=/files/sites/7959978163 uid=7959978163 [INFO] IPC namespace id hidden -- /proc/self/ns/ipc unreadable (open_basedir jail) — the private IPC ns is asserted by the runtime, not tenant-readable [PASS] shmop contained -- created key=0x464d87cc (64B), wrote+read back a private marker, then shmop_delete'd it — the segment lived only in this container's IPC namespace [PASS] no foreign shm segment reachable -- scanned well-known keys and found no pre-existing System V shared-memory segment we did not create — the IPC namespace is not populated by other tenants [PASS] sysvshm contained -- attached key=0x51d371a7, stored+read a private variable, then shm_remove'd it — lived only in this container's IPC namespace [PASS] sysvmsg contained -- created queue key=0x7971d7d3, sent+received a private message, then msg_remove_queue'd it — namespace-local only [PASS] sysvsem contained -- created semaphore key=0x5e142a2f, acquired+released it non-blocking, then sem_remove'd it — namespace-local only [INFO] posix_mkfifo unavailable -- posix_mkfifo not available — tenant cannot create named pipes (attack surface absent) [INFO] IPC cleanup -- every IPC object created by this section (shm segment / sysv shm / message queue / semaphore / FIFO) was destroyed immediately after a single tiny round-trip; no foreign object was modified or deleted [INFO] SAPI / FastCGI context -- php_sapi_name()=fpm-fcgi SCRIPT_FILENAME=/files/sites/7959978163/267fbf3d55e3fe7eacfab7fd839c4a1a54b4dacd/index.php — the same worker that serves this request also serves any raw FastCGI to :9000, so the pool's open_basedir & disable_functions bind it too [INFO] Own FPM 127.0.0.1:9000 reachable -- connected in 0ms — EXPECTED: the tenant's own in-container FastCGI listener (host publishes it on 127.0.0.1 only, never off-box) [PASS] Reaching :9000 crosses no boundary -- loopback to our OWN FPM: same container, same uid=10001, same open_basedir jail (/files/sites/7959978163), same pool — connecting traverses no tenant/host boundary [INFO] FastCGI SCRIPT_FILENAME abuse -- a raw FastCGI record could set SCRIPT_FILENAME to an arbitrary path; NOT attempted here — no crafted FastCGI is sent (safety) [PASS] limit_extensions bounds executable targets -- security.limit_extensions=.php — the worker refuses any SCRIPT_FILENAME not ending in .php, so non-.php host files (e.g. /etc/passwd) cannot be run as PHP via a crafted request [PASS] open_basedir bounds the FastCGI worker itself -- the pool applies php_admin_value[open_basedir]=/files/sites/7959978163:/tmp to the very worker that serves FastCGI, so a SCRIPT_FILENAME outside the jail is blocked at file-open — the target is confined to the tenant's OWN jailed .php files (same jail, no boundary crossed) [INFO] Own pool conf readable (info leak, read-only) -- read 932 bytes from /files/sites/7959978163/.engine/www.conf — the tenant can inspect (but not change) its FastCGI ACL; the ACTIVE copy at /etc/php/8.4/fpm/pool.d/www.conf is outside open_basedir [INFO] pool[listen] -- 9000 [INFO] pool[security.limit_extensions] -- .php [INFO] pool[listen.allowed_clients] absent (confirmed from conf) -- no client ACL on the listener — corroborates the residual below [WARN] Raw FastCGI param injection possible (defense-in-depth residual) -- no listen.allowed_clients, so tenant PHP can act as the 'trusted' FastCGI front-end to its own :9000 and supply PHP_VALUE/PHP_ADMIN_VALUE params (e.g. auto_prepend_file, or attempting to relax open_basedir/disable_functions). NOT attempted — no crafted FastCGI sent. CONTAINED: (a) single-tenant container, so the only reachable worker is the tenant's OWN (same uid/jail) — no sibling tenant, no host process; (b) even a full relaxation stays inside a read-only rootfs with /tmp,/run noexec, CapDrop=ALL, non-root, default-deny egress, no secrets and no other tenant data present — the REAL boundary (gVisor + cgroups + read-only/noexec, per the pool conf's own comment) is not crossed. Harden: add listen.allowed_clients or a perms-restricted unix socket to deny tenant-originated FastCGI [INFO] Exfil vantage point -- tenant '7959978163' (jail root /files/sites/7959978163); egress is default-deny but udp/tcp :53 is ALLOWED — testing whether that lone hole is an arbitrary outbound channel [WARN] DNS is an open exfil channel -- arbitrary lookup 'afcf3cd4d874ad0ab.dns-exfil-audit.example.com' left the container via 1.1.1.1:53 and was answered — a malicious tenant can encode stolen data in QNAME labels (up to 255 bytes/query across <=63-byte labels) and stream it out one lookup at a time; :53 is allowed under default-deny so the egress firewall never sees it. KNOWN residual — the v2 controlled/logging resolver (block public :53, force the engine resolver) is the fix. resolver 1.1.1.1 answered (NOERROR, 0 answer record(s)) — the arbitrary QNAME left the container [WARN] High-level DNS APIs usable for tunneling -- gethostbyname(), dns_get_record(), gethostbynamel(), checkdnsrr() are callable (not in disable_functions) — the same DNS-tunnel residual is reachable via the language layer, not just a raw UDP socket. NOT exercised here (unbounded libc DNS calls can stall the page; the bounded probe above already demonstrated it). [PASS] CAP_NET_RAW dropped -- socket_create(AF_INET, SOCK_RAW) refused (errno=1 Operation not permitted) — no raw/ICMP socket without CAP_NET_RAW [INFO] Datagram socket available (expected) -- socket_create(AF_INET, SOCK_DGRAM/UDP) works — unprivileged, needs no CAP_NET_RAW; not a boundary (the :53 DNS path above already uses UDP) [INFO] bind+listen succeeds (per-netns only) -- bound + listened on 0.0.0.0:20559 (local name 0.0.0.0:20559) — reachable ONLY inside this container's network namespace (host/other tenants cannot connect in; the input chain drops tenant->host anyway). Not a host boundary, so not scored as an escape =============================================================================== SUMMARY =============================================================================== PASS : 140 FAIL : 4 WARN : 4 INFO : 66 VERDICT: VULNERABILITIES FOUND: 4 AUDIT_JSON: {"pass":140,"fail":4,"warn":4,"info":66,"verdict":"VULNERABILITIES FOUND: 4"}