fix: unlock Community deploy policy hard-blocking (#1643)

Remove the leftover paid-only blockingEnabled switch so enabled
block-on-deploy policies enforce on every tier, matching the
documented every-tier security surface. Existing Community policies
begin blocking immediately with no migration.
This commit is contained in:
Anso
2026-07-16 15:24:05 -04:00
committed by GitHub
parent b2576ede72
commit b91025dc8b
6 changed files with 137 additions and 34 deletions
@@ -0,0 +1,134 @@
/**
* Community deploy-policy route regressions: a matching block_on_deploy policy
* must hard-block via the stack deploy route on Community (local session and
* trusted node_proxy with x-sencho-tier: community), returning the established
* 409 payload and never calling deployStack.
*/
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from 'vitest';
import request, { type Response } from 'supertest';
import jwt from 'jsonwebtoken';
import { setupTestDb, cleanupTestDb, loginAsTestAdmin, TEST_JWT_SECRET } from './helpers/setupTestDb';
import { PROXY_TIER_HEADER } from '../services/license-headers';
vi.mock('../services/FileSystemService', () => ({
FileSystemService: {
getInstance: () => ({
getStacks: vi.fn().mockResolvedValue([]),
getBaseDir: () => '/tmp/compose',
readComposeFile: vi.fn().mockResolvedValue(''),
hasComposeFile: vi.fn().mockResolvedValue(true),
}),
},
}));
let tmpDir: string;
let app: import('express').Express;
let authCookie: string;
let listImagesSpy: ReturnType<typeof vi.spyOn>;
let deploySpy: ReturnType<typeof vi.spyOn>;
let trivyAvailableSpy: ReturnType<typeof vi.spyOn>;
let scanSpy: ReturnType<typeof vi.spyOn>;
function expectPolicyHardBlock(res: Response): void {
expect(res.status).toBe(409);
expect(res.body.error).toContain('community-block-critical');
expect(res.body.policy).toMatchObject({ name: 'community-block-critical' });
expect(res.body.violations).toEqual(
expect.arrayContaining([
expect.objectContaining({ imageRef: 'nginx:bad' }),
]),
);
expect(scanSpy).toHaveBeenCalled();
expect(deploySpy).not.toHaveBeenCalled();
}
beforeAll(async () => {
tmpDir = await setupTestDb();
({ app } = await import('../index'));
authCookie = await loginAsTestAdmin(app);
const { ComposeService } = await import('../services/ComposeService');
listImagesSpy = vi.spyOn(ComposeService.prototype, 'listStackImages').mockResolvedValue(['nginx:bad']);
deploySpy = vi.spyOn(ComposeService.prototype, 'deployStack').mockResolvedValue(undefined);
const TrivyService = (await import('../services/TrivyService')).default;
const trivy = TrivyService.getInstance();
trivyAvailableSpy = vi.spyOn(trivy, 'isTrivyAvailable').mockReturnValue(true);
scanSpy = vi.spyOn(trivy, 'scanImagePreflight').mockResolvedValue({
id: 42,
node_id: 1,
image_ref: 'nginx:bad',
image_digest: null,
scanned_at: Date.now(),
total_vulnerabilities: 1,
critical_count: 1,
high_count: 0,
medium_count: 0,
low_count: 0,
unknown_count: 0,
fixable_count: 0,
secret_count: 0,
misconfig_count: 0,
scanners_used: 'vuln',
highest_severity: 'CRITICAL',
os_info: null,
trivy_version: '0.50.0',
scan_duration_ms: null,
triggered_by: 'deploy-preflight',
status: 'completed',
error: null,
stack_context: 'community-block-app',
policy_evaluation: null,
});
const { DatabaseService } = await import('../services/DatabaseService');
DatabaseService.getInstance().createScanPolicy({
name: 'community-block-critical',
node_id: null,
node_identity: '',
stack_pattern: 'community-block-*',
max_severity: 'HIGH',
block_on_deploy: 1,
block_on_severity: 1,
block_on_kev: 0,
block_on_fixable: 0,
enabled: 1,
replicated_from_control: 0,
});
});
afterAll(() => {
listImagesSpy.mockRestore();
deploySpy.mockRestore();
trivyAvailableSpy.mockRestore();
scanSpy.mockRestore();
vi.restoreAllMocks();
cleanupTestDb(tmpDir);
});
beforeEach(() => {
deploySpy.mockClear();
listImagesSpy.mockClear();
scanSpy.mockClear();
});
describe('Community deploy policy hard-block (route)', () => {
it('returns 409 and skips deployStack for a local Community session', async () => {
const res = await request(app)
.post('/api/stacks/community-block-app/deploy')
.set('Cookie', authCookie);
expectPolicyHardBlock(res);
});
it('returns 409 and skips deployStack for trusted node_proxy Community tier', async () => {
const token = jwt.sign({ scope: 'node_proxy' }, TEST_JWT_SECRET, { expiresIn: '1m' });
const res = await request(app)
.post('/api/stacks/community-block-proxy/deploy')
.set('Authorization', `Bearer ${token}`)
.set(PROXY_TIER_HEADER, 'community');
expectPolicyHardBlock(res);
});
});
@@ -1216,12 +1216,10 @@ describe('GitSourceService.apply', () => {
const validateSpy = vi.spyOn(svc, 'validateCompose').mockResolvedValue({ ok: true });
const { FileSystemService } = await import('../services/FileSystemService');
const { ComposeService } = await import('../services/ComposeService');
const { LicenseService } = await import('../services/LicenseService');
const TrivyService = (await import('../services/TrivyService')).default;
const saveSpy = vi.spyOn(FileSystemService.prototype, 'saveStackContent').mockResolvedValue();
const listImagesSpy = vi.spyOn(ComposeService.prototype, 'listStackImages').mockResolvedValue(['nginx:bad']);
const deploySpy = vi.spyOn(ComposeService.prototype, 'deployStack').mockResolvedValue();
const tierSpy = vi.spyOn(LicenseService.getInstance(), 'getTier').mockReturnValue('paid');
const trivy = TrivyService.getInstance();
const trivyAvailableSpy = vi.spyOn(trivy, 'isTrivyAvailable').mockReturnValue(true);
const scanSpy = vi.spyOn(trivy, 'scanImagePreflight').mockResolvedValue({
@@ -1267,13 +1265,13 @@ describe('GitSourceService.apply', () => {
expect(result.applied).toBe(true);
expect(result.deployed).toBe(false);
expect(result.deployError).toContain('Policy "block-high" blocked deploy');
expect(scanSpy).toHaveBeenCalled();
expect(deploySpy).not.toHaveBeenCalled();
validateSpy.mockRestore();
saveSpy.mockRestore();
listImagesSpy.mockRestore();
deploySpy.mockRestore();
tierSpy.mockRestore();
trivyAvailableSpy.mockRestore();
scanSpy.mockRestore();
});
@@ -175,21 +175,6 @@ describe('enforcePolicyPreDeploy', () => {
expect(composeStub.listStackImages).not.toHaveBeenCalled();
});
it('allows deploy without scanning when paid-tier blocking is disabled', async () => {
dbStub.getMatchingPolicy.mockReturnValue(mkPolicy());
const result = await enforcePolicyPreDeploy('web', 1, {
bypass: false,
actor: 'u',
blockingEnabled: false,
});
expect(result.ok).toBe(true);
expect(result.violations).toEqual([]);
expect(trivyStub.isTrivyAvailable).not.toHaveBeenCalled();
expect(composeStub.listStackImages).not.toHaveBeenCalled();
});
it('fails open with a warning alert when Trivy is not installed', async () => {
dbStub.getMatchingPolicy.mockReturnValue(mkPolicy());
trivyStub.isTrivyAvailable.mockReturnValue(false);