fix(genbi): harden context loader package verification

This commit is contained in:
Jax Liu
2026-09-04 21:41:39 +08:00
parent 5089df372c
commit 19c18ac2f8
8 changed files with 231 additions and 30 deletions
+4
View File
@@ -74,6 +74,10 @@ jobs:
working-directory: .
run: pnpm install --frozen-lockfile
- name: Test context-loader installer contract
working-directory: .
run: pnpm --dir apps/context-loader test
- name: Typecheck
run: pnpm run typecheck
+62 -11
View File
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { existsSync, readFileSync } from "node:fs";
import { lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
@@ -28,25 +28,31 @@ export function packageRootFrom(moduleUrl = import.meta.url) {
}
export function readVerifiedState(root = packageRootFrom()) {
const packageJson = readJson(path.join(root, "package.json"), "package.json");
const verifiedRoot = verifiedPackageRoot(root);
const packageJson = readPackageJson(verifiedRoot);
if (packageJson.name !== PACKAGE_NAME || typeof packageJson.version !== "string") {
throw new ContextLoaderPackageError("invalid-package", "package.json does not identify this package");
}
const state = readJson(path.join(root, STATE_FILE), STATE_FILE);
const manifest = readManifest(verifiedRoot, packageJson.version);
const state = readJson(verifiedRegularFile(verifiedRoot, STATE_FILE), STATE_FILE);
const target = targetFor();
if (state.package !== PACKAGE_NAME || state.version !== packageJson.version || state.target !== target) {
throw new ContextLoaderPackageError("stale-state", "verification record does not match package version or platform");
const artifact = manifest.artifacts?.[target];
if (!isArtifact(artifact)) {
throw new ContextLoaderPackageError("invalid-manifest", "artifact manifest has no safe row for this platform");
}
if (!isSha256(state.binarySha256)) {
throw new ContextLoaderPackageError("invalid-state", "verification record has no binary SHA-256");
if (
state.package !== PACKAGE_NAME ||
state.version !== packageJson.version ||
state.target !== target ||
state.archiveSha256 !== artifact.archiveSha256 ||
state.binarySha256 !== artifact.binarySha256
) {
throw new ContextLoaderPackageError("stale-state", "verification record does not match package version or platform");
}
if (state.binaryPath !== CANONICAL_BINARY) {
throw new ContextLoaderPackageError("invalid-state", "verification record does not name the canonical package-local binary");
}
const binary = path.resolve(root, state.binaryPath);
if (!binary.startsWith(`${root}${path.sep}`) || !existsSync(binary)) {
throw new ContextLoaderPackageError("missing-binary", "verified package-local binary is absent");
}
const binary = verifiedRegularFile(verifiedRoot, state.binaryPath);
if (sha256File(binary) !== state.binarySha256) {
throw new ContextLoaderPackageError("binary-digest-mismatch", "verified package-local binary was modified");
}
@@ -68,3 +74,48 @@ export function readJson(file, label) {
export function isSha256(value) {
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
}
function verifiedPackageRoot(root) {
try {
if (!lstatSync(root).isDirectory()) throw new Error("not a directory");
return realpathSync(root);
} catch {
throw new ContextLoaderPackageError("invalid-package", "package root is missing or is not a real directory");
}
}
function readPackageJson(root) {
return readJson(verifiedRegularFile(root, "package.json"), "package.json");
}
function readManifest(root, version) {
const manifest = readJson(verifiedRegularFile(root, "artifacts.json"), "artifacts.json");
if (manifest.schema !== 1 || manifest.package !== PACKAGE_NAME || manifest.version !== version || typeof manifest.artifacts !== "object" || manifest.artifacts === null) {
throw new ContextLoaderPackageError("invalid-manifest", "artifact manifest does not match package identity");
}
return manifest;
}
function isArtifact(artifact) {
return typeof artifact === "object" && artifact !== null &&
typeof artifact.url === "string" && artifact.url.startsWith("https://") &&
isSha256(artifact.archiveSha256) && isSha256(artifact.binarySha256) &&
typeof artifact.binaryPath === "string" &&
!artifact.binaryPath.includes("..") && !path.isAbsolute(artifact.binaryPath);
}
function verifiedRegularFile(root, relativePath) {
const lexicalRoot = path.resolve(root);
const lexicalFile = path.resolve(lexicalRoot, relativePath);
if (!lexicalFile.startsWith(`${lexicalRoot}${path.sep}`)) {
throw new ContextLoaderPackageError("invalid-state", "verification record escapes the package root");
}
try {
if (!lstatSync(lexicalFile).isFile()) throw new Error("not a regular file");
const physicalFile = realpathSync(lexicalFile);
if (!physicalFile.startsWith(`${root}${path.sep}`) || !statSync(physicalFile).isFile()) throw new Error("not contained");
return physicalFile;
} catch {
throw new ContextLoaderPackageError("missing-binary", "verified package-local file is absent, linked, or outside the package root");
}
}
+21 -1
View File
@@ -1,5 +1,5 @@
import { createHash } from "node:crypto";
import { chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import { access, chmod, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
import path from "node:path";
import { gunzipSync } from "node:zlib";
import { CANONICAL_BINARY, ContextLoaderPackageError, isSha256, readJson, sha256File, targetFor } from "../lib/verified.mjs";
@@ -45,6 +45,26 @@ export async function installContextLoader({ packageRoot, fetchImpl = fetch, pla
}
}
/**
* The checked-out source package carries an intentionally empty, pre-release
* artifact template. Only that exact repository layout skips postinstall;
* packed packages always download and verify their tagged artifact.
*/
export async function isRepositorySourcePackage(packageRoot) {
const repositoryRoot = path.resolve(packageRoot, "..", "..");
if (path.resolve(packageRoot) !== path.join(repositoryRoot, "apps", "context-loader")) return false;
try {
await Promise.all([
access(path.join(repositoryRoot, ".git")),
access(path.join(repositoryRoot, "pnpm-workspace.yaml")),
access(path.join(repositoryRoot, "core", "wren", "pyproject.toml")),
]);
return true;
} catch {
return false;
}
}
function validateManifest(packageJson, manifest, artifact, target) {
if (manifest.schema !== 1 || manifest.package !== packageJson.name || manifest.version !== packageJson.version) {
throw new ContextLoaderPackageError("invalid-manifest", "manifest does not match package identity");
+3 -1
View File
@@ -1,8 +1,10 @@
import path from "node:path";
import { fileURLToPath } from "node:url";
import { installContextLoader } from "./installer.mjs";
import { installContextLoader, isRepositorySourcePackage } from "./installer.mjs";
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
if (await isRepositorySourcePackage(packageRoot)) process.exit(0);
try {
await installContextLoader({ packageRoot });
} catch (error) {
+36 -3
View File
@@ -1,11 +1,11 @@
import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { mkdtemp, readFile, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { gzipSync } from "node:zlib";
import { installContextLoader } from "../scripts/installer.mjs";
import { installContextLoader, isRepositorySourcePackage } from "../scripts/installer.mjs";
import { readVerifiedState } from "../lib/verified.mjs";
const sha256 = (content) => createHash("sha256").update(content).digest("hex");
@@ -50,7 +50,7 @@ test("installs a digest-verified darwin-arm64 binary atomically and records cano
assert.equal(first.reused, false);
assert.deepEqual(await readFile(first.binary), artifact);
const state = readVerifiedState(root);
assert.equal(state.binary, first.binary);
assert.equal(state.binary, await realpath(first.binary));
assert.match(state.identity, /package:@wrenai\/context-loader@0\.1\.0:darwin-arm64:[a-f0-9]{64}/);
const second = await installContextLoader({ packageRoot: root, fetchImpl: async () => { throw new Error("offline"); }, platform: "darwin", arch: "arm64" });
assert.equal(second.reused, true);
@@ -76,3 +76,36 @@ test("rejects stale or altered installed state at runtime", async () => {
await writeFile(statePath, JSON.stringify({ ...state, version: "0.1.1" }));
assert.throws(() => readVerifiedState(root), /stale-state/);
});
test("rejects final-file and ancestor-directory symlinks at runtime", async () => {
const finalLink = await fixture();
const installed = await installContextLoader({ packageRoot: finalLink.root, fetchImpl: fetchArchive(finalLink.archive), platform: "darwin", arch: "arm64" });
const external = path.join(await mkdtemp(path.join(os.tmpdir(), "context-loader-external-")), "wren-context-loader");
await writeFile(external, await readFile(installed.binary), { mode: 0o755 });
await rm(installed.binary);
await symlink(external, installed.binary);
assert.throws(() => readVerifiedState(finalLink.root), /linked|outside/);
const ancestorLink = await fixture();
const installedAncestor = await installContextLoader({ packageRoot: ancestorLink.root, fetchImpl: fetchArchive(ancestorLink.archive), platform: "darwin", arch: "arm64" });
const externalDir = await mkdtemp(path.join(os.tmpdir(), "context-loader-external-dir-"));
await writeFile(path.join(externalDir, "wren-context-loader"), await readFile(installedAncestor.binary), { mode: 0o755 });
await rm(path.join(ancestorLink.root, "bin"), { recursive: true });
await symlink(externalDir, path.join(ancestorLink.root, "bin"));
assert.throws(() => readVerifiedState(ancestorLink.root), /linked|outside/);
});
test("skips only the verifiable source-workspace layout, never an equivalent packed copy", async () => {
const repositoryRoot = await mkdtemp(path.join(os.tmpdir(), "context-loader-source-"));
const sourcePackage = path.join(repositoryRoot, "apps", "context-loader");
await mkdir(path.join(repositoryRoot, "core", "wren"), { recursive: true });
await mkdir(sourcePackage, { recursive: true });
await writeFile(path.join(repositoryRoot, ".git"), "gitdir: fixture\n");
await writeFile(path.join(repositoryRoot, "pnpm-workspace.yaml"), "packages: []\n");
await writeFile(path.join(repositoryRoot, "core", "wren", "pyproject.toml"), "[project]\nname = \"wren\"\n");
assert.equal(await isRepositorySourcePackage(sourcePackage), true);
const packedCopy = path.join(repositoryRoot, "node_modules", "@wrenai", "context-loader");
await mkdir(packedCopy, { recursive: true });
assert.equal(await isRepositorySourcePackage(packedCopy), false);
});
+65 -11
View File
@@ -1,5 +1,5 @@
import { execFile } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { existsSync, lstatSync, readFileSync, realpathSync, statSync } from "node:fs";
import { createHash } from "node:crypto";
import { fileURLToPath } from "node:url";
import path from "node:path";
@@ -97,37 +97,91 @@ function resolveVerifiedPackageBinary(): ResolvedContextLoader | undefined {
/** Validates a package-local installation record without resolving it from the host environment. */
export function readVerifiedContextLoaderPackage(packageRoot: string, target = targetForCurrentPlatform()): ResolvedContextLoader | undefined {
try {
const packageJson = readJson(path.join(packageRoot, "package.json"));
const state = readJson(path.join(packageRoot, "install-state.json"));
const bin = path.resolve(packageRoot, CANONICAL_PACKAGE_BINARY);
const root = physicalPackageRoot(packageRoot);
if (root === undefined) return undefined;
const packageJson = readVerifiedJson(root, "package.json");
const manifest = readVerifiedJson(root, "artifacts.json");
const state = readVerifiedJson(root, "install-state.json");
if (packageJson === undefined || manifest === undefined || state === undefined) return undefined;
const artifacts = typeof manifest.artifacts === "object" && manifest.artifacts !== null ? manifest.artifacts as Record<string, unknown> : undefined;
const artifact = artifacts?.[target];
const bin = physicalRegularFile(root, CANONICAL_PACKAGE_BINARY);
if (
packageJson.name !== CONTEXT_LOADER_PACKAGE ||
typeof packageJson.version !== "string" ||
manifest.schema !== 1 ||
manifest.package !== CONTEXT_LOADER_PACKAGE ||
manifest.version !== packageJson.version ||
!isArtifact(artifact) ||
state.package !== CONTEXT_LOADER_PACKAGE ||
state.version !== packageJson.version ||
state.target !== target ||
state.binaryPath !== CANONICAL_PACKAGE_BINARY ||
!isSha256(state.binarySha256) ||
!bin.startsWith(`${packageRoot}${path.sep}`) ||
!existsSync(bin) ||
createHash("sha256").update(readFileSync(bin)).digest("hex") !== state.binarySha256
state.archiveSha256 !== artifact.archiveSha256 ||
state.binarySha256 !== artifact.binarySha256 ||
bin === undefined ||
createHash("sha256").update(readFileSync(bin)).digest("hex") !== artifact.binarySha256
) {
return undefined;
}
return { bin, kind: "package", verifiedIdentity: `package:${CONTEXT_LOADER_PACKAGE}@${packageJson.version}:${target}:${state.binarySha256}` };
return { bin, kind: "package", verifiedIdentity: `package:${CONTEXT_LOADER_PACKAGE}@${packageJson.version}:${target}:${artifact.binarySha256}` };
} catch {
return undefined;
}
}
function readJson(file: string): Record<string, unknown> {
return JSON.parse(readFileSync(file, "utf8")) as Record<string, unknown>;
function readVerifiedJson(root: string, relativePath: string): Record<string, unknown> | undefined {
const file = physicalRegularFile(root, relativePath);
if (file === undefined) return undefined;
try {
const value = JSON.parse(readFileSync(file, "utf8"));
return typeof value === "object" && value !== null ? value as Record<string, unknown> : undefined;
} catch {
return undefined;
}
}
function isSha256(value: unknown): value is string {
return typeof value === "string" && /^[a-f0-9]{64}$/u.test(value);
}
function isArtifact(value: unknown): value is { archiveSha256: string; binarySha256: string; binaryPath: string; url: string } {
if (typeof value !== "object" || value === null) return false;
const artifact = value as Record<string, unknown>;
return (
typeof artifact.url === "string" &&
artifact.url.startsWith("https://") &&
isSha256(artifact.archiveSha256) &&
isSha256(artifact.binarySha256) &&
typeof artifact.binaryPath === "string" &&
!artifact.binaryPath.includes("..") &&
!path.isAbsolute(artifact.binaryPath)
);
}
/** Returns a package-owned physical root; package-root symlinks are never trusted. */
function physicalPackageRoot(packageRoot: string): string | undefined {
try {
if (!lstatSync(packageRoot).isDirectory()) return undefined;
return realpathSync(packageRoot);
} catch {
return undefined;
}
}
/** Refuses final-file links and any ancestor link that resolves outside the physical package root. */
function physicalRegularFile(root: string, relativePath: string): string | undefined {
const lexical = path.resolve(root, relativePath);
if (!lexical.startsWith(`${root}${path.sep}`)) return undefined;
try {
if (!lstatSync(lexical).isFile()) return undefined;
const physical = realpathSync(lexical);
return physical.startsWith(`${root}${path.sep}`) && statSync(physical).isFile() ? physical : undefined;
} catch {
return undefined;
}
}
function targetForCurrentPlatform(): string {
return `${process.platform}-${process.arch}`;
}
+38 -3
View File
@@ -1,4 +1,4 @@
import { mkdir, mkdtemp, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, realpath, rm, symlink, writeFile } from "node:fs/promises";
import { createHash } from "node:crypto";
import os from "node:os";
import path from "node:path";
@@ -47,10 +47,13 @@ async function makeVerifiedPackage(marker: string, version = "0.1.0"): Promise<s
const binary = path.join(root, "bin", "wren-context-loader");
const content = Buffer.from(`#!/bin/sh\n# ${marker}\n`);
const digest = createHash("sha256").update(content).digest("hex");
const archiveSha256 = "a".repeat(64);
const target = `${process.platform}-${process.arch}`;
await mkdir(path.dirname(binary), { recursive: true });
await writeFile(binary, content, { mode: 0o755 });
await writeFile(path.join(root, "package.json"), JSON.stringify({ name: "@wrenai/context-loader", version }));
await writeFile(path.join(root, "install-state.json"), JSON.stringify({ package: "@wrenai/context-loader", version, target: `${process.platform}-${process.arch}`, binaryPath: path.join("bin", "wren-context-loader"), binarySha256: digest }));
await writeFile(path.join(root, "artifacts.json"), JSON.stringify({ schema: 1, package: "@wrenai/context-loader", version, artifacts: { [target]: { url: "https://example.invalid/context-loader.tar.gz", archiveSha256, binarySha256: digest, binaryPath: "wren-context-loader" } } }));
await writeFile(path.join(root, "install-state.json"), JSON.stringify({ package: "@wrenai/context-loader", version, target, archiveSha256, binaryPath: path.join("bin", "wren-context-loader"), binarySha256: digest }));
return root;
}
@@ -113,7 +116,7 @@ describe("verified package context loader", () => {
const a = readVerifiedContextLoaderPackage(aRoot);
const b = readVerifiedContextLoaderPackage(bRoot);
expect(a?.kind).toBe("package");
expect(a?.bin).toBe(path.join(aRoot, "bin", "wren-context-loader"));
expect(a?.bin).toBe(await realpath(path.join(aRoot, "bin", "wren-context-loader")));
expect(a).toBeDefined();
expect(b).toBeDefined();
expect(getContextLoaderIdentity(a!)).not.toBe(getContextLoaderIdentity(b!));
@@ -127,6 +130,38 @@ describe("verified package context loader", () => {
await writeFile(path.join(stale, "install-state.json"), JSON.stringify({ package: "@wrenai/context-loader", version: "0.0.0", target: `${process.platform}-${process.arch}`, binaryPath: path.join("bin", "wren-context-loader"), binarySha256: "0".repeat(64) }));
expect(readVerifiedContextLoaderPackage(stale)).toBeUndefined();
});
it("requires the packaged artifact manifest to attest the record and binary, not merely a self-reported state hash", async () => {
const missingManifest = await makeVerifiedPackage("missing-manifest");
await rm(path.join(missingManifest, "artifacts.json"));
expect(readVerifiedContextLoaderPackage(missingManifest)).toBeUndefined();
const forgedState = await makeVerifiedPackage("trusted");
const forgedBytes = Buffer.from("#!/bin/sh\n# forged\n");
const forgedDigest = createHash("sha256").update(forgedBytes).digest("hex");
await writeFile(path.join(forgedState, "bin", "wren-context-loader"), forgedBytes, { mode: 0o755 });
const state = JSON.parse(await readFile(path.join(forgedState, "install-state.json"), "utf8"));
await writeFile(path.join(forgedState, "install-state.json"), JSON.stringify({ ...state, binarySha256: forgedDigest }));
expect(readVerifiedContextLoaderPackage(forgedState)).toBeUndefined();
});
it("rejects final-file and ancestor-directory symlinks even when their bytes match the declared digest", async () => {
const finalLink = await makeVerifiedPackage("final-link");
const finalTarget = path.join(await mkdtemp(path.join(os.tmpdir(), "wren-harness-loader-external-")), "wren-context-loader");
const finalBytes = await readFile(path.join(finalLink, "bin", "wren-context-loader"));
await writeFile(finalTarget, finalBytes, { mode: 0o755 });
await rm(path.join(finalLink, "bin", "wren-context-loader"));
await symlink(finalTarget, path.join(finalLink, "bin", "wren-context-loader"));
expect(readVerifiedContextLoaderPackage(finalLink)).toBeUndefined();
const ancestorLink = await makeVerifiedPackage("ancestor-link");
const externalDir = await mkdtemp(path.join(os.tmpdir(), "wren-harness-loader-external-dir-"));
const ancestorBytes = await readFile(path.join(ancestorLink, "bin", "wren-context-loader"));
await writeFile(path.join(externalDir, "wren-context-loader"), ancestorBytes, { mode: 0o755 });
await rm(path.join(ancestorLink, "bin"), { recursive: true });
await symlink(externalDir, path.join(ancestorLink, "bin"));
expect(readVerifiedContextLoaderPackage(ancestorLink)).toBeUndefined();
});
});
/**
@@ -23,6 +23,8 @@ describe("installed-package and vendor-contract CI wiring", () => {
expect(workflow).toContain("macOS packed-install context-loader acceptance");
expect(workflow).toContain("runs-on: macos-14");
expect(workflow).toContain("pnpm run check:installed-package");
expect(workflow).toContain("Test context-loader installer contract");
expect(workflow).toContain("pnpm --dir apps/context-loader test");
});
it("keeps the deterministic vendor-contract entry point explicit", () => {