Harden origin and media URL security

This commit is contained in:
Jage9
2026-03-08 20:51:50 -04:00
parent 3d69bbcea2
commit 78bc931cce
12 changed files with 378 additions and 14 deletions

View File

@@ -31,6 +31,12 @@ What this sets up:
- `server/run_server.sh` (loads `.env` and starts server)
- first-run admin bootstrap prompt (if no admin exists)
Before starting the service, set `CHGRID_HOST_ORIGIN` in `server/.env` to the exact browser origin that will host Chat Grid, for example:
```bash
CHGRID_HOST_ORIGIN=https://example.com
```
## 3) Publish Client
```bash
@@ -77,6 +83,8 @@ ProxyPass /auth/session/ http://127.0.0.1:8765/auth/session/
ProxyPassReverse /auth/session/ http://127.0.0.1:8765/auth/session/
```
The websocket server enforces browser origin matching against `CHGRID_HOST_ORIGIN`, so the public site origin must match that env var exactly.
What each route does:
- `/ws`: websocket signaling (presence, movement, item actions, chat, voice signaling).
- `/auth/session/set`: called by client after successful login to set `HttpOnly` session cookie.
@@ -100,6 +108,12 @@ ProxyPassReverse /listen/8000/ http://127.0.0.1:8000/
`deploy/php/media_proxy.php` is copied into your publish directory by `deploy_client.sh`.
The proxy also requires the same `CHGRID_HOST_ORIGIN` value in the PHP/Apache environment so only your own site origin can read from it. For Apache, one simple option is:
```apache
SetEnv CHGRID_HOST_ORIGIN https://example.com
```
Use:
```text

View File

@@ -2,6 +2,9 @@
# Keep your existing main DocumentRoot unchanged when hosting Chat Grid under /chgrid.
# Required modules: proxy, proxy_http, proxy_wstunnel
# Optional but recommended modules for client update freshness: headers, setenvif
# Set the public browser origin for websocket and media-proxy origin checks.
# Example:
# SetEnv CHGRID_HOST_ORIGIN https://example.com
# Proxy websocket signaling endpoint to local Python service.
# `/ws` is used by the browser signaling client for realtime packets.

View File

@@ -115,6 +115,38 @@ function send_text($code, $message)
exit;
}
function normalize_origin($value)
{
$value = trim((string) $value);
if ($value === '') {
return '';
}
$parts = parse_url($value);
if ($parts === false || !isset($parts['scheme']) || !isset($parts['host'])) {
return '';
}
$scheme = strtolower((string) $parts['scheme']);
if ($scheme !== 'http' && $scheme !== 'https') {
return '';
}
if (isset($parts['user']) || isset($parts['pass']) || isset($parts['query']) || isset($parts['fragment'])) {
return '';
}
$path = isset($parts['path']) ? (string) $parts['path'] : '';
if ($path !== '' && $path !== '/') {
return '';
}
$host = strtolower((string) $parts['host']);
if ($host === '') {
return '';
}
if (strpos($host, ':') !== false && substr($host, 0, 1) !== '[') {
$host = '[' . $host . ']';
}
$port = isset($parts['port']) ? ':' . (int) $parts['port'] : '';
return $scheme . '://' . $host . $port;
}
function host_matches_suffix($host, $suffix)
{
if ($suffix === '') {
@@ -377,7 +409,20 @@ function resolve_safe_redirect_chain($initialUrl, $allowlistSuffixes, $requestHe
return '';
}
header('Access-Control-Allow-Origin: *');
// Optional allowlist env var: CHGRID_MEDIA_PROXY_ALLOWLIST=dropbox.com,example.com
$allowlistEnv = getenv('CHGRID_MEDIA_PROXY_ALLOWLIST');
$allowlistSuffixes = parse_allowlist_suffixes($allowlistEnv);
$allowedOrigin = normalize_origin(getenv('CHGRID_HOST_ORIGIN'));
if ($allowedOrigin === '') {
send_text(500, 'CHGRID_HOST_ORIGIN is required');
}
$requestOrigin = normalize_origin(isset($_SERVER['HTTP_ORIGIN']) ? (string) $_SERVER['HTTP_ORIGIN'] : '');
if ($requestOrigin !== '' && $requestOrigin !== $allowedOrigin) {
send_text(403, 'origin not allowed');
}
header('Access-Control-Allow-Origin: ' . $allowedOrigin);
header('Vary: Origin');
header('Access-Control-Allow-Methods: GET, HEAD, OPTIONS');
header('Access-Control-Allow-Headers: Range');
@@ -396,10 +441,6 @@ if ($rawUrl === '') {
}
$rawUrl = normalize_dropbox_url($rawUrl);
// Optional allowlist env var: CHGRID_MEDIA_PROXY_ALLOWLIST=dropbox.com,example.com
$allowlistEnv = getenv('CHGRID_MEDIA_PROXY_ALLOWLIST');
$allowlistSuffixes = parse_allowlist_suffixes($allowlistEnv);
if (!function_exists('curl_init')) {
send_text(500, 'curl extension is required');
}

View File

@@ -58,6 +58,7 @@ print(secrets.token_urlsafe(64))
PY
)"
printf "CHGRID_AUTH_SECRET=%s\n" "$AUTH_SECRET" > .env
printf "# Required browser origin, for example CHGRID_HOST_ORIGIN=https://example.com\n" >> .env
chmod 600 .env
echo "created $SERVER_DIR/.env with CHGRID_AUTH_SECRET"
fi
@@ -123,4 +124,4 @@ fi
chmod +x "$SERVER_DIR/run_server.sh"
echo "server install complete"
echo "next: edit $SERVER_DIR/config.toml (TLS, bind_ip, port)"
echo "next: edit $SERVER_DIR/config.toml (TLS, bind_ip, port) and set CHGRID_HOST_ORIGIN in $SERVER_DIR/.env"