@contentstack/management
Version:
The Content Management API is used to manage the content of your Contentstack account
209 lines (195 loc) • 8.93 kB
JavaScript
;
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
});
};