import { deflateRawSync } from 'node:zlib';
import { describe, expect, it } from 'vitest';
import { inventoryGpoContainer } from '../../server/gpo/gpo-container-inventory';
import { inventoryZipArchive } from '../../server/gpo/zip-inventory';
import { ImportError } from '../../src/shared/errors';
interface SyntheticZipEntry {
path: string;
content: string | Uint8Array;
method?: 0 | 8;
declaredUncompressedBytes?: number;
crcOverride?: number;
madeBySystem?: number;
externalAttributes?: number;
}
function crc32(bytes: Uint8Array): number {
let value = 0xffffffff;
for (const byte of bytes) {
value ^= byte;
for (let bit = 0; bit < 8; bit += 1) value = (value >>> 1) ^ (0xedb88320 & -(value & 1));
}
return (value ^ 0xffffffff) >>> 0;
}
function syntheticZip(entries: SyntheticZipEntry[]): Uint8Array {
const localParts: Buffer[] = [];
const centralParts: Buffer[] = [];
let localOffset = 0;
for (const entry of entries) {
const name = Buffer.from(entry.path, 'utf8');
const content = typeof entry.content === 'string' ? Buffer.from(entry.content, 'utf8') : Buffer.from(entry.content);
const method = entry.method ?? 8;
const compressed = method === 8 ? deflateRawSync(content) : content;
const checksum = entry.crcOverride ?? crc32(content);
const declaredSize = entry.declaredUncompressedBytes ?? content.length;
const local = Buffer.alloc(30 + name.length);
local.writeUInt32LE(0x04034b50, 0);
local.writeUInt16LE(20, 4);
local.writeUInt16LE(0x0800, 6);
local.writeUInt16LE(method, 8);
local.writeUInt32LE(checksum, 14);
local.writeUInt32LE(compressed.length, 18);
local.writeUInt32LE(declaredSize, 22);
local.writeUInt16LE(name.length, 26);
name.copy(local, 30);
localParts.push(local, compressed);
const central = Buffer.alloc(46 + name.length);
central.writeUInt32LE(0x02014b50, 0);
central.writeUInt16LE(((entry.madeBySystem ?? 0) << 8) | 20, 4);
central.writeUInt16LE(20, 6);
central.writeUInt16LE(0x0800, 8);
central.writeUInt16LE(method, 10);
central.writeUInt32LE(checksum, 16);
central.writeUInt32LE(compressed.length, 20);
central.writeUInt32LE(declaredSize, 24);
central.writeUInt16LE(name.length, 28);
central.writeUInt32LE(entry.externalAttributes ?? 0, 38);
central.writeUInt32LE(localOffset, 42);
name.copy(central, 46);
centralParts.push(central);
localOffset += local.length + compressed.length;
}
const centralDirectory = Buffer.concat(centralParts);
const end = Buffer.alloc(22);
end.writeUInt32LE(0x06054b50, 0);
end.writeUInt16LE(entries.length, 8);
end.writeUInt16LE(entries.length, 10);
end.writeUInt32LE(centralDirectory.length, 12);
end.writeUInt32LE(localOffset, 16);
return Buffer.concat([...localParts, centralDirectory, end]);
}
function diagnosticCode(action: () => unknown): string | undefined {
try {
action();
} catch (error) {
return error instanceof ImportError ? error.diagnostics[0]?.code : undefined;
}
return undefined;
}
describe('server-side GPO ZIP inventory', () => {
it('inventories a synthetic GPO backup candidate without parsing or executing entries', () => {
const root = '11111111-1111-4111-8111-111111111111';
const bytes = syntheticZip([
{ path: `${root}/Backup.xml`, content: '' },
{ path: `${root}/DomainSysvol/GPO/Machine/Registry.pol`, content: new Uint8Array([80, 82, 101, 103]) },
{ path: `${root}/DomainSysvol/GPO/Machine/Scripts/startup.ps1`, content: 'Write-Output synthetic' },
{ path: `${root}/DomainSysvol/GPO/gpt.ini`, content: '[General]' }
]);
const result = inventoryGpoContainer(bytes);
expect(result.recognition).toMatchObject({
status: 'gpo-backup-candidate',
evidenceCodes: ['gpo-backup-metadata-path-observed', 'gpo-domain-sysvol-structure-observed']
});
expect(result.archive.entries.every(entry => entry.kind !== 'file' || /^[a-f0-9]{64}$/.test(entry.sha256 ?? ''))).toBe(true);
expect(result.inventory.status).toBe('complete');
expect(result.inventory.entries).toEqual(expect.arrayContaining([
expect.objectContaining({ role: 'backup-metadata', scope: 'shared', disposition: 'inventory-only', secretIndicatorStatus: 'not-inspected' }),
expect.objectContaining({ role: 'registry-policy', scope: 'computer', disposition: 'inventory-only' }),
expect.objectContaining({ role: 'script', scope: 'computer', disposition: 'inventory-only' }),
expect.objectContaining({ role: 'unknown', disposition: 'unsupported' })
]));
expect(result.inventory.diagnosticCodes).toEqual(expect.arrayContaining([
'gpo-artifact-parser-pending', 'gpo-artifact-unsupported', 'gpo-script-inventory-only'
]));
});
it('does not recognize filename-only ZIP content as a GPO backup', () => {
const result = inventoryGpoContainer(syntheticZip([{ path: 'Backup.xml', content: '' }]));
expect(result.recognition.status).toBe('unrecognized-zip');
expect(result.recognition.evidenceCodes).toContain('gpo-container-structure-unconfirmed');
});
it('rejects traversal, case collisions and file-as-directory aliases before inflation', () => {
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([{ path: '../escape.xml', content: 'x' }]))))
.toBe('zip-entry-path-rejected');
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([
{ path: 'safe/Backup.xml', content: 'a' },
{ path: 'SAFE/backup.XML', content: 'b' }
])))).toBe('zip-entry-path-collision');
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([
{ path: 'safe', content: 'file' },
{ path: 'safe/child.xml', content: 'child' }
])))).toBe('zip-entry-path-collision');
});
it('rejects symbolic links and nested ZIP content', () => {
const symlinkMode = ((0xa000 | 0o777) << 16) >>> 0;
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([{
path: 'link', content: 'target', madeBySystem: 3, externalAttributes: symlinkMode
}])))).toBe('zip-link-rejected');
const nested = syntheticZip([{ path: 'inner.txt', content: 'synthetic' }]);
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([{ path: 'nested.bin', content: nested }]))))
.toBe('zip-nested-archive-rejected');
});
it('verifies actual size, CRC and actual compression ratio', () => {
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([{
path: 'size.txt', content: 'actual', declaredUncompressedBytes: 5
}])))).toBe('zip-entry-size-mismatch');
expect(diagnosticCode(() => inventoryZipArchive(syntheticZip([{
path: 'crc.txt', content: 'actual', crcOverride: 0
}])))).toBe('zip-entry-crc-mismatch');
expect(diagnosticCode(() => inventoryZipArchive(
syntheticZip([{ path: 'ratio.txt', content: 'A'.repeat(10_000) }]),
{ maximumZipCompressionRatio: 2 }
))).toBe('zip-compression-ratio-limit');
});
});