Compare commits

...

1 Commits

Author SHA1 Message Date
Emerson Gomes
b9b70bc87e Harden markdown link protocols 2025-12-01 20:19:59 -06:00
2 changed files with 23 additions and 29 deletions

View File

@@ -94,19 +94,8 @@ export const NEXT_PUBLIC_INCLUDE_ERROR_POPUP_SUPPORT_LINK =
export const NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY =
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY;
// Add support for custom URL protocols in markdown links
export const ALLOWED_URL_PROTOCOLS = [
"http:",
"https:",
"mailto:",
"tel:",
"slack:",
"vscode:",
"file:",
"sms:",
"spotify:",
"zoommtg:",
];
// Restrict markdown links to safe protocols
export const ALLOWED_URL_PROTOCOLS = ["http:", "https:", "mailto:"] as const;
export const MAX_CHARACTERS_PERSONA_DESCRIPTION = 5000000;
export const MAX_STARTER_MESSAGES = 4;

View File

@@ -11,28 +11,33 @@ export const truncateString = (str: string, maxLength: number) => {
};
/**
* Custom URL transformer function for ReactMarkdown
* Allows specific protocols to be used in markdown links
* We use this with the urlTransform prop in ReactMarkdown
* Custom URL transformer function for ReactMarkdown.
* Only allows a small, safe set of protocols and strips everything else.
* Returning null removes the href attribute entirely.
*/
export function transformLinkUri(href: string) {
if (!href) return href;
export function transformLinkUri(href: string): string | null {
if (!href) return null;
const trimmedHref = href.trim();
if (!trimmedHref) return null;
const url = href.trim();
try {
const parsedUrl = new URL(url);
if (
ALLOWED_URL_PROTOCOLS.some((protocol) =>
parsedUrl.protocol.startsWith(protocol)
)
) {
return url;
const parsedUrl = new URL(trimmedHref);
const protocol = parsedUrl.protocol.toLowerCase();
if (ALLOWED_URL_PROTOCOLS.some((allowed) => allowed === protocol)) {
return trimmedHref;
}
return null;
} catch {
// If it's not a valid URL with protocol, return the original href
return href;
// Allow relative URLs, but drop anything that looks like a protocol-prefixed link
if (/^[a-zA-Z][a-zA-Z\d+.-]*:\S*/.test(trimmedHref)) {
return null;
}
return trimmedHref;
}
return href;
}
export function isSubset(parent: string[], child: string[]): boolean {