diff --git a/app/(dashboard)/proxy-hosts/actions.ts b/app/(dashboard)/proxy-hosts/actions.ts index 326b97bf..6d684357 100644 --- a/app/(dashboard)/proxy-hosts/actions.ts +++ b/app/(dashboard)/proxy-hosts/actions.ts @@ -319,6 +319,7 @@ function parseGeoBlockConfig(formData: FormData): { allow_cidrs: parseStringList("geoblock_allow_cidrs"), allow_ips: parseStringList("geoblock_allow_ips"), trusted_proxies: parseStringList("geoblock_trusted_proxies"), + fail_closed: formData.get("geoblock_fail_closed") === "on", response_status: parseOptionalNumber(formData.get("geoblock_response_status")) ?? 403, response_body: parseOptionalText(formData.get("geoblock_response_body")) ?? "Forbidden", response_headers: parseResponseHeaders(formData), diff --git a/app/(dashboard)/settings/actions.ts b/app/(dashboard)/settings/actions.ts index 38268887..46b7608c 100644 --- a/app/(dashboard)/settings/actions.ts +++ b/app/(dashboard)/settings/actions.ts @@ -553,6 +553,7 @@ export async function updateGeoBlockSettingsAction(_prevState: ActionResult | nu allow_cidrs: parseGeoBlockStringList("geoblock_allow_cidrs", formData), allow_ips: parseGeoBlockStringList("geoblock_allow_ips", formData), trusted_proxies: parseGeoBlockStringList("geoblock_trusted_proxies", formData), + fail_closed: parseGeoBlockCheckbox(formData.get("geoblock_fail_closed")), response_status: responseStatus, response_body: responseBody, response_headers: parseGeoBlockResponseHeaders(formData), diff --git a/src/components/proxy-hosts/GeoBlockFields.tsx b/src/components/proxy-hosts/GeoBlockFields.tsx index 523180c4..1d7f434a 100644 --- a/src/components/proxy-hosts/GeoBlockFields.tsx +++ b/src/components/proxy-hosts/GeoBlockFields.tsx @@ -6,10 +6,12 @@ import { AccordionSummary, Autocomplete, Box, + Checkbox, Chip, CircularProgress, Collapse, Divider, + FormControlLabel, Grid, IconButton, Stack, @@ -468,6 +470,19 @@ export function GeoBlockFields({ initialValues, showModeSelector = true }: GeoBl helperText="Used to parse X-Forwarded-For. Use private_ranges for all RFC-1918 ranges." /> + + + } + label={Fail closed (block indeterminate IPs)} + /> + + diff --git a/src/lib/caddy.ts b/src/lib/caddy.ts index 494b4f58..ee6d8c8b 100644 --- a/src/lib/caddy.ts +++ b/src/lib/caddy.ts @@ -730,6 +730,7 @@ function mergeGeoBlockSettings( allow_ips: [...(global.allow_ips ?? []), ...(host.allow_ips ?? [])], trusted_proxies: [...(global.trusted_proxies ?? []), ...(host.trusted_proxies ?? [])], // Host config wins for scalar fields + fail_closed: host.fail_closed || global.fail_closed || false, response_status: host.response_status ?? global.response_status ?? 403, response_body: host.response_body ?? global.response_body ?? "Forbidden", response_headers: { ...(global.response_headers ?? {}), ...(host.response_headers ?? {}) }, @@ -784,6 +785,7 @@ function buildBlockerHandler(config: GeoBlockSettings): Record if (config.allow_ips?.length) handler.allow_ips = config.allow_ips; if (config.trusted_proxies?.length) handler.trusted_proxies = config.trusted_proxies; + if (config.fail_closed) handler.fail_closed = true; if (config.redirect_url) { handler.redirect_url = config.redirect_url; diff --git a/src/lib/settings.ts b/src/lib/settings.ts index a69b4e3d..4c799400 100644 --- a/src/lib/settings.ts +++ b/src/lib/settings.ts @@ -64,6 +64,9 @@ export type GeoBlockSettings = { // Trusted proxies for X-Forwarded-For parsing trusted_proxies: string[]; + // When true, block requests where the real client IP cannot be determined + // (e.g. connection from trusted proxy but no usable XFF entry). Default: false (fail-open) + fail_closed: boolean; // Block response customization response_status: number; // default 403