harden runtime packaging and verification

This commit is contained in:
Sinabina
2026-07-17 12:09:49 -07:00
parent 20d2840bd3
commit d7357c288f
36 changed files with 801 additions and 265 deletions
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bun
import { createHash } from 'node:crypto';
import { execFileSync } from 'node:child_process';
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { pathToFileURL } from 'node:url';
import {
DEFAULT_CAPABILITY_LAUNCHERS,
installManagedRuntime,
runtimeNativePackagePaths,
} from '../../runtime/install.js';
import { atomicWriteJson } from '../../runtime/storage.js';
const REPO_ROOT = path.resolve(import.meta.dir, '../..');
const FORBIDDEN_COMPONENT = /browserbase|browserless|huggingface|onnxruntime|claude-agent-sdk/i;
export interface RuntimeBundleAudit {
schemaVersion: 1;
platform: string;
arch: string;
sourceBundleVersion: string;
generatedAt: string;
sourceGitCommit: string;
sourceGitDirty: boolean;
components: number;
files: number;
bytes: number;
capabilityLaunchers: number;
nativeComponents: string[];
forbiddenComponents: string[];
bundleManifestSha256: string;
reproductionCommand: string;
}
export function summarizeRuntimeBundle(
manifest: Record<string, unknown> & {
version: string;
components: string[];
files: Array<{ path: string; size: number; mode: number; sha256: string }>;
},
): RuntimeBundleAudit {
const nativeComponents = runtimeNativePackagePaths();
const forbiddenComponents = manifest.components.filter((component) => FORBIDDEN_COMPONENT.test(component));
const digestInput = JSON.stringify(manifest);
return {
schemaVersion: 1,
platform: process.platform,
arch: process.arch,
sourceBundleVersion: manifest.version,
generatedAt: new Date().toISOString(),
sourceGitCommit: execFileSync('git', ['rev-parse', 'HEAD'], { cwd: REPO_ROOT, encoding: 'utf8' }).trim(),
sourceGitDirty: execFileSync(
'git',
['status', '--porcelain', '--untracked-files=all'],
{ cwd: REPO_ROOT, encoding: 'utf8' },
).trim().length > 0,
components: manifest.components.length,
files: manifest.files.length,
bytes: manifest.files.reduce((total, file) => total + file.size, 0),
capabilityLaunchers: Object.keys(DEFAULT_CAPABILITY_LAUNCHERS).length,
nativeComponents: [...nativeComponents],
forbiddenComponents,
bundleManifestSha256: createHash('sha256').update(digestInput).digest('hex'),
reproductionCommand: `bun run scripts/gstack2/audit-runtime-bundle.ts --output evals/runtime-bundle/${process.platform}-${process.arch}.json`,
};
}
async function main(argv = process.argv.slice(2)): Promise<void> {
const outputIndex = argv.indexOf('--output');
if (argv.length !== 0 && (outputIndex !== 0 || argv.length !== 2 || !argv[1])) {
throw new TypeError('Usage: audit-runtime-bundle.ts [--output <path>]');
}
const scratch = await fs.mkdtemp(path.join(os.tmpdir(), 'gstack2-runtime-bundle-audit-'));
try {
const result = await installManagedRuntime({
sourceDir: REPO_ROOT,
home: path.join(scratch, 'home'),
buildMissing: false,
});
const manifest = JSON.parse(await fs.readFile(path.join(result.path, '.gstack-bundle.json'), 'utf8'));
const audit = summarizeRuntimeBundle(manifest);
if (audit.forbiddenComponents.length > 0) {
throw new Error(`Forbidden production components: ${audit.forbiddenComponents.join(', ')}`);
}
if (outputIndex === 0) {
const output = path.resolve(REPO_ROOT, argv[1]);
await atomicWriteJson(output, audit, { mode: 0o644 });
}
process.stdout.write(`${JSON.stringify(audit, null, 2)}\n`);
} finally {
await fs.rm(scratch, { recursive: true, force: true });
}
}
const invokedPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : null;
if (invokedPath === import.meta.url) await main();
+46
View File
@@ -1,5 +1,51 @@
#!/usr/bin/env bash
set -euo pipefail
umask 077
SOURCE="$(cd "${1:-$PWD}" && pwd -P)"
ROOT="$(mktemp -d /tmp/gstack2-devcontainer-gate.XXXXXX)"
ROOT="$(cd "$ROOT" && pwd -P)"
REPO="$ROOT/source"
cleanup() {
rm -rf -- "$ROOT"
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
mkdir -p "$REPO"
tar -C "$SOURCE" \
--exclude='./.git' \
--exclude='./node_modules' \
-cf - . \
| tar -C "$REPO" -xf -
# The parity and generated-file checks need the checkout's Git history. Point
# the disposable worktree at the read-only source metadata, while leaving all
# Git commands free to discover fixture repositories normally.
REAL_GIT="${GSTACK_GATE_BASE_GIT:-$(command -v git)}"
if SOURCE_GIT_DIR="$("$REAL_GIT" -c safe.directory="$SOURCE" -C "$SOURCE" rev-parse --absolute-git-dir 2>/dev/null)"; then
printf 'gitdir: %s\n' "$SOURCE_GIT_DIR" > "$REPO/.git"
GIT_WRAPPER_DIR="$ROOT/bin"
mkdir -p "$GIT_WRAPPER_DIR"
export GSTACK_GATE_BASE_GIT="$REAL_GIT"
export GSTACK_GATE_SOURCE="$SOURCE"
export GSTACK_GATE_WORK_TREE="$REPO"
export GIT_OPTIONAL_LOCKS=0
export PATH="$GIT_WRAPPER_DIR:$PATH"
cat > "$GIT_WRAPPER_DIR/git" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
exec "$GSTACK_GATE_BASE_GIT" \
-c safe.directory="$GSTACK_GATE_SOURCE" \
-c safe.directory="$GSTACK_GATE_WORK_TREE" \
"$@"
EOF
chmod 700 "$GIT_WRAPPER_DIR/git"
fi
cd "$REPO"
bun install --frozen-lockfile
bun run test:gstack2
+5 -5
View File
@@ -541,10 +541,6 @@ interface RenderedModuleRecord {
disposition: string;
}
function referencedModules(content: string): string[] {
return [...new Set([...content.matchAll(/references\/legacy\/([a-z0-9-]+)\.md/g)].map((match) => match[1]))].sort();
}
/**
* Compute the complete module graph for each independently installable public
* skill. Owner modules are roots because compatibility aliases may select any
@@ -570,7 +566,11 @@ function packageModuleClosure(rendered: Map<string, RenderedModuleRecord>): Map<
for (const source of [...sources]) {
const module = rendered.get(source);
if (!module) throw new Error(`${tree} references unknown preserved module ${source}`);
for (const dependency of referencedModules(module.content)) {
const dependencies = new Set(
[...module.content.matchAll(/references\/legacy\/([a-z0-9-]+)\.md/g)]
.map((match) => match[1]),
);
for (const dependency of [...dependencies].sort()) {
if (!rendered.has(dependency)) throw new Error(`${source} references unknown preserved module ${dependency}`);
if (!sources.has(dependency)) {
sources.add(dependency);
+9 -4
View File
@@ -645,10 +645,13 @@ export function validateStructuredResult(value: unknown): value is StructuredHos
function parseJsonCandidate(text: string): unknown {
const trimmed = text.trim();
try { return JSON.parse(trimmed); } catch { /* try a fenced payload */ }
const fenced = trimmed.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i)?.[1];
if (fenced) return JSON.parse(fenced);
throw new Error('final agent message was not JSON');
try {
return JSON.parse(fenced ?? trimmed);
} catch (error) {
if (error instanceof SyntaxError) throw new Error('final agent message was not JSON');
throw error;
}
}
export function parseStructuredFinal(messages: string[]): {
@@ -842,7 +845,9 @@ function stageAuthentication(codexHome: string): void {
if (!fs.existsSync(source)) return;
const destination = path.join(codexHome, 'auth.json');
fs.copyFileSync(source, destination);
try { fs.chmodSync(destination, 0o600); } catch { /* Windows and restrictive filesystems may ignore chmod. */ }
// Windows has no POSIX owner-only mode. On POSIX, failing to secure copied
// credentials is fatal rather than silently continuing with broad access.
if (os.platform() !== 'win32') fs.chmodSync(destination, 0o600);
}
async function runFixture(options: {
+2 -4
View File
@@ -221,9 +221,7 @@ function policyUnitTranscript() {
});
}
function containsSensitiveMaterial(value: string): boolean {
return /(?:sk-[A-Za-z0-9_-]{12,}|AKIA[0-9A-Z]{16}|gh[opusr]_[A-Za-z0-9]{20,}|-----BEGIN [A-Z ]+PRIVATE KEY-----)/.test(value);
}
const SENSITIVE_MATERIAL = /(?:sk-[A-Za-z0-9_-]{12,}|AKIA[0-9A-Z]{16}|gh[opusr]_[A-Za-z0-9]{20,}|-----BEGIN [A-Z ]+PRIVATE KEY-----)/;
function sanitizeLivePrompt(value: string): string {
return value
@@ -237,7 +235,7 @@ const LIVE_OUTPUT_SCHEMA = `Return one JSON object and no prose with exactly the
const LIVE_JUDGE_SCHEMA = `Return one JSON object and no prose with fields verdict and dimensions. verdict must be EQUIVALENT, INTENTIONAL_IMPROVEMENT, or REGRESSION. dimensions must be an object with exactly these keys: ${SEMANTIC_DIMENSIONS.join(', ')}. Each dimension value must be an object with classification (one of the same three values) and a concise reason. Treat any loss of pressure, gates, evidence, mutation restraint, recommendation, or voice as REGRESSION. Do not call tools.`;
async function runClaude(prompt: string, model: string, maxBudgetUsd: number): Promise<{ raw: string; parsed: Record<string, string> }> {
if (containsSensitiveMaterial(prompt)) throw new Error('Refusing live semantic eval: prompt matched a credential pattern');
if (SENSITIVE_MATERIAL.test(prompt)) throw new Error('Refusing live semantic eval: prompt matched a credential pattern');
const proc = Bun.spawn([
'claude', '-p', '--bare', '--no-session-persistence', '--disable-slash-commands', '--no-chrome',
'--model', model, '--max-turns', '1', '--max-budget-usd', maxBudgetUsd.toFixed(2),
+9 -8
View File
@@ -344,11 +344,14 @@ function listInstalledSkills(root: string): string[] {
}
export function stripTerminalControls(value: string): string {
return value
.replace(/\x1B\][^\x07]*(?:\x07|\x1B\\)/g, '')
.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, '')
.replace(/\r(?=[^\n])/g, '')
.trim();
const terminalControls = [
/\x1B\][^\x07]*(?:\x07|\x1B\\)/g,
/\x1B\[[0-?]*[ -/]*[@-~]/g,
/\r(?=[^\n])/g,
];
let clean = value;
for (const control of terminalControls) clean = clean.replace(control, '');
return clean.trim();
}
function trimEvidenceOutput(value: string, maxCharacters = 16_000): string {
@@ -555,9 +558,7 @@ function runRemoval(options: {
};
}
export function runFastChecks(repoRoot = DEFAULT_REPO_ROOT): RepositoryInspection {
return inspectRepository(repoRoot);
}
export const runFastChecks = inspectRepository;
export function runFullMatrix(options: FullMatrixOptions): InstallMatrixEvidence {
if (!options.outputPath) throw new Error('Full install matrix requires a caller-supplied outputPath');