Repository navigation
Conversation
On shared hosting 127.0.0.1 is shared by every account on the server. server.py trusted any loopback peer and shared no secret with api.php, so a co-tenant could: - call the backend directly, bypassing the web server's auth, and open the server-side connections from websh.json; - bind 127.0.0.1:8765 while the backend was down, after which api.php forwarded users' SSH credentials and keystrokes to that process. Two websh installs on one host also collided on the default port, so one account's proxy could end up talking to the other's backend. server.py gains WEBSH_SOCKET (env-only): listen on a Unix socket created 0600 instead of HOST:PORT. A stale socket left by a crashed backend is replaced; a live socket or a non-socket file is refused. Peers on the socket are trusted for X-Forwarded-For and WEBSH_AUTH_HEADER. api.php now always reaches the backend over websh.sock next to websh.json (override with WEBSH_SOCKET) and auto-starts server.py with that path; WEBSH_PORT is gone. The auto-start lock moves from the shared temp dir to the socket's directory. A socket in a directory other users can write would reopen the hole (a co-tenant plants one first), so api.php and server.py both require the directory to belong to the account and be closed to other users' writes, and api.php refuses a socket owned by someone else. Misconfigurations (no Unix socket support in curl, which needs PHP 7.0.7+ and libcurl 7.40+; a path over 107 bytes; an unusable directory or lock file) answer 500 with the reason, never a TCP fallback. Docker, systemd and reverse-proxy setups keep the TCP default. Upgrade note: a backend started by an older api.php keeps listening on 127.0.0.1:8765 until it is stopped. Tests cover the socket server (0600 mode; stale, live and non-socket paths; a directory others can write; main() wiring) and socket peers in _client_ip/_client_identity. The PHP smoke runs its stub on a socket, auto-starts the real server.py end to end (0600 socket, no TCP port) and checks that a socket in a shared directory is refused.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #154 (
fix/php85-curl-close): this diff contains only the socket change. Please merge #153 first. It rewrites the outdated "PHP 5.3+" lines inapi.phpand the README, which this PR leaves untouched to avoid a conflict. The new floor for the PHP proxy is PHP 7.0.7+ with libcurl 7.40+.Problem
On shared hosting,
127.0.0.1is shared by every account on the server.server.pytrusted any loopback peer and shared no secret withapi.php, so a co-tenant could:websh.json;127.0.0.1:8765while the backend was down, after whichapi.phpforwarded users' SSH credentials and keystrokes to that process.Two websh installs on one host also collided on the default port, so one account's proxy could end up talking to the other's backend.
Changes
server.pyWEBSH_SOCKET. When it is set, the backend listens on a Unix socket created with mode 0600 instead ofHOST:PORT.X-Forwarded-ForandWEBSH_AUTH_HEADER, the same way loopback peers are today.api.phpwebsh.socknext towebsh.json.WEBSH_SOCKEToverrides the path.server.pywith that socket path.WEBSH_PORTis removed.Upgrade note
A backend started by an older
api.phpkeeps listening on127.0.0.1:8765until it is stopped. After upgrading, stop it (pkill -f server.py) or wait for the host to recycle the process. Hosts where PHP doesn't run as the account itself (for example,mod_phpunder a shared user), or whereopen_basedirblocks the site root, now get a 500 that explains the problem, where before they ran without this protection.Test plan
ruff, backend suite: 541 tests OKmain()wiring;_client_ipand_client_identity.server.pyis auto-started end to end: 0600 socket and no TCP listener;