Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ fileignoreconfig:
ignore_detectors:
- filecontent
- filename: package-lock.json
checksum: 424e5c45fa8043c95e0da5b215279c41cbe85230f75262ec7ac9ba01520e8821
checksum: 17b5bbabcc58beaa180a7fa931fc3fb407ee0e3447d47da224f60118c0a4c294
- filename: .husky/pre-commit
checksum: 52a664f536cf5d1be0bea19cb6031ca6e8107b45b6314fe7d47b7fad7d800632
- filename: test/sanity-check/api/user-test.js
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## [v1.27.1](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.1) (2026-01-5)
- Fix
- Resolve qs dependency version

## [v1.27.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.0) (2025-12-15)
- Enhancement
- Refactored region endpoint resolution to use centralized `@contentstack/utils` package
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2012-2025 Contentstack
Copyright (c) 2012-2026 Contentstack

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
54 changes: 42 additions & 12 deletions lib/core/concurrency-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ const defaultConfig = {

/**
* Creates a concurrency queue manager for Axios requests with retry logic and rate limiting.
* SECURITY NOTICE - SSRF Prevention (CWE-918):
* This module implements comprehensive Server-Side Request Forgery (SSRF) protection.
* All axios requests are validated using validateAndSanitizeConfig() which:
* - Restricts requests to approved Contentstack domains only
* - Blocks private IP addresses and internal network access
* - Enforces HTTP/HTTPS protocols only (blocks file://, ftp://, etc.)
* - Validates both URL and baseURL configurations
* - Prevents URL injection attacks through proper sanitization
* @param {Object} options - Configuration options.
* @param {Object} options.axios - Axios instance to manage.
* @param {Object=} options.config - Queue configuration options.
Expand Down Expand Up @@ -70,6 +78,30 @@ export function ConcurrencyQueue ({ axios, config }) {
this.running = []
this.paused = false

// SECURITY: Safe axios wrapper that always validates configs to prevent SSRF (CWE-918)
// This ensures ALL axios requests are validated before execution
const safeAxiosRequest = (requestConfig) => {
// Validate and sanitize to prevent SSRF attacks (CWE-918)
// This function throws an error if the URL is not allowed
const sanitized = validateAndSanitizeConfig(requestConfig)

// Additional runtime check: Ensure URL has been validated
if (!sanitized || !sanitized.url) {
throw new Error('Invalid request: URL validation failed')
}

// SECURITY: The axios call below is safe because validateAndSanitizeConfig ensures:
// 1. Only approved Contentstack domains are allowed
// 2. Private IP addresses are blocked
// 3. Only HTTP/HTTPS protocols are permitted
// 4. URL injection attacks are prevented
//
// This axios call is protected by validateAndSanitizeConfig above which validates
// all URLs against SSRF attacks. The function throws an error for any disallowed URLs.
// deepcode ignore Ssrf: URL is validated and sanitized by validateAndSanitizeConfig before use
return axios(sanitized)
}

// Helper function to determine if an error is a transient network failure
const isTransientNetworkError = (error) => {
// DNS resolution failures
Expand Down Expand Up @@ -158,12 +190,13 @@ export function ConcurrencyQueue ({ axios, config }) {
setTimeout(() => {
// Keep the request in running queue to maintain maxRequests constraint
// Set retry flags to ensure proper queue handling
const sanitizedConfig = validateAndSanitizeConfig(updateRequestConfig(error, `Network retry ${attempt}`, delay))
sanitizedConfig.retryCount = sanitizedConfig.retryCount || 0
const requestConfig = updateRequestConfig(error, `Network retry ${attempt}`, delay)
requestConfig.retryCount = requestConfig.retryCount || 0

// Use axios directly but ensure the running queue is properly managed
// The request interceptor will handle this retry appropriately
axios(sanitizedConfig)
// SECURITY: Using safeAxiosRequest wrapper that validates against SSRF attacks
safeAxiosRequest(requestConfig)
.then((response) => {
// On successful retry, call the original onComplete to properly clean up
if (error.config.onComplete) {
Expand Down Expand Up @@ -315,9 +348,8 @@ export function ConcurrencyQueue ({ axios, config }) {

// Retry the requests that were pending due to token expiration
this.running.forEach(({ request, resolve, reject }) => {
// Retry the request with sanitized configuration to prevent SSRF
const sanitizedConfig = validateAndSanitizeConfig(request)
axios(sanitizedConfig).then(resolve).catch(reject)
// SECURITY: Using safeAxiosRequest wrapper that validates against SSRF attacks
safeAxiosRequest(request).then(resolve).catch(reject)
})
this.running = [] // Clear the running queue after retrying requests
} catch (error) {
Expand Down Expand Up @@ -445,9 +477,8 @@ export function ConcurrencyQueue ({ axios, config }) {
// Cool down the running requests
delay(wait, response.status === 401)
error.config.retryCount = networkError
// SSRF Prevention: Validate URL before making request
const sanitizedConfig = validateAndSanitizeConfig(updateRequestConfig(error, retryErrorType, wait))
return axios(sanitizedConfig)
// SECURITY: Using safeAxiosRequest wrapper that validates against SSRF attacks
return safeAxiosRequest(updateRequestConfig(error, retryErrorType, wait))
}
if (this.config.retryCondition && this.config.retryCondition(error)) {
retryErrorType = error.response ? `Error with status: ${response.status}` : `Error Code:${error.code}`
Expand Down Expand Up @@ -477,9 +508,8 @@ export function ConcurrencyQueue ({ axios, config }) {
error.config.retryCount = retryCount
return new Promise(function (resolve) {
return setTimeout(function () {
// SSRF Prevention: Validate URL before making request
const sanitizedConfig = validateAndSanitizeConfig(updateRequestConfig(error, retryErrorType, delaytime))
return resolve(axios(sanitizedConfig))
// SECURITY: Using safeAxiosRequest wrapper that validates against SSRF attacks
return resolve(safeAxiosRequest(updateRequestConfig(error, retryErrorType, delaytime)))
}, delaytime)
})
}
Expand Down
Loading
Loading