From ba6bf584bea7645425fb2d9b9e8d4f6a4ee0421a Mon Sep 17 00:00:00 2001 From: dhaval Date: Wed, 4 Mar 2026 13:43:36 +0530 Subject: [PATCH 1/4] Update sca-scan.yml --- .github/workflows/sca-scan.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sca-scan.yml b/.github/workflows/sca-scan.yml index 2307d48902..6c83843da9 100644 --- a/.github/workflows/sca-scan.yml +++ b/.github/workflows/sca-scan.yml @@ -15,4 +15,4 @@ jobs: args: --all-projects --fail-on=all json: true continue-on-error: true - - uses: contentstack/sca-policy@main + - uses: contentstack/sca-policy@main From 4ce08dd837cfb0f2b8096a5f99855417f6c0d52e Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:14:09 +0530 Subject: [PATCH 2/4] enh: Added support for NO_PROXY support in env file --- .../src/contentstack-management-sdk.ts | 13 +- .../src/http-client/client.ts | 23 ++- .../src/proxy-helper.ts | 136 +++++++++++++++++- 3 files changed, 163 insertions(+), 9 deletions(-) diff --git a/packages/contentstack-utilities/src/contentstack-management-sdk.ts b/packages/contentstack-utilities/src/contentstack-management-sdk.ts index 5306e9eb21..77600cc31f 100644 --- a/packages/contentstack-utilities/src/contentstack-management-sdk.ts +++ b/packages/contentstack-utilities/src/contentstack-management-sdk.ts @@ -2,7 +2,7 @@ import { client, ContentstackClient, ContentstackConfig } from '@contentstack/ma import authHandler from './auth-handler'; import { Agent } from 'node:https'; import configHandler, { default as configStore } from './config-handler'; -import { getProxyConfig } from './proxy-helper'; +import { getProxyConfigForHost, resolveRequestHost, clearProxyEnv } from './proxy-helper'; import dotenv from 'dotenv'; dotenv.config(); @@ -17,8 +17,15 @@ class ManagementSDKInitiator { } async createAPIClient(config): Promise { - // Get proxy configuration with priority: Environment variables > Global config - const proxyConfig = getProxyConfig(); + // Resolve host so NO_PROXY applies even when config.host is omitted (e.g. from region.cma) + const host = resolveRequestHost(config); + // NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY and config-set proxy + const proxyConfig = getProxyConfigForHost(host); + + // When bypassing, clear proxy env immediately so SDK never see it (they may read at init or first request). + if (!proxyConfig) { + clearProxyEnv(); + } const option: ContentstackConfig = { host: config.host, diff --git a/packages/contentstack-utilities/src/http-client/client.ts b/packages/contentstack-utilities/src/http-client/client.ts index a7b1013545..2fd213d2ed 100644 --- a/packages/contentstack-utilities/src/http-client/client.ts +++ b/packages/contentstack-utilities/src/http-client/client.ts @@ -3,7 +3,23 @@ import { IHttpClient } from './client-interface'; import { HttpResponse } from './http-response'; import configStore from '../config-handler'; import authHandler from '../auth-handler'; -import { hasProxy, getProxyUrl, getProxyConfig } from '../proxy-helper'; +import { hasProxy, getProxyUrl, getProxyConfig, getProxyConfigForHost } from '../proxy-helper'; + +/** + * Derive request host from baseURL or url for NO_PROXY checks. + */ +function getRequestHost(baseURL?: string, url?: string): string | undefined { + const toTry = [baseURL, url].filter(Boolean) as string[]; + for (const candidateUrl of toTry) { + try { + const parsed = new URL(candidateUrl.startsWith('http') ? candidateUrl : `https://${candidateUrl}`); + return parsed.hostname || undefined; + } catch { + // Invalid URL; try next candidate (baseURL or url) + } + } + return undefined; +} export type HttpClientOptions = { disableEarlyAccessHeaders?: boolean; @@ -411,9 +427,10 @@ export class HttpClient implements IHttpClient { } } - // Configure proxy if available (priority: request.proxy > getProxyConfig()) + // Configure proxy if available. NO_PROXY has priority: hosts in NO_PROXY never use proxy. if (!this.request.proxy) { - const proxyConfig = getProxyConfig(); + const host = getRequestHost(this.request.baseURL, url); + const proxyConfig = host ? getProxyConfigForHost(host) : getProxyConfig(); if (proxyConfig) { this.request.proxy = proxyConfig; } diff --git a/packages/contentstack-utilities/src/proxy-helper.ts b/packages/contentstack-utilities/src/proxy-helper.ts index 708b097fc6..68de79fc8d 100644 --- a/packages/contentstack-utilities/src/proxy-helper.ts +++ b/packages/contentstack-utilities/src/proxy-helper.ts @@ -11,11 +11,84 @@ export interface ProxyConfig { } /** - * Get proxy configuration with priority: Environment variables > Global config + * Parse NO_PROXY / no_proxy env (both uppercase and lowercase). + * NO_PROXY has priority over HTTP_PROXY/HTTPS_PROXY: hosts in this list never use the proxy. + * Values are hostnames only, comma-separated; leading dot matches subdomains (e.g. .contentstack.io). + * The bypass list is fully dynamic: only env values are used (no hardcoded default). + * @returns List of trimmed entries, or empty array when NO_PROXY/no_proxy is unset + */ +export function getNoProxyList(): string[] { + const raw = process.env.NO_PROXY || process.env.no_proxy || ''; + return raw + .split(',') + .map((s) => s.trim()) + .filter(Boolean); +} + +/** + * Normalize host for NO_PROXY matching: strip protocol/URL, port, lowercase, handle IPv6 brackets. + * Accepts hostname, host:port, or full URL (e.g. https://api.contentstack.io). + */ +function normalizeHost(host: string): string { + if (!host || typeof host !== 'string') return ''; + let h = host.trim().toLowerCase(); + // If it looks like a URL, extract hostname so NO_PROXY matching works (e.g. region.cma is full URL) + if (h.includes('://')) { + try { + const u = new URL(h); + h = u.hostname; + } catch { + // fall through to port stripping below + } + } + const portIdx = h.lastIndexOf(':'); + if (h.startsWith('[')) { + const close = h.indexOf(']'); + if (close !== -1 && h.length > close + 1 && h[close + 1] === ':') { + h = h.slice(1, close); + } + } else if (portIdx !== -1) { + const after = h.slice(portIdx + 1); + if (/^\d+$/.test(after)) { + h = h.slice(0, portIdx); + } + } + return h; +} + +/** + * Check if the given host should bypass the proxy based on NO_PROXY / no_proxy. + * Supports: exact host, leading-dot subdomain match (e.g. .contentstack.io), and wildcard *. + * @param host - Request hostname (with or without port; will be normalized) + * @returns true if proxy should not be used for this host + */ +export function shouldBypassProxy(host: string): boolean { + const normalized = normalizeHost(host); + if (!normalized) return false; + + const list = getNoProxyList(); + for (const entry of list) { + const e = entry.trim().toLowerCase(); + if (!e) continue; + if (e === '*') return true; + if (e.startsWith('.')) { + const domain = e.slice(1); + if (normalized === domain || normalized.endsWith(e)) return true; + } else { + if (normalized === e) return true; + } + } + return false; +} + +/** + * Get proxy configuration. Sources (in order): env (HTTP_PROXY/HTTPS_PROXY), then global config + * from `csdx config:set:proxy --host --port --protocol `. + * For per-request use, prefer getProxyConfigForHost(host) so NO_PROXY overrides both sources. * @returns ProxyConfig object or undefined if no proxy is configured */ export function getProxyConfig(): ProxyConfig | undefined { - // Priority 1: Check environment variables (HTTPS_PROXY or HTTP_PROXY) + // Priority 1: Environment variables (HTTPS_PROXY or HTTP_PROXY) const proxyUrl = process.env.HTTPS_PROXY || process.env.HTTP_PROXY; if (proxyUrl) { @@ -46,7 +119,7 @@ export function getProxyConfig(): ProxyConfig | undefined { } } - // Priority 2: Check global config store + // Priority 2: Global config (csdx config:set:proxy) const globalProxyConfig = configStore.get('proxy'); if (globalProxyConfig) { if (typeof globalProxyConfig === 'object') { @@ -86,6 +159,63 @@ export function getProxyConfig(): ProxyConfig | undefined { return undefined; } +/** + * Get proxy config only when the request host is not in NO_PROXY. + * NO_PROXY has priority over both HTTP_PROXY/HTTPS_PROXY and over proxy set via + * `csdx config:set:proxy` — if the host matches NO_PROXY, no proxy is used. + * Use this for all outbound requests so Contentstack and localhost bypass the proxy when set. + * @param host - Request hostname (e.g. api.contentstack.io or full URL like https://api.contentstack.io) + * @returns ProxyConfig or undefined if proxy is disabled or host should bypass (NO_PROXY) + */ +export function getProxyConfigForHost(host: string): ProxyConfig | undefined { + if (shouldBypassProxy(host)) return undefined; + return getProxyConfig(); +} + +/** + * Resolve request host for proxy/NO_PROXY checks: config.host or default CMA from region. + * Use when the caller may omit host so NO_PROXY still applies (e.g. from region.cma). + * @param config - Object with optional host (e.g. API client config) + * @returns Host string (hostname or empty) + */ +export function resolveRequestHost(config: { host?: string }): string { + if (config.host) return config.host; + const cma = configStore.get('region')?.cma; + if (cma && typeof cma === 'string') { + if (cma.startsWith('http')) { + try { + const u = new URL(cma); + return u.hostname || cma; + } catch { + return cma; + } + } + return cma; + } + return ''; +} + +/** + * Temporarily clear proxy-related env vars so SDK/axios cannot use them. + * Call the returned function to restore. Use when creating a client for a host in NO_PROXY. + * @returns Restore function (call to put env back) + */ +export function clearProxyEnv(): () => void { + const saved: Record = {}; + const keys = ['HTTP_PROXY', 'HTTPS_PROXY', 'http_proxy', 'https_proxy', 'ALL_PROXY', 'all_proxy']; + for (const k of keys) { + if (k in process.env) { + saved[k] = process.env[k]; + delete process.env[k]; + } + } + return () => { + for (const k of keys) { + if (saved[k] !== undefined) process.env[k] = saved[k]; + } + }; +} + /** * Check if proxy is configured (from any source) * @returns true if proxy is configured, false otherwise From 1b24c6a84a022a6947aca694d338ce033ff6ac73 Mon Sep 17 00:00:00 2001 From: naman-contentstack Date: Mon, 16 Mar 2026 16:27:11 +0530 Subject: [PATCH 3/4] chore: version bumps --- packages/contentstack-auth/package.json | 8 ++++---- packages/contentstack-command/package.json | 6 +++--- packages/contentstack-config/package.json | 8 ++++---- packages/contentstack-utilities/package.json | 4 ++-- packages/contentstack/package.json | 8 ++++---- pnpm-lock.yaml | 12 ++++++------ 6 files changed, 23 insertions(+), 23 deletions(-) diff --git a/packages/contentstack-auth/package.json b/packages/contentstack-auth/package.json index 314d009329..40c2bb0cb3 100644 --- a/packages/contentstack-auth/package.json +++ b/packages/contentstack-auth/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-auth", "description": "Contentstack CLI plugin for authentication activities", - "version": "1.8.0-beta.0", + "version": "1.8.0-beta.1", "author": "Contentstack", "bugs": "https://github.com/contentstack/cli/issues", "scripts": { @@ -22,8 +22,8 @@ "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"" }, "dependencies": { - "@contentstack/cli-command": "~1.8.0-beta.0", - "@contentstack/cli-utilities": "~1.18.0-beta.0", + "@contentstack/cli-command": "~1.8.0-beta.1", + "@contentstack/cli-utilities": "~1.19.0-beta.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", "otplib": "^12.0.1" @@ -81,4 +81,4 @@ } }, "repository": "contentstack/cli" -} +} \ No newline at end of file diff --git a/packages/contentstack-command/package.json b/packages/contentstack-command/package.json index c491e46b40..cc37771bd3 100644 --- a/packages/contentstack-command/package.json +++ b/packages/contentstack-command/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-command", "description": "Contentstack CLI plugin for configuration", - "version": "1.8.0-beta.0", + "version": "1.8.0-beta.1", "author": "Contentstack", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -20,7 +20,7 @@ "test:unit": "mocha --timeout 10000 --forbid-only \"test/unit/**/*.test.ts\"" }, "dependencies": { - "@contentstack/cli-utilities": "~1.18.0-beta.0", + "@contentstack/cli-utilities": "~1.19.0-beta.0", "contentstack": "^3.25.3", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28" @@ -65,4 +65,4 @@ "repositoryPrefix": "<%- repo %>/blob/main/packages/contentstack-command/<%- commandPath %>" }, "repository": "contentstack/cli" -} +} \ No newline at end of file diff --git a/packages/contentstack-config/package.json b/packages/contentstack-config/package.json index a85fac3f02..1845e52ca0 100644 --- a/packages/contentstack-config/package.json +++ b/packages/contentstack-config/package.json @@ -1,7 +1,7 @@ { "name": "@contentstack/cli-config", "description": "Contentstack CLI plugin for configuration", - "version": "1.20.0-beta.0", + "version": "1.20.0-beta.1", "author": "Contentstack", "scripts": { "build": "pnpm compile && oclif manifest && oclif readme", @@ -21,8 +21,8 @@ "test:unit:report": "nyc --extension .ts mocha --forbid-only \"test/unit/**/*.test.ts\"" }, "dependencies": { - "@contentstack/cli-command": "~1.8.0-beta.0", - "@contentstack/cli-utilities": "~1.18.0-beta.0", + "@contentstack/cli-command": "~1.8.0-beta.1", + "@contentstack/cli-utilities": "~1.19.0-beta.0", "@contentstack/utils": "~1.7.0", "@oclif/core": "^4.3.0", "@oclif/plugin-help": "^6.2.28", @@ -95,4 +95,4 @@ } }, "repository": "contentstack/cli" -} +} \ No newline at end of file diff --git a/packages/contentstack-utilities/package.json b/packages/contentstack-utilities/package.json index c9ff037fc4..19914b31a6 100644 --- a/packages/contentstack-utilities/package.json +++ b/packages/contentstack-utilities/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/cli-utilities", - "version": "1.18.0-beta.0", + "version": "1.19.0-beta.0", "description": "Utilities for contentstack projects", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -82,4 +82,4 @@ "ts-node": "^10.9.2", "typescript": "^4.9.5" } -} +} \ No newline at end of file diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index eab2cc1671..68a38d544c 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -24,16 +24,16 @@ "dependencies": { "@contentstack/cli-audit": "~1.19.0-beta.0", "@contentstack/cli-cm-export": "~1.24.0-beta.0", - "@contentstack/cli-cm-import": "~1.32.0-beta.1", + "@contentstack/cli-cm-import": "~1.32.0-beta.0", "@contentstack/cli-auth": "~1.8.0-beta.0", - "@contentstack/cli-cm-bootstrap": "~1.19.0-beta.1", + "@contentstack/cli-cm-bootstrap": "~1.19.0-beta.0", "@contentstack/cli-cm-branches": "~1.7.0-beta.0", "@contentstack/cli-cm-bulk-publish": "~1.11.0-beta.0", - "@contentstack/cli-cm-clone": "~1.21.0-beta.1", + "@contentstack/cli-cm-clone": "~1.21.0-beta.0", "@contentstack/cli-cm-export-to-csv": "~1.12.0-beta.0", "@contentstack/cli-cm-import-setup": "~1.8.0-beta.0", "@contentstack/cli-cm-migrate-rte": "~1.6.4", - "@contentstack/cli-cm-seed": "~1.15.0-beta.2", + "@contentstack/cli-cm-seed": "~1.15.0-beta.0", "@contentstack/cli-command": "~1.8.0-beta.0", "@contentstack/cli-config": "~1.20.0-beta.0", "@contentstack/cli-launch": "^1.9.6", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f892adb53a..c4d7c95736 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,7 +67,7 @@ importers: version: 1.12.0-beta.0(@types/node@14.18.63)(debug@4.4.3) '@contentstack/cli-utilities': specifier: ~1.18.0-beta.0 - version: link:../contentstack-utilities + version: 1.18.0-beta.0(@types/node@14.18.63)(debug@4.4.3) '@contentstack/cli-variants': specifier: ~1.4.0-beta.0 version: 1.4.0-beta.0(@types/node@14.18.63)(debug@4.4.3) @@ -193,10 +193,10 @@ importers: packages/contentstack-auth: dependencies: '@contentstack/cli-command': - specifier: ~1.8.0-beta.0 + specifier: ~1.8.0-beta.1 version: link:../contentstack-command '@contentstack/cli-utilities': - specifier: ~1.18.0-beta.0 + specifier: ~1.19.0-beta.0 version: link:../contentstack-utilities '@oclif/core': specifier: ^4.3.0 @@ -266,7 +266,7 @@ importers: packages/contentstack-command: dependencies: '@contentstack/cli-utilities': - specifier: ~1.18.0-beta.0 + specifier: ~1.19.0-beta.0 version: link:../contentstack-utilities '@oclif/core': specifier: ^4.3.0 @@ -315,10 +315,10 @@ importers: packages/contentstack-config: dependencies: '@contentstack/cli-command': - specifier: ~1.8.0-beta.0 + specifier: ~1.8.0-beta.1 version: link:../contentstack-command '@contentstack/cli-utilities': - specifier: ~1.18.0-beta.0 + specifier: ~1.19.0-beta.0 version: link:../contentstack-utilities '@contentstack/utils': specifier: ~1.7.0 From 4d968b7bb48811d4472d26763438eb3c6a2e7ef4 Mon Sep 17 00:00:00 2001 From: shafeeqd959 Date: Wed, 18 Mar 2026 13:54:01 +0530 Subject: [PATCH 4/4] core dependency bumped --- packages/contentstack/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/contentstack/package.json b/packages/contentstack/package.json index 68a38d544c..f887b85dd7 100755 --- a/packages/contentstack/package.json +++ b/packages/contentstack/package.json @@ -38,7 +38,7 @@ "@contentstack/cli-config": "~1.20.0-beta.0", "@contentstack/cli-launch": "^1.9.6", "@contentstack/cli-migration": "~1.12.0-beta.0", - "@contentstack/cli-utilities": "~1.18.0-beta.0", + "@contentstack/cli-utilities": "~1.19.0-beta.0", "@contentstack/cli-variants": "~1.4.0-beta.0", "@contentstack/management": "~1.27.5", "@oclif/core": "^4.3.0", @@ -168,4 +168,4 @@ } }, "repository": "https://github.com/contentstack/cli" -} \ No newline at end of file +}