fix(#338): Block YOLO mode in production
- Add isProductionEnvironment() check to prevent YOLO mode bypass - Log warning when YOLO mode request is blocked in production - Fall back to process.env.NODE_ENV when config service returns undefined - Add comprehensive tests for production blocking behavior SECURITY: YOLO mode bypasses all quality gates which is dangerous in production environments. This change ensures quality gates are always enforced when NODE_ENV=production. Refs #338 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1288,5 +1288,222 @@ describe("QualityGatesService", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("YOLO mode blocked in production (SEC-ORCH-13)", () => {
|
||||||
|
const params = {
|
||||||
|
taskId: "task-prod-123",
|
||||||
|
agentId: "agent-prod-456",
|
||||||
|
files: ["src/feature.ts"],
|
||||||
|
diffSummary: "Production deployment",
|
||||||
|
};
|
||||||
|
|
||||||
|
it("should block YOLO mode when NODE_ENV is production", async () => {
|
||||||
|
// Enable YOLO mode but set production environment
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return "production";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockResponse: QualityCheckResponse = {
|
||||||
|
approved: true,
|
||||||
|
gate: "pre-commit",
|
||||||
|
message: "All checks passed",
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockCoordinatorClient.checkQuality).mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await service.preCommitCheck(params);
|
||||||
|
|
||||||
|
// Should call coordinator (YOLO mode blocked in production)
|
||||||
|
expect(mockCoordinatorClient.checkQuality).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Should return coordinator response, not YOLO bypass
|
||||||
|
expect(result.approved).toBe(true);
|
||||||
|
expect(result.message).toBe("All checks passed");
|
||||||
|
expect(result.details?.yoloMode).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log warning when YOLO mode is blocked in production", async () => {
|
||||||
|
// Enable YOLO mode but set production environment
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return "production";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const loggerWarnSpy = vi.spyOn(service["logger"], "warn");
|
||||||
|
|
||||||
|
const mockResponse: QualityCheckResponse = {
|
||||||
|
approved: true,
|
||||||
|
gate: "pre-commit",
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockCoordinatorClient.checkQuality).mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
await service.preCommitCheck(params);
|
||||||
|
|
||||||
|
// Should log warning about YOLO mode being blocked
|
||||||
|
expect(loggerWarnSpy).toHaveBeenCalledWith(
|
||||||
|
"YOLO mode blocked in production environment - quality gates will be enforced",
|
||||||
|
expect.objectContaining({
|
||||||
|
requestedYoloMode: true,
|
||||||
|
environment: "production",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow YOLO mode in development environment", async () => {
|
||||||
|
// Enable YOLO mode with development environment
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return "development";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.preCommitCheck(params);
|
||||||
|
|
||||||
|
// Should NOT call coordinator (YOLO mode enabled)
|
||||||
|
expect(mockCoordinatorClient.checkQuality).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Should return YOLO bypass result
|
||||||
|
expect(result.approved).toBe(true);
|
||||||
|
expect(result.message).toBe("Quality gates disabled (YOLO mode)");
|
||||||
|
expect(result.details?.yoloMode).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should allow YOLO mode in test environment", async () => {
|
||||||
|
// Enable YOLO mode with test environment
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return "test";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await service.postCommitCheck(params);
|
||||||
|
|
||||||
|
// Should NOT call coordinator (YOLO mode enabled)
|
||||||
|
expect(mockCoordinatorClient.checkQuality).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Should return YOLO bypass result
|
||||||
|
expect(result.approved).toBe(true);
|
||||||
|
expect(result.message).toBe("Quality gates disabled (YOLO mode)");
|
||||||
|
expect(result.details?.yoloMode).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should block YOLO mode for post-commit in production", async () => {
|
||||||
|
// Enable YOLO mode but set production environment
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return "production";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
const mockResponse: QualityCheckResponse = {
|
||||||
|
approved: false,
|
||||||
|
gate: "post-commit",
|
||||||
|
message: "Coverage below threshold",
|
||||||
|
details: {
|
||||||
|
coverage: { current: 78, required: 85 },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockCoordinatorClient.checkQuality).mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await service.postCommitCheck(params);
|
||||||
|
|
||||||
|
// Should call coordinator and enforce quality gates
|
||||||
|
expect(mockCoordinatorClient.checkQuality).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// Should return coordinator rejection, not YOLO bypass
|
||||||
|
expect(result.approved).toBe(false);
|
||||||
|
expect(result.message).toBe("Coverage below threshold");
|
||||||
|
expect(result.details?.coverage).toEqual({ current: 78, required: 85 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work when NODE_ENV is not set (default to non-production)", async () => {
|
||||||
|
// Enable YOLO mode without NODE_ENV set
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also clear process.env.NODE_ENV
|
||||||
|
const originalNodeEnv = process.env.NODE_ENV;
|
||||||
|
delete process.env.NODE_ENV;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await service.preCommitCheck(params);
|
||||||
|
|
||||||
|
// Should allow YOLO mode when NODE_ENV not set
|
||||||
|
expect(mockCoordinatorClient.checkQuality).not.toHaveBeenCalled();
|
||||||
|
expect(result.approved).toBe(true);
|
||||||
|
expect(result.details?.yoloMode).toBe(true);
|
||||||
|
} finally {
|
||||||
|
// Restore NODE_ENV
|
||||||
|
process.env.NODE_ENV = originalNodeEnv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should fall back to process.env.NODE_ENV when config not set", async () => {
|
||||||
|
// Enable YOLO mode, config returns undefined but process.env is production
|
||||||
|
vi.mocked(mockConfigService.get).mockImplementation((key: string) => {
|
||||||
|
if (key === "orchestrator.yolo.enabled") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (key === "NODE_ENV") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set process.env.NODE_ENV to production
|
||||||
|
const originalNodeEnv = process.env.NODE_ENV;
|
||||||
|
process.env.NODE_ENV = "production";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const mockResponse: QualityCheckResponse = {
|
||||||
|
approved: true,
|
||||||
|
gate: "pre-commit",
|
||||||
|
};
|
||||||
|
|
||||||
|
vi.mocked(mockCoordinatorClient.checkQuality).mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await service.preCommitCheck(params);
|
||||||
|
|
||||||
|
// Should block YOLO mode (production via process.env)
|
||||||
|
expect(mockCoordinatorClient.checkQuality).toHaveBeenCalled();
|
||||||
|
expect(result.details?.yoloMode).toBeUndefined();
|
||||||
|
} finally {
|
||||||
|
// Restore NODE_ENV
|
||||||
|
process.env.NODE_ENV = originalNodeEnv;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -217,10 +217,39 @@ export class QualityGatesService {
|
|||||||
* YOLO mode bypasses all quality gates.
|
* YOLO mode bypasses all quality gates.
|
||||||
* Default: false (quality gates enabled)
|
* Default: false (quality gates enabled)
|
||||||
*
|
*
|
||||||
* @returns True if YOLO mode is enabled
|
* SECURITY: YOLO mode is blocked in production environments to prevent
|
||||||
|
* bypassing quality gates in production deployments. This is a security
|
||||||
|
* measure to ensure code quality standards are always enforced in production.
|
||||||
|
*
|
||||||
|
* @returns True if YOLO mode is enabled (always false in production)
|
||||||
*/
|
*/
|
||||||
private isYoloModeEnabled(): boolean {
|
private isYoloModeEnabled(): boolean {
|
||||||
return this.configService.get<boolean>("orchestrator.yolo.enabled") ?? false;
|
const yoloRequested = this.configService.get<boolean>("orchestrator.yolo.enabled") ?? false;
|
||||||
|
|
||||||
|
// Block YOLO mode in production
|
||||||
|
if (yoloRequested && this.isProductionEnvironment()) {
|
||||||
|
this.logger.warn(
|
||||||
|
"YOLO mode blocked in production environment - quality gates will be enforced",
|
||||||
|
{
|
||||||
|
requestedYoloMode: true,
|
||||||
|
environment: "production",
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return yoloRequested;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if running in production environment
|
||||||
|
*
|
||||||
|
* @returns True if NODE_ENV is 'production'
|
||||||
|
*/
|
||||||
|
private isProductionEnvironment(): boolean {
|
||||||
|
const nodeEnv = this.configService.get<string>("NODE_ENV") ?? process.env.NODE_ENV;
|
||||||
|
return nodeEnv === "production";
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user