fix(#338): Whitelist allowed environment variables in Docker containers
- Add DEFAULT_ENV_WHITELIST constant with safe env vars (AGENT_ID, TASK_ID, NODE_ENV, LOG_LEVEL, TZ, MOSAIC_* vars, etc.) - Implement filterEnvVars() to separate allowed/filtered vars - Log security warning when non-whitelisted vars are filtered - Support custom whitelist via orchestrator.sandbox.envWhitelist config - Add comprehensive tests for whitelist functionality (39 tests passing) Prevents accidental leakage of secrets like API keys, database credentials, AWS secrets, etc. to Docker containers. Refs #338 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,6 +3,31 @@ import { ConfigService } from "@nestjs/config";
|
||||
import Docker from "dockerode";
|
||||
import { DockerSandboxOptions, ContainerCreateResult } from "./types/docker-sandbox.types";
|
||||
|
||||
/**
|
||||
* Default whitelist of allowed environment variable names/patterns for Docker containers.
|
||||
* Only these variables will be passed to spawned agent containers.
|
||||
* This prevents accidental leakage of secrets like API keys, database credentials, etc.
|
||||
*/
|
||||
export const DEFAULT_ENV_WHITELIST: readonly string[] = [
|
||||
// Agent identification
|
||||
"AGENT_ID",
|
||||
"TASK_ID",
|
||||
// Node.js runtime
|
||||
"NODE_ENV",
|
||||
"NODE_OPTIONS",
|
||||
// Logging
|
||||
"LOG_LEVEL",
|
||||
"DEBUG",
|
||||
// Locale
|
||||
"LANG",
|
||||
"LC_ALL",
|
||||
"TZ",
|
||||
// Application-specific safe vars
|
||||
"MOSAIC_WORKSPACE_ID",
|
||||
"MOSAIC_PROJECT_ID",
|
||||
"MOSAIC_AGENT_TYPE",
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* Service for managing Docker container isolation for agents
|
||||
* Provides secure sandboxing with resource limits and cleanup
|
||||
@@ -16,6 +41,7 @@ export class DockerSandboxService {
|
||||
private readonly defaultMemoryMB: number;
|
||||
private readonly defaultCpuLimit: number;
|
||||
private readonly defaultNetworkMode: string;
|
||||
private readonly envWhitelist: readonly string[];
|
||||
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
@@ -50,6 +76,10 @@ export class DockerSandboxService {
|
||||
"bridge"
|
||||
);
|
||||
|
||||
// Load custom whitelist from config, or use defaults
|
||||
const customWhitelist = this.configService.get<string[]>("orchestrator.sandbox.envWhitelist");
|
||||
this.envWhitelist = customWhitelist ?? DEFAULT_ENV_WHITELIST;
|
||||
|
||||
this.logger.log(
|
||||
`DockerSandboxService initialized (enabled: ${this.sandboxEnabled.toString()}, socket: ${socketPath})`
|
||||
);
|
||||
@@ -87,13 +117,23 @@ export class DockerSandboxService {
|
||||
// Convert CPU limit to NanoCPUs (1.0 = 1,000,000,000 nanocpus)
|
||||
const nanoCpus = Math.floor(cpuLimit * 1000000000);
|
||||
|
||||
// Build environment variables
|
||||
// Build environment variables with whitelist filtering
|
||||
const env = [`AGENT_ID=${agentId}`, `TASK_ID=${taskId}`];
|
||||
|
||||
if (options?.env) {
|
||||
Object.entries(options.env).forEach(([key, value]) => {
|
||||
const { allowed, filtered } = this.filterEnvVars(options.env);
|
||||
|
||||
// Add allowed vars
|
||||
Object.entries(allowed).forEach(([key, value]) => {
|
||||
env.push(`${key}=${value}`);
|
||||
});
|
||||
|
||||
// Log warning for filtered vars
|
||||
if (filtered.length > 0) {
|
||||
this.logger.warn(
|
||||
`SECURITY: Filtered ${filtered.length.toString()} non-whitelisted env var(s) for agent ${agentId}: ${filtered.join(", ")}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Container name with timestamp to ensure uniqueness
|
||||
@@ -246,4 +286,44 @@ export class DockerSandboxService {
|
||||
isEnabled(): boolean {
|
||||
return this.sandboxEnabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current environment variable whitelist
|
||||
* @returns The configured whitelist of allowed env var names
|
||||
*/
|
||||
getEnvWhitelist(): readonly string[] {
|
||||
return this.envWhitelist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter environment variables against the whitelist
|
||||
* @param envVars Object of environment variables to filter
|
||||
* @returns Object with allowed vars and array of filtered var names
|
||||
*/
|
||||
filterEnvVars(envVars: Record<string, string>): {
|
||||
allowed: Record<string, string>;
|
||||
filtered: string[];
|
||||
} {
|
||||
const allowed: Record<string, string> = {};
|
||||
const filtered: string[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(envVars)) {
|
||||
if (this.isEnvVarAllowed(key)) {
|
||||
allowed[key] = value;
|
||||
} else {
|
||||
filtered.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { allowed, filtered };
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an environment variable name is allowed by the whitelist
|
||||
* @param varName Environment variable name to check
|
||||
* @returns True if allowed
|
||||
*/
|
||||
private isEnvVarAllowed(varName: string): boolean {
|
||||
return this.envWhitelist.includes(varName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user