UNPKG

@contentstack/management

Version:

The Content Management API is used to manage the content of your Contentstack account

209 lines (195 loc) 8.93 kB
"use strict"; var _interopRequireDefault3 = require("@babel/runtime/helpers/interopRequireDefault"); var _interopRequireDefault2 = _interopRequireDefault3(require("@babel/runtime/helpers/interopRequireDefault")); Object.defineProperty(exports, "__esModule", { value: true }); exports.validateAndSanitizeConfig = undefined; var _defineProperty2 = require("@babel/runtime/helpers/defineProperty"); var _defineProperty3 = (0, _interopRequireDefault2["default"])(_defineProperty2); exports.isHost = isHost; exports.isNode = isNode; exports.getNodeVersion = getNodeVersion; exports["default"] = getUserAgent; var _os = require("os"); function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty3["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } var HOST_REGEX = /^(?!(?:(?:https?|ftp):\/\/|internal|localhost|(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))(?:[\w-]+\.contentstack\.(?:io|com)(?::[^\/\s:]+)?|[\w-]+(?:\.[\w-]+)*(?::[^\/\s:]+)?)(?![\/?#])$/; // eslint-disable-line function isHost(host) { if (!host) return false; return HOST_REGEX.test(host); } function isNode() { return typeof process !== 'undefined' && !process.browser; } function getNodeVersion() { return process.versions.node ? "v".concat(process.versions.node) : process.version; } function isReactNative() { return typeof window !== 'undefined' && 'navigator' in window && 'product' in window.navigator && window.navigator.product === 'ReactNative'; } function getBrowserOS() { if (!window) { return null; } var userAgent = window.navigator.userAgent; var platform = window.navigator.platform; var macosPlatforms = ['Macintosh', 'MacIntel', 'MacPPC', 'Mac68K']; var windowsPlatforms = ['Win32', 'Win64', 'Windows', 'WinCE']; var iosPlatforms = ['iPhone', 'iPad', 'iPod']; var os = null; if (macosPlatforms.indexOf(platform) !== -1) { os = 'macOS'; } else if (iosPlatforms.indexOf(platform) !== -1) { os = 'iOS'; } else if (windowsPlatforms.indexOf(platform) !== -1) { os = 'Windows'; } else if (/Android/.test(userAgent)) { os = 'Android'; } else if (/Linux/.test(platform)) { os = 'Linux'; } return os; } function getNodeOS() { var os = (0, _os.platform)() || 'linux'; var version = (0, _os.release)() || '0.0.0'; var osMap = { android: 'Android', aix: 'Linux', darwin: 'macOS', freebsd: 'Linux', linux: 'Linux', openbsd: 'Linux', sunos: 'Linux', win32: 'Windows' }; if (os in osMap) { return "".concat(osMap[os] || 'Linux', "/").concat(version); } return null; } function getUserAgent(sdk, application, integration, feature) { var headerParts = []; if (application) { headerParts.push("app ".concat(application)); } if (integration) { headerParts.push("integration ".concat(integration)); } if (feature) { headerParts.push('feature ' + feature); } headerParts.push("sdk ".concat(sdk)); var os = null; try { if (isReactNative()) { os = getBrowserOS(); headerParts.push('platform ReactNative'); } else if (isNode()) { os = getNodeOS(); headerParts.push("platform node.js/".concat(getNodeVersion())); } else { os = getBrowserOS(); headerParts.push("platform browser"); } } catch (e) { os = null; } if (os) { headerParts.push("os ".concat(os)); } return "".concat(headerParts.filter(function (item) { return item !== ''; }).join('; '), ";"); } // URL validation functions to prevent SSRF attacks var isValidURL = function isValidURL(url) { try { // Reject obviously malicious patterns early if (url.includes('@') || url.includes('file://') || url.includes('ftp://')) { return false; } // Allow relative URLs (they are safe as they use the same origin) if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) { return true; } // Only validate absolute URLs for SSRF protection var parsedURL = new URL(url); // Reject non-HTTP(S) protocols if (!['http:', 'https:'].includes(parsedURL.protocol)) { return false; } var officialDomains = ['api.contentstack.io', 'eu-api.contentstack.com', 'azure-na-api.contentstack.com', 'azure-eu-api.contentstack.com', 'gcp-na-api.contentstack.com', 'gcp-eu-api.contentstack.com']; var isContentstackDomain = officialDomains.some(function (domain) { return parsedURL.hostname === domain || parsedURL.hostname.endsWith('.' + domain); }); if (isContentstackDomain && parsedURL.protocol !== 'https:') { return false; } // Prevent IP addresses in URLs to avoid internal network access var ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; var ipv6Regex = /^\[?([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}\]?$/; if (ipv4Regex.test(parsedURL.hostname) || ipv6Regex.test(parsedURL.hostname)) { // Only allow localhost IPs in development var isDevelopment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test' || !process.env.NODE_ENV; var localhostIPs = ['127.0.0.1', '0.0.0.0', '::1', 'localhost']; if (!isDevelopment || !localhostIPs.includes(parsedURL.hostname)) { return false; } } return isAllowedHost(parsedURL.hostname); } catch (_unused) { // If URL parsing fails, it might be a relative URL without protocol // Allow it if it doesn't contain protocol indicators or suspicious patterns return !(url !== null && url !== void 0 && url.includes('://')) && !(url !== null && url !== void 0 && url.includes('\\')) && !(url !== null && url !== void 0 && url.includes('@')); } }; var isAllowedHost = function isAllowedHost(hostname) { // Define allowed domains for Contentstack API // Official Contentstack domains var allowedDomains = ['api.contentstack.io', 'eu-api.contentstack.com', 'azure-na-api.contentstack.com', 'azure-eu-api.contentstack.com', 'gcp-na-api.contentstack.com', 'gcp-eu-api.contentstack.com']; // Check for localhost/development environments var localhostPatterns = ['localhost', '127.0.0.1', '0.0.0.0']; // Only allow localhost in development environments to prevent SSRF in production var isDevelopment = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test' || !process.env.NODE_ENV; // Default to allowing in non-production if NODE_ENV is not set if (isDevelopment && localhostPatterns.includes(hostname)) { return true; } // Check if hostname is in allowed domains or is a subdomain of allowed domains var isContentstackDomain = allowedDomains.some(function (domain) { return hostname === domain || hostname.endsWith('.' + domain); }); // If it's not a Contentstack domain, validate custom hostname if (!isContentstackDomain) { // Prevent internal/reserved IP ranges and localhost variants var ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/; if (hostname !== null && hostname !== void 0 && hostname.match(ipv4Regex)) { var parts = hostname.split('.'); var firstOctet = parseInt(parts[0]); // Only block private IP ranges if (firstOctet === 10 || firstOctet === 192 || firstOctet === 127) { return false; } } // Allow custom domains that don't match dangerous patterns return !hostname.includes('file://') && !hostname.includes('\\') && !hostname.includes('@') && hostname !== 'localhost'; } return isContentstackDomain; }; var validateAndSanitizeConfig = exports.validateAndSanitizeConfig = function validateAndSanitizeConfig(config) { if (!(config !== null && config !== void 0 && config.url) || typeof (config === null || config === void 0 ? void 0 : config.url) !== 'string') { throw new Error('Invalid request configuration: missing or invalid URL'); } // Validate the URL to prevent SSRF attacks if (!isValidURL(config.url)) { throw new Error("SSRF Prevention: URL \"".concat(config.url, "\" is not allowed")); } // Additional validation for baseURL if present if (config.baseURL && typeof config.baseURL === 'string' && !isValidURL(config.baseURL)) { throw new Error("SSRF Prevention: Base URL \"".concat(config.baseURL, "\" is not allowed")); } return _objectSpread(_objectSpread({}, config), {}, { url: config.url.trim() // Sanitize URL by removing whitespace }); };