diff --git a/messages/en-US.json b/messages/en-US.json index 1f868601..d84f4f33 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -175,6 +175,7 @@ "resourceHTTPDescription": "Proxy requests over HTTPS using a fully qualified domain name.", "resourceRaw": "Raw TCP/UDP Resource", "resourceRawDescription": "Proxy requests over raw TCP/UDP using a port number.", + "resourceRawDescriptionCloud": "Proxy requests over raw TCP/UDP using a port number. REQUIRES THE USE OF A REMOTE NODE.", "resourceCreate": "Create Resource", "resourceCreateDescription": "Follow the steps below to create a new resource", "resourceSeeAll": "See All Resources", diff --git a/server/routers/newt/buildConfiguration.ts b/server/routers/newt/buildConfiguration.ts index e349f24e..65cb18a2 100644 --- a/server/routers/newt/buildConfiguration.ts +++ b/server/routers/newt/buildConfiguration.ts @@ -230,7 +230,7 @@ export async function buildTargetConfigurationForNewtClient(siteId: number) { !target.hcMethod ) { logger.debug( - `Skipping target ${target.targetId} due to missing health check fields` + `Skipping adding target health check ${target.targetId} due to missing health check fields` ); return null; // Skip targets with missing health check fields } diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 34b75ebb..42e2849f 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -101,6 +101,49 @@ const updateHttpResourceBodySchema = z { error: "Invalid custom Host Header value. Use domain name format, or save empty to unset custom Host Header." } + ) + .refine( + (data) => { + if (data.headers) { + // HTTP header names must be valid token characters (RFC 7230) + const validHeaderName = /^[a-zA-Z0-9!#$%&'*+\-.^_`|~]+$/; + return data.headers.every((h) => validHeaderName.test(h.name)); + } + return true; + }, + { + error: "Header names may only contain valid HTTP token characters (letters, digits, and !#$%&'*+-.^_`|~)." + } + ) + .refine( + (data) => { + if (data.headers) { + // HTTP header values must be visible ASCII or horizontal whitespace, no control chars (RFC 7230) + const validHeaderValue = /^[\t\x20-\x7E]*$/; + return data.headers.every((h) => validHeaderValue.test(h.value)); + } + return true; + }, + { + error: "Header values may only contain printable ASCII characters and horizontal whitespace." + } + ) + .refine( + (data) => { + if (data.headers) { + // Reject Traefik template syntax {{word}} in names or values + const templatePattern = /\{\{[^}]+\}\}/; + return data.headers.every( + (h) => + !templatePattern.test(h.name) && + !templatePattern.test(h.value) + ); + } + return true; + }, + { + error: "Header names and values must not contain template expressions such as {{value}}." + } ); export type UpdateResourceResponse = Resource; diff --git a/src/app/[orgId]/settings/resources/proxy/create/page.tsx b/src/app/[orgId]/settings/resources/proxy/create/page.tsx index 47f24cd9..ff51a311 100644 --- a/src/app/[orgId]/settings/resources/proxy/create/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/create/page.tsx @@ -61,6 +61,7 @@ import { DockerManager, DockerState } from "@app/lib/docker"; import { orgQueries } from "@app/lib/queries"; import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; import { zodResolver } from "@hookform/resolvers/zod"; +import { build } from "@server/build"; import { Resource } from "@server/db"; import { isTargetValid } from "@server/lib/validators"; import { ListTargetsResponse } from "@server/routers/target"; @@ -292,7 +293,7 @@ export default function Page() { { id: "raw" as ResourceType, title: t("resourceRaw"), - description: t("resourceRawDescription") + description: build == "saas" ? t("resourceRawDescriptionCloud") : t("resourceRawDescription") } ]) ];