feat: add a trust proxy env to control whether to trust incoming x-forwarded-for headers (#662)

This commit is contained in:
Nico 2026-03-13 19:08:08 +01:00 committed by GitHub
parent 8fdf858dcb
commit 9632c77177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 21 additions and 1 deletions

View file

@ -5,4 +5,5 @@ ZEROBYTE_REPOSITORIES_DIR=./data/repositories
ZEROBYTE_VOLUMES_DIR=./data/volumes
APP_SECRET=<openssl rand -hex 32>
BASE_URL=http://localhost:300
TRUST_PROXY=false
PORT=3000

View file

@ -96,6 +96,7 @@ Zerobyte can be customized using environment variables. Below are the available
| `PORT` | The port the web interface and API will listen on. | `4096` |
| `RESTIC_HOSTNAME` | The hostname used by Restic when creating snapshots. Automatically detected if a custom hostname is set in Docker. | `zerobyte` |
| `TZ` | Timezone for the container (e.g., `Europe/Zurich`). **Crucial for accurate backup scheduling.** | `UTC` |
| `TRUST_PROXY` | When `true`, trust an existing `X-Forwarded-For` header from your reverse proxy. Leave `false` for direct deployments. | `false` |
| `TRUSTED_ORIGINS` | Comma-separated list of extra trusted origins for CORS (e.g., `http://localhost:3000,http://example.com`). | (none) |
| `LOG_LEVEL` | Logging verbosity. Options: `debug`, `info`, `warn`, `error`. | `info` |
| `SERVER_IDLE_TIMEOUT` | Idle timeout for the server in seconds. | `60` |

View file

@ -5,6 +5,7 @@ import { config } from "~/server/core/config";
const app = createApp();
type NodeRuntimeRequest = Request & {
ip?: string;
runtime?: {
node?: {
res?: { setTimeout: (timeoutMs: number) => void };
@ -16,7 +17,22 @@ export const prepareApiRequest = (request: Request, timeoutMs: number) => {
const nodeRequest = request as NodeRuntimeRequest;
nodeRequest.runtime?.node?.res?.setTimeout(timeoutMs);
return request.clone();
if (config.trustProxy && request.headers.has("x-forwarded-for")) {
return request.clone();
}
const remoteAddress = nodeRequest.ip;
if (remoteAddress) {
const headers = new Headers(request.headers);
headers.set("x-forwarded-for", remoteAddress);
return new Request(request, { headers });
}
const headers = new Headers(request.headers);
headers.delete("x-forwarded-for");
return new Request(request, { headers });
};
const handle = ({ request }: { request: Request }) =>

View file

@ -34,6 +34,7 @@ const envSchema = z
MIGRATIONS_PATH: z.string().optional(),
APP_VERSION: z.string().default("dev"),
TRUSTED_ORIGINS: z.string().optional(),
TRUST_PROXY: z.string().default("false"),
DISABLE_RATE_LIMITING: z.string().default("false"),
APP_SECRET: z.string().min(32).max(256),
BASE_URL: z.string(),
@ -53,6 +54,7 @@ const envSchema = z
.map((origin) => origin.trim())
.filter(Boolean)
.concat(s.BASE_URL) ?? [s.BASE_URL],
trustProxy: s.TRUST_PROXY === "true",
disableRateLimiting: s.DISABLE_RATE_LIMITING === "true",
appSecret: s.APP_SECRET,
baseUrl: s.BASE_URL,