worm-sign
Version:
A prescient scanner to detect and banish Shai Hulud malware from your dependencies.
116 lines (115 loc) • 4.07 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.isPrivateIp = isPrivateIp;
exports.validateUrl = validateUrl;
const dns = __importStar(require("dns"));
const net = __importStar(require("net"));
const url_1 = require("url");
function isPrivateIp(ip) {
if (net.isIP(ip) === 0)
return false;
// IPv4 Private Ranges
// 10.0.0.0/8
// 172.16.0.0/12
// 192.168.0.0/16
// 127.0.0.0/8 (Loopback)
// 169.254.0.0/16 (Link-local)
if (net.isIPv4(ip)) {
const parts = ip.split('.').map(Number);
if (parts[0] === 10)
return true;
if (parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31)
return true;
if (parts[0] === 192 && parts[1] === 168)
return true;
if (parts[0] === 127)
return true;
if (parts[0] === 169 && parts[1] === 254)
return true;
return false;
}
// IPv6 Private Ranges
// fc00::/7 (Unique Local)
// fe80::/10 (Link-local)
// ::1/128 (Loopback)
if (net.isIPv6(ip)) {
// Simplified check for common private prefixes
// Normalize? net.isIPv6 handles format, but we need to check ranges.
// For now, let's block loopback and link-local which are most critical for SSRF against local services.
if (ip === '::1')
return true;
if (ip.toLowerCase().startsWith('fe80:'))
return true;
if (ip.toLowerCase().startsWith('fc') || ip.toLowerCase().startsWith('fd'))
return true;
return false;
}
return false;
}
async function validateUrl(urlStr) {
let parsed;
try {
parsed = new url_1.URL(urlStr);
}
catch {
throw new Error(`Invalid URL: ${urlStr}`);
}
if (parsed.protocol !== 'https:') {
throw new Error('Security Error: Only HTTPS protocol is allowed.');
}
const hostname = parsed.hostname;
// If hostname is an IP, check directly
if (net.isIP(hostname)) {
if (isPrivateIp(hostname)) {
throw new Error(`Security Error: Access to private IP ${hostname} is forbidden.`);
}
return hostname;
}
// Resolve hostname
return new Promise((resolve, reject) => {
dns.lookup(hostname, (err, address) => {
if (err) {
reject(new Error(`DNS lookup failed for ${hostname}: ${err.message}`));
return;
}
if (isPrivateIp(address)) {
reject(new Error(`Security Error: Hostname ${hostname} resolves to private IP ${address}.`));
return;
}
resolve(address);
});
});
}