mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-04 11:01:16 +01:00
fix(runner): derive installed server authority for native readiness
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
2554c93d0c
commit
52486d6b8d
3 files changed
+119
-5
No files matched your search
@@ -30,6 +30,7 @@ import {
|
||||
import type { Readable, Writable } from "node:stream";
|
||||
|
||||
import { resolveQualifiedAcpxProfile, type QualifiedAcpxProfile } from "./qualified-profiles.js";
|
||||
import { resolveInstalledProviderAuthority } from "./installed-provider-authority.js";
|
||||
import {
|
||||
VERIFIED_RUNTIME_EXECUTABLE_ENV,
|
||||
verifiedRuntimeExecutableHandoff,
|
||||
@@ -645,11 +646,13 @@ export async function verifyQualifiedAcpxInstallation(
|
||||
): Promise<VerifiedAcpxInstallation> {
|
||||
// Keep the selected package authority available when its bundled provider
|
||||
// wrapper delegates platform installation to the published server manifest.
|
||||
if ((profile.agent === "codex" || profile.agent === "claude") && resolvePackageJson === defaultPackageJsonResolver && process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT !== undefined) {
|
||||
resolvePackageJson = createAcpxPackageJsonResolver(
|
||||
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT,
|
||||
process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST,
|
||||
);
|
||||
if ((profile.agent === "codex" || profile.agent === "claude") && resolvePackageJson === defaultPackageJsonResolver) {
|
||||
const boundRoot = process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_ROOT;
|
||||
const authority = boundRoot === undefined ? resolveInstalledProviderAuthority(import.meta.url) : {
|
||||
root: boundRoot,
|
||||
manifest: process.env.PAPERCLIP_ACPX_PROVIDER_PACKAGE_MANIFEST,
|
||||
};
|
||||
if (authority) resolvePackageJson = createAcpxPackageJsonResolver(authority.root, authority.manifest);
|
||||
}
|
||||
const builtin = profile.agent === "grok";
|
||||
if (builtin && (profile.agentServerPackage !== "builtin:grok-acp" || profile.agentServerVersion !== "1" || profile.agentRuntimePackage !== "native:grok" || profile.agentRuntimeVersion !== "1.0.13")) {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { mkdtemp, mkdir, writeFile, symlink, rm, realpath } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { resolveInstalledProviderAuthority } from "./installed-provider-authority.js";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => { await Promise.all(roots.splice(0).map(root => rm(root, { recursive: true, force: true }))); });
|
||||
async function fixture(name = "@paperclipai/server") {
|
||||
const root = await realpath(await mkdtemp(join(tmpdir(), "installed-acpx-authority-")));
|
||||
roots.push(root);
|
||||
const directory = join(root, "dist/vendor/paperclip-runner/drivers/acpx");
|
||||
await mkdir(directory, { recursive: true });
|
||||
const module = join(directory, "installation-integrity.js");
|
||||
await writeFile(module, "export {};\n");
|
||||
const manifest = join(root, "package.json");
|
||||
await writeFile(manifest, JSON.stringify({ name }));
|
||||
return { root, module, manifest };
|
||||
}
|
||||
|
||||
describe("installed provider authority", () => {
|
||||
it("derives the owning server only from the normal installed verifier layout", async () => {
|
||||
const f = await fixture();
|
||||
expect(resolveInstalledProviderAuthority(pathToFileURL(f.module).href)).toEqual({ root: f.root, manifest: f.manifest });
|
||||
expect(resolveInstalledProviderAuthority(pathToFileURL(join(f.root, "src/drivers/acpx/installation-integrity.ts")).href)).toBeUndefined();
|
||||
expect(resolveInstalledProviderAuthority(pathToFileURL(join(f.root, "dist/drivers/acpx/installation-integrity.js")).href)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("rejects a substituted package owner", async () => {
|
||||
const f = await fixture("unrelated-package");
|
||||
expect(() => resolveInstalledProviderAuthority(pathToFileURL(f.module).href)).toThrow("owning server package");
|
||||
});
|
||||
|
||||
it("rejects a symlinked manifest before adopting its declarations", async () => {
|
||||
const f = await fixture();
|
||||
const other = join(f.root, "other.json");
|
||||
await writeFile(other, JSON.stringify({ name: "@paperclipai/server" }));
|
||||
await rm(f.manifest);
|
||||
await symlink(other, f.manifest);
|
||||
expect(() => resolveInstalledProviderAuthority(pathToFileURL(f.module).href)).toThrow();
|
||||
});
|
||||
|
||||
it("rejects a redirected verifier directory", async () => {
|
||||
const f = await fixture();
|
||||
const old = join(f.root, "dist/vendor/paperclip-runner/drivers/acpx");
|
||||
const other = join(f.root, "other");
|
||||
await mkdir(other);
|
||||
await writeFile(join(other, "installation-integrity.js"), "export {};\n");
|
||||
await rm(old, { recursive: true });
|
||||
await symlink(other, old);
|
||||
expect(() => resolveInstalledProviderAuthority(pathToFileURL(f.module).href)).toThrow("package layout");
|
||||
});
|
||||
|
||||
it.each(["", " ".repeat(256 * 1024 + 1)])("rejects unbounded or empty owner metadata", async contents => {
|
||||
const f = await fixture();
|
||||
await writeFile(f.manifest, contents);
|
||||
expect(() => resolveInstalledProviderAuthority(pathToFileURL(f.module).href)).toThrow("bounded regular file");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { closeSync, constants, fstatSync, lstatSync, openSync, readFileSync, realpathSync } from "node:fs";
|
||||
import { isAbsolute, join, resolve } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export function resolveInstalledProviderAuthority(moduleUrl: string): { root: string; manifest: string } | undefined {
|
||||
const url = new URL(moduleUrl);
|
||||
if (url.protocol !== "file:") return undefined;
|
||||
const modulePath = fileURLToPath(url);
|
||||
const suffix = "/dist/vendor/paperclip-runner/drivers/acpx/installation-integrity.js";
|
||||
if (!modulePath.endsWith(suffix)) return undefined;
|
||||
const root = modulePath.slice(0, -suffix.length);
|
||||
if (!isAbsolute(root) || resolve(root) !== root || realpathSync(root) !== root) {
|
||||
throw new Error("Installed ACPX server authority must be a canonical package directory");
|
||||
}
|
||||
for (const relative of ["", "dist", "dist/vendor", "dist/vendor/paperclip-runner", "dist/vendor/paperclip-runner/drivers", "dist/vendor/paperclip-runner/drivers/acpx"]) {
|
||||
const path = join(root, relative);
|
||||
const metadata = lstatSync(path);
|
||||
if (!metadata.isDirectory() || metadata.isSymbolicLink() || realpathSync(path) !== path) {
|
||||
throw new Error("Installed ACPX server authority escapes its package layout");
|
||||
}
|
||||
}
|
||||
const moduleMetadata = lstatSync(modulePath);
|
||||
if (!moduleMetadata.isFile() || moduleMetadata.isSymbolicLink() || realpathSync(modulePath) !== modulePath) {
|
||||
throw new Error("Installed ACPX verifier must remain in its package layout");
|
||||
}
|
||||
const manifest = join(root, "package.json");
|
||||
const fd = openSync(manifest, constants.O_RDONLY | constants.O_NOFOLLOW);
|
||||
try {
|
||||
const before = fstatSync(fd, { bigint: true });
|
||||
if (!before.isFile() || before.size < 1n || before.size > 256n * 1024n) {
|
||||
throw new Error("Installed ACPX server manifest must be a bounded regular file");
|
||||
}
|
||||
const bytes = readFileSync(fd);
|
||||
const after = fstatSync(fd, { bigint: true });
|
||||
const named = lstatSync(manifest, { bigint: true });
|
||||
if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size
|
||||
|| before.mtimeNs !== after.mtimeNs || before.ctimeNs !== after.ctimeNs
|
||||
|| named.dev !== before.dev || named.ino !== before.ino || named.isSymbolicLink()
|
||||
|| bytes.length !== Number(before.size) || realpathSync(manifest) !== manifest) {
|
||||
throw new Error("Installed ACPX server manifest changed during admission");
|
||||
}
|
||||
const value = JSON.parse(bytes.toString("utf8"));
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)
|
||||
|| !Object.hasOwn(value, "name") || value.name !== "@paperclipai/server") {
|
||||
throw new Error("Installed ACPX verifier requires the owning server package");
|
||||
}
|
||||
} finally {
|
||||
closeSync(fd);
|
||||
}
|
||||
return { root, manifest };
|
||||
}
|
||||
Reference in new issue
Block a user