UNPKG

@nodesecure/js-x-ray

Version:
120 lines 4.88 kB
// Import Third-party Dependencies import ipaddress from "ipaddr.js"; // Import Internal Dependencies import { toArrayLocation } from "./utils/toArrayLocation.js"; import { CollectableSetRegistry } from "./CollectableSetRegistry.js"; // CONSTANTS const kShadyLinkRegExps = [ /(http[s]?:\/\/(bit\.ly|ipinfo\.io|httpbin\.org|api\.ipify\.org).*)$/, /(http[s]?:\/\/.*\.(link|xyz|tk|ml|ga|cf|gq|pw|top|club|mw|bd|ke|am|sbs|date|quest|cd|bid|ws|icu|cam|uno|email|stream))$/ ]; // List of known URI schemes (IANA registered + common ones) // See: https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml const kKnownProtocols = new Set([ // Web "http:", "https:", // File & Data "file:", "data:", "blob:", // FTP "ftp:", "ftps:", "sftp:", "tftp:", // Mail & Messaging "mailto:", "xmpp:", "irc:", "ircs:", "sip:", "sips:", "tel:", "sms:", "mms:", // Remote access "ssh:", "telnet:", "vnc:", "rdp:", // Version control "git:", "svn:", "cvs:", "hg:", // P2P & Torrents "magnet:", "ed2k:", "torrent:", // Crypto & Blockchain "bitcoin:", "ethereum:", "ipfs:", "ipns:", // App-specific "slack:", "discord:", "spotify:", "steam:", "skype:", "zoommtg:", "msteams:", "vscode:", "vscode-insiders:", "jetbrains:", // Mobile & Desktop deep links "intent:", "market:", "itms:", "itms-apps:", "fb:", "twitter:", "instagram:", "whatsapp:", "tg:", // Other common protocols "ws:", "wss:", "ldap:", "ldaps:", "nntp:", "news:", "rtsp:", "rtspu:", "rtsps:", "webcal:", "feed:", "podcast:", // eslint-disable-next-line no-script-url "javascript:", "about:", "view-source:", // Security related "acap:", "cap:", "cid:", "mid:", "urn:", "tag:", "dns:", "geo:", "ni:", "nih:" ]); export class ShadyLink { static isURLSafe(input, options) { if (!URL.canParse(input)) { return { safe: true }; } const parsedUrl = new URL(input); // Unknown protocol, not a real URL if (!kKnownProtocols.has(parsedUrl.protocol)) { return { safe: true }; } const { collectableSetRegistry, file, location, metadata } = options; const sourceArrayLocation = toArrayLocation(location); collectableSetRegistry.add("url", { value: parsedUrl.href, file, location: sourceArrayLocation, metadata }); const hostname = parsedUrl.hostname; // Early check for localhost if (hostname === "localhost") { collectableSetRegistry.add("hostname", { value: hostname, file, location: sourceArrayLocation, metadata }); return { safe: false, isLocalAddress: true }; } if (parsedUrl.protocol === "file:") { if (hostname) { collectableSetRegistry.add("hostname", { value: hostname, file, location: sourceArrayLocation, metadata }); } return { safe: true }; } // Remove brackets from IPv6 addresses (e.g., "[::1]" -> "::1") const cleanHostname = hostname.startsWith("[") && hostname.endsWith("]") ? hostname.slice(1, -1) : hostname; if (this.isValidIPAddress(cleanHostname)) { const result = this.isIpAddressSafe(cleanHostname, { collectableSetRegistry, file, location: sourceArrayLocation, metadata }); if (!result.safe) { return result; } } else if (hostname) { collectableSetRegistry.add("hostname", { value: hostname, file, location: sourceArrayLocation, metadata }); } const scheme = parsedUrl.protocol.replace(":", ""); if (scheme !== "https") { return { safe: false }; } const isShadyLink = kShadyLinkRegExps.some((regex) => regex.test(input)); return { safe: !isShadyLink }; } static isValidIPAddress(input) { return /\D/.test(input) && ipaddress.isValid(input); } static isIpAddressSafe(input, options) { const { collectableSetRegistry, file, location, metadata } = options; collectableSetRegistry.add("ip", { value: input, file, location: Array.isArray(location) ? location : toArrayLocation(location), metadata }); if (this.#isPrivateIPAddress(input)) { return { safe: false, isLocalAddress: true }; } return { safe: true }; } static #isPrivateIPAddress(ipAddress) { let ip = ipaddress.parse(ipAddress); if (ip instanceof ipaddress.IPv6 && ip.isIPv4MappedAddress()) { ip = ip.toIPv4Address(); } const range = ip.range(); if (range !== "unicast") { return true; } return false; } } //# sourceMappingURL=ShadyLink.js.map