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:
Jason Woltje
2026-02-05 18:17:00 -06:00
parent 67c72a2d82
commit e747c8db04
2 changed files with 311 additions and 8 deletions

View File

@@ -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);
}
}