mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 09:55:29 +02:00
* feat(cso): add verified audits and replayable repair bundles * fix(cso): harden qualification and setup boundaries * fix(cso): assemble security canaries at runtime * fix(cso): bound release proof and maintenance work Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): require complete evaluation reports Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): replay expired snapshots from supplied source Co-Authored-By: OpenAI Codex <noreply@openai.com> * test(cso): synchronize DNS cancellation assertion Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore(ship): exempt repository owner from liveness proof Co-Authored-By: OpenAI Codex <noreply@openai.com> * test(cso): make recheck retention overlap deterministic Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: bump version and changelog (v1.85.0.0) Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): pass native release gates Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: move release to v1.86.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): resolve rechecks by finding Co-Authored-By: OpenAI Codex <noreply@openai.com> * chore: move release to v1.87.0.0 Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): pass macOS and Windows release gates Normalize BSD wc output, compare Windows paths by filesystem identity, preserve portable snapshot race coverage, and narrow POSIX-only Windows fixtures. Co-Authored-By: OpenAI Codex <noreply@openai.com> * fix(cso): harden native verification gates * fix(cso): refine Windows native diagnostics * test(cso): isolate Windows Git startup failure * test(cso): stabilize Windows native diagnostics * fix(cso): support hardened Git on Windows * fix(cso): close final verification gaps * test(cso): bound cold Docker fixture setup * fix(cso): restore cross-platform free-suite gates --------- Co-authored-by: OpenAI Codex <noreply@openai.com>
146 lines
8.1 KiB
TypeScript
146 lines
8.1 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import * as fs from 'node:fs';
|
|
import * as os from 'node:os';
|
|
import * as path from 'node:path';
|
|
import {
|
|
assertPublicPackageMetadata,
|
|
parsePublicGhcrTarget,
|
|
verifyPublicGhcrImage,
|
|
type PublicGhcrDependencies,
|
|
} from '../scripts/cso-public-ghcr';
|
|
|
|
const HASH = 'a'.repeat(64);
|
|
const IMAGE = `ghcr.io/garrytan/gstack/cso-staging/node-amd64@sha256:${HASH}`;
|
|
|
|
describe('CSO public GHCR release proof', () => {
|
|
test('binds an exact CSO package and digest to its declared platform', () => {
|
|
expect(parsePublicGhcrTarget(IMAGE, 'linux/amd64', 'GarryTan/GStack')).toEqual({
|
|
image: IMAGE,
|
|
owner: 'garrytan',
|
|
repository: 'gstack',
|
|
packageName: 'gstack/cso-staging/node-amd64',
|
|
digest: `sha256:${HASH}`,
|
|
platform: 'linux/amd64',
|
|
});
|
|
expect(parsePublicGhcrTarget(
|
|
`ghcr.io/garrytan/gstack/cso-scanners/semgrep-arm64@sha256:${HASH}`,
|
|
'linux/arm64',
|
|
'garrytan/gstack',
|
|
).packageName).toBe('gstack/cso-scanners/semgrep-arm64');
|
|
});
|
|
|
|
test('rejects tags, foreign repositories, unknown packages, and platform substitution', () => {
|
|
for (const [image, platform, repository] of [
|
|
['ghcr.io/garrytan/gstack/cso-staging/node-amd64:latest', 'linux/amd64', 'garrytan/gstack'],
|
|
[IMAGE.replace('garrytan/gstack', 'attacker/gstack'), 'linux/amd64', 'garrytan/gstack'],
|
|
[IMAGE.replace('node-amd64', 'unknown-amd64'), 'linux/amd64', 'garrytan/gstack'],
|
|
[IMAGE, 'linux/arm64', 'garrytan/gstack'],
|
|
]) expect(() => parsePublicGhcrTarget(image, platform, repository)).toThrow();
|
|
});
|
|
|
|
test('accepts only exact public container metadata for the expected owner', () => {
|
|
const target = parsePublicGhcrTarget(IMAGE, 'linux/amd64', 'garrytan/gstack');
|
|
const valid = { name: target.packageName, package_type: 'container', visibility: 'public', owner: { login: 'GarryTan' } };
|
|
expect(() => assertPublicPackageMetadata(valid, target)).not.toThrow();
|
|
for (const mutation of [
|
|
{ ...valid, visibility: 'private' },
|
|
{ ...valid, name: 'gstack/cso-staging/other-amd64' },
|
|
{ ...valid, package_type: 'npm' },
|
|
{ ...valid, owner: { login: 'attacker' } },
|
|
]) expect(() => assertPublicPackageMetadata(mutation, target)).toThrow('GHCR_PACKAGE_IS_NOT_PUBLIC');
|
|
});
|
|
|
|
test('pulls through an isolated empty Docker config and strips ambient credentials', async () => {
|
|
const calls: Array<{ args: string[]; env: Record<string, string> }> = [];
|
|
const fetch = (async (url: string | URL | Request, init?: RequestInit) => {
|
|
expect((init?.headers as Record<string, string>).Authorization).toBe('Bearer metadata-token');
|
|
const value = String(url).includes('/packages/container/')
|
|
? { name: 'gstack/cso-staging/node-amd64', package_type: 'container', visibility: 'public', owner: { login: 'garrytan' } }
|
|
: { login: 'garrytan', type: 'User' };
|
|
return new Response(JSON.stringify(value), { status: 200 });
|
|
}) as typeof globalThis.fetch;
|
|
const runDocker: PublicGhcrDependencies['runDocker'] = async (args, env) => {
|
|
calls.push({ args, env });
|
|
const configRoot = args[args.indexOf('--config') + 1];
|
|
expect(fs.readFileSync(`${configRoot}/config.json`, 'utf8')).toBe('{"auths":{}}\n');
|
|
expect(env).not.toHaveProperty('GH_TOKEN');
|
|
expect(env).not.toHaveProperty('GITHUB_TOKEN');
|
|
expect(env.HOME).toBe(configRoot);
|
|
expect(env.DOCKER_CONFIG).toBe(configRoot);
|
|
if (args.includes('inspect')) return { exitCode: 0, stdout: JSON.stringify([IMAGE]), stderr: '' };
|
|
return { exitCode: 0, stdout: IMAGE, stderr: '' };
|
|
};
|
|
const proof = await verifyPublicGhcrImage(
|
|
{ image: IMAGE, platform: 'linux/amd64', githubRepository: 'garrytan/gstack', removeAfter: true },
|
|
{ fetch, githubToken: 'metadata-token', dockerPath: '/usr/bin/docker', runDocker, now: () => '2026-09-11T00:00:00.000Z', sleep: async () => {} },
|
|
);
|
|
expect(proof).toMatchObject({ image: IMAGE, packageVisibility: 'public', anonymousPull: 'passed', dockerConfig: 'isolated-empty-auths' });
|
|
expect(calls).toHaveLength(3);
|
|
expect(calls[0].args).toContain('pull');
|
|
expect(calls[0].args).toContain('--quiet');
|
|
expect(calls[2].args).toContain('rm');
|
|
});
|
|
|
|
test('fails closed before pulling a non-public package or accepting another digest', async () => {
|
|
let dockerCalls = 0;
|
|
const privateFetch = (async (url: string | URL | Request) => new Response(JSON.stringify(
|
|
String(url).includes('/packages/container/')
|
|
? { name: 'gstack/cso-staging/node-amd64', package_type: 'container', visibility: 'private', owner: { login: 'garrytan' } }
|
|
: { login: 'garrytan', type: 'User' },
|
|
), { status: 200 })) as typeof globalThis.fetch;
|
|
await expect(verifyPublicGhcrImage(
|
|
{ image: IMAGE, platform: 'linux/amd64', githubRepository: 'garrytan/gstack' },
|
|
{ fetch: privateFetch, githubToken: 'metadata-token', dockerPath: '/usr/bin/docker', runDocker: async () => { dockerCalls++; return { exitCode: 0, stdout: '', stderr: '' }; }, sleep: async () => {} },
|
|
)).rejects.toThrow('GHCR_PACKAGE_IS_NOT_PUBLIC');
|
|
expect(dockerCalls).toBe(0);
|
|
|
|
const publicFetch = (async (url: string | URL | Request) => new Response(JSON.stringify(
|
|
String(url).includes('/packages/container/')
|
|
? { name: 'gstack/cso-staging/node-amd64', package_type: 'container', visibility: 'public', owner: { login: 'garrytan' } }
|
|
: { login: 'garrytan', type: 'User' },
|
|
), { status: 200 })) as typeof globalThis.fetch;
|
|
await expect(verifyPublicGhcrImage(
|
|
{ image: IMAGE, platform: 'linux/amd64', githubRepository: 'garrytan/gstack' },
|
|
{ fetch: publicFetch, githubToken: 'metadata-token', dockerPath: '/usr/bin/docker', runDocker: async args => args.includes('inspect')
|
|
? { exitCode: 0, stdout: JSON.stringify([IMAGE.replace(HASH, 'b'.repeat(64))]), stderr: '' }
|
|
: { exitCode: 0, stdout: '', stderr: '' }, sleep: async () => {} },
|
|
)).rejects.toThrow('ANONYMOUS_IMAGE_DIGEST_MISMATCH');
|
|
});
|
|
|
|
test('requires scoped GitHub authentication for visibility metadata without passing it to Docker', async () => {
|
|
let fetched = false, pulled = false;
|
|
await expect(verifyPublicGhcrImage(
|
|
{ image: IMAGE, platform: 'linux/amd64', githubRepository: 'garrytan/gstack' },
|
|
{
|
|
githubToken: '',
|
|
dockerPath: '/usr/bin/docker',
|
|
fetch: (async () => { fetched = true; return new Response('{}'); }) as typeof globalThis.fetch,
|
|
runDocker: async () => { pulled = true; return { exitCode: 0, stdout: '', stderr: '' }; },
|
|
},
|
|
)).rejects.toThrow('GITHUB_PACKAGE_METADATA_TOKEN_REQUIRED');
|
|
expect(fetched).toBe(false);
|
|
expect(pulled).toBe(false);
|
|
});
|
|
|
|
// The public-image release verifier runs in Linux CI. This case exercises
|
|
// its process-kill path with a POSIX shebang fixture; Windows CreateProcess
|
|
// cannot execute that fixture, while the metadata contract above remains
|
|
// portable and continues to run in the curated Windows lane.
|
|
test.skipIf(process.platform === 'win32')('kills a Docker client as soon as bounded output exceeds the release limit', async () => {
|
|
const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'cso-public-docker-test-'));
|
|
const docker = path.join(directory, 'docker');
|
|
fs.writeFileSync(docker, '#!/bin/sh\npython3 -c "import sys; sys.stdout.write(chr(120) * 70000)"\n', { mode: 0o700 });
|
|
const fetch = (async (url: string | URL | Request) => new Response(JSON.stringify(
|
|
String(url).includes('/packages/container/')
|
|
? { name: 'gstack/cso-staging/node-amd64', package_type: 'container', visibility: 'public', owner: { login: 'garrytan' } }
|
|
: { login: 'garrytan', type: 'User' },
|
|
), { status: 200 })) as typeof globalThis.fetch;
|
|
try {
|
|
await expect(verifyPublicGhcrImage(
|
|
{ image: IMAGE, platform: 'linux/amd64', githubRepository: 'garrytan/gstack' },
|
|
{ fetch, githubToken: 'metadata-token', dockerPath: docker, sleep: async () => {} },
|
|
)).rejects.toThrow('ANONYMOUS_DOCKER_OUTPUT_LIMIT');
|
|
} finally { fs.rmSync(directory, { recursive: true, force: true }); }
|
|
});
|
|
});
|