Harden origin and media URL security
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user