MAESTRO: Merge PR #884 adding Zscaler SSL certificate support for ChromaDB vector search
Adds automatic detection and handling of Zscaler enterprise security certificates on macOS. Combines standard certifi CA certificates with Zscaler certificates into a single bundle, passed via SSL_CERT_FILE/REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE env vars to the chroma-mcp subprocess. Certificate bundle is cached for 24 hours. Falls back gracefully when Zscaler is not present, with no impact on non-Zscaler environments. Co-Authored-By: RClark4958 <rickdclark48@gmail.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+130
-129
File diff suppressed because one or more lines are too long
@@ -17,6 +17,8 @@ import { SettingsDefaultsManager } from '../../shared/SettingsDefaultsManager.js
|
||||
import { USER_SETTINGS_PATH } from '../../shared/paths.js';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fs from 'fs';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
// Version injected at build time by esbuild define
|
||||
declare const __DEFAULT_PACKAGE_VERSION__: string;
|
||||
@@ -104,6 +106,80 @@ export class ChromaSync {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get or create combined SSL certificate bundle for Zscaler/corporate proxy environments
|
||||
* Combines standard certifi certificates with enterprise security certificates (e.g., Zscaler)
|
||||
*/
|
||||
private getCombinedCertPath(): string | undefined {
|
||||
const combinedCertPath = path.join(os.homedir(), '.claude-mem', 'combined_certs.pem');
|
||||
|
||||
// If combined certs already exist and are recent (less than 24 hours old), use them
|
||||
if (fs.existsSync(combinedCertPath)) {
|
||||
const stats = fs.statSync(combinedCertPath);
|
||||
const ageMs = Date.now() - stats.mtimeMs;
|
||||
if (ageMs < 24 * 60 * 60 * 1000) {
|
||||
return combinedCertPath;
|
||||
}
|
||||
}
|
||||
|
||||
// Only create on macOS (Zscaler certificate extraction uses macOS security command)
|
||||
if (process.platform !== 'darwin') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
try {
|
||||
// Use uvx to resolve the correct certifi path for the exact Python environment it uses
|
||||
// This is more reliable than scanning the uv cache directory structure
|
||||
let certifiPath: string | undefined;
|
||||
try {
|
||||
certifiPath = execSync(
|
||||
'uvx --with certifi python -c "import certifi; print(certifi.where())"',
|
||||
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 10000 }
|
||||
).trim();
|
||||
} catch {
|
||||
// uvx or certifi not available
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (!certifiPath || !fs.existsSync(certifiPath)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Try to extract Zscaler certificate from macOS keychain
|
||||
let zscalerCert = '';
|
||||
try {
|
||||
zscalerCert = execSync(
|
||||
'security find-certificate -a -c "Zscaler" -p /Library/Keychains/System.keychain',
|
||||
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'], timeout: 5000 }
|
||||
);
|
||||
} catch {
|
||||
// Zscaler not found, which is fine - not all environments have it
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Validate PEM certificate format (must have both BEGIN and END markers)
|
||||
if (!zscalerCert ||
|
||||
!zscalerCert.includes('-----BEGIN CERTIFICATE-----') ||
|
||||
!zscalerCert.includes('-----END CERTIFICATE-----')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Create combined certificate bundle with atomic write (write to temp, then rename)
|
||||
const certifiContent = fs.readFileSync(certifiPath, 'utf8');
|
||||
const tempPath = combinedCertPath + '.tmp';
|
||||
fs.writeFileSync(tempPath, certifiContent + '\n' + zscalerCert);
|
||||
fs.renameSync(tempPath, combinedCertPath);
|
||||
logger.info('CHROMA_SYNC', 'Created combined SSL certificate bundle for Zscaler', {
|
||||
path: combinedCertPath
|
||||
});
|
||||
|
||||
return combinedCertPath;
|
||||
} catch (error) {
|
||||
logger.debug('CHROMA_SYNC', 'Could not create combined cert bundle', {}, error as Error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if Chroma is disabled (Windows)
|
||||
*/
|
||||
@@ -129,6 +205,9 @@ export class ChromaSync {
|
||||
const pythonVersion = settings.CLAUDE_MEM_PYTHON_VERSION;
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Get combined SSL certificate bundle for Zscaler/corporate proxy environments
|
||||
const combinedCertPath = this.getCombinedCertPath();
|
||||
|
||||
const transportOptions: any = {
|
||||
command: 'uvx',
|
||||
args: [
|
||||
@@ -140,6 +219,19 @@ export class ChromaSync {
|
||||
stderr: 'ignore'
|
||||
};
|
||||
|
||||
// Add SSL certificate environment variables for corporate proxy/Zscaler environments
|
||||
if (combinedCertPath) {
|
||||
transportOptions.env = {
|
||||
...process.env,
|
||||
SSL_CERT_FILE: combinedCertPath,
|
||||
REQUESTS_CA_BUNDLE: combinedCertPath,
|
||||
CURL_CA_BUNDLE: combinedCertPath
|
||||
};
|
||||
logger.info('CHROMA_SYNC', 'Using combined SSL certificates for Zscaler compatibility', {
|
||||
certPath: combinedCertPath
|
||||
});
|
||||
}
|
||||
|
||||
// CRITICAL: On Windows, try to hide console window to prevent PowerShell popups
|
||||
// Note: windowsHide may not be supported by MCP SDK's StdioClientTransport
|
||||
if (isWindows) {
|
||||
|
||||
Reference in New Issue
Block a user