diff --git a/frontend/src/lib/optics.ts b/frontend/src/lib/optics.ts index 0e758f4b..ac72e192 100644 --- a/frontend/src/lib/optics.ts +++ b/frontend/src/lib/optics.ts @@ -70,6 +70,37 @@ export const DEFAULT_OPTICS = [ }, ] satisfies DefaultOpticOption[]; +const isPrivateIp4 = (url: string) => { + const parts = url.split('://'); + const ip = + parts.length > 0 + ? parts[parts.length - 1].replace(/\/$/, '').split(':')[0] + : url.replace(/\/$/, '').split(':')[0]; + + if (/^(10)\.(.*)\.(.*)\.(.*)$/.test(ip)) return true; + if (/^(172)\.(1[6-9]|2[0-9]|3[0-1])\.(.*)\.(.*)$/.test(ip)) return true; + if (/^(192)\.168\.(.*)\.(.*)$/.test(ip)) return true; + if (/^(127)\.(0)\.(0)\.(1)$/.test(ip)) return true; + if (/^(100)\.(6[4-9]|[7-9][0-9]|1[0-1][0-9]|12[0-7])\.(.*)\.(.*)$/.test(ip)) return true; + + return false; +}; + +const isPrivateIp6 = (url: string) => { + const parts = url.split('://'); + const ip = + parts.length > 0 + ? parts[parts.length - 1].replace(/\/$/, '').split(':')[0] + : url.replace(/\/$/, ''); + + if (/^fe80::/i.test(ip)) return true; + if (/^fd[0-9a-f]{2}:/i.test(ip)) return true; + + return false; +}; + +const isPrivateIp = (url: string) => isPrivateIp4(url) || isPrivateIp6(url); + /** * Fetces the given `opticUrl` if allowed. The rules for which are allowed * should consider potentially malicious URLs such as `file://` or @@ -77,6 +108,11 @@ export const DEFAULT_OPTICS = [ */ export const fetchRemoteOptic = async (opts: { opticUrl: string; fetch?: typeof fetch }) => { if (opts.opticUrl.startsWith('file://')) return void 0; + if (isPrivateIp(opts.opticUrl)) return void 0; + const response = await (opts.fetch ?? fetch)(opts.opticUrl); + + if (isPrivateIp(response.url)) return void 0; + return await response.text(); };