Bug Summary
The communityWebsite field in signupSchema validates that the user input does not include http:// or https://, then prepends https:// in a .transform():
communityWebsite: z
.string()
.optional()
.or(z.literal(""))
.refine(
(val) => !val || /^(?!https?:\/\/)([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}(\/.*)?$/.test(val),
{ message: "Enter domain only (e.g. example.com)." }
)
.transform((val) => (val && val.trim() !== "" ? `https://${val}` : val)),
The regex ([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,} accepts:
- IP addresses with appended labels:
192.168.1.internal or 169.254.169.metadata pass the regex (the part before the dot is valid, and internal/metadata are valid TLD-like strings).
- Internal hostnames:
my-server.local or admin.lan pass the regex and are transformed to https://my-server.local and https://admin.lan.
- Subdomains of attacker-controlled domains:
internal.attacker.com trivially passes.
After .transform(), the result https://192.168.1.internal is stored as the community website. If the backend later fetches the URL for a preview (link unfurling) or renders it as a clickable link, internal network addresses could reach backend services.
Additionally, the regex requires at minimum [a-zA-Z]{2,} after the final dot, which accepts single-character country codes and invented TLDs not in any public suffix list. There is no allowlist of known TLDs.
Expected Behavior
After prepending https://, the resulting URL should be additionally validated against a safe URL allowlist (only http: and https: schemes, no private IP ranges). A simple check using new URL(transformed) and verifying hostname does not resolve to a private range would catch most cases.
Actual Behavior
Internal hostnames and IP-like addresses pass the communityWebsite regex and are stored with https:// prepended.
Affected File
src/features/Auth/v1/hooks/useSignupForm.ts, communityWebsite field.
@NexGenStudioDev I would like to work on this issue. Could you please assign/ it to me? Contributing under NSoC '26.
Bug Summary
The
communityWebsitefield insignupSchemavalidates that the user input does not includehttp://orhttps://, then prependshttps://in a.transform():The regex
([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}accepts:192.168.1.internalor169.254.169.metadatapass the regex (the part before the dot is valid, andinternal/metadataare valid TLD-like strings).my-server.localoradmin.lanpass the regex and are transformed tohttps://my-server.localandhttps://admin.lan.internal.attacker.comtrivially passes.After
.transform(), the resulthttps://192.168.1.internalis stored as the community website. If the backend later fetches the URL for a preview (link unfurling) or renders it as a clickable link, internal network addresses could reach backend services.Additionally, the regex requires at minimum
[a-zA-Z]{2,}after the final dot, which accepts single-character country codes and invented TLDs not in any public suffix list. There is no allowlist of known TLDs.Expected Behavior
After prepending
https://, the resulting URL should be additionally validated against a safe URL allowlist (onlyhttp:andhttps:schemes, no private IP ranges). A simple check usingnew URL(transformed)and verifyinghostnamedoes not resolve to a private range would catch most cases.Actual Behavior
Internal hostnames and IP-like addresses pass the
communityWebsiteregex and are stored withhttps://prepended.Affected File
src/features/Auth/v1/hooks/useSignupForm.ts,communityWebsitefield.@NexGenStudioDev I would like to work on this issue. Could you please assign/ it to me? Contributing under NSoC '26.