Qualify restricted SDK syntax independently of build architecture
This commit is contained in:
@@ -26,13 +26,41 @@ export interface ImportFinding {
|
|||||||
export const restrictedSDKSpecifier = "quixos:restricted-sdk/v1";
|
export const restrictedSDKSpecifier = "quixos:restricted-sdk/v1";
|
||||||
export const restrictedSDKPath = "share/quixos/restricted/core.mjs";
|
export const restrictedSDKPath = "share/quixos/restricted/core.mjs";
|
||||||
|
|
||||||
|
/** Compare trusted SDK syntax without build-machine paths in source comments.
|
||||||
|
* Keep literal/directive spelling and every executable AST node. This is SDK
|
||||||
|
* compatibility only: artifact files and the executed host SDK still have exact
|
||||||
|
* byte digests, and authored SDK bytes are never loaded by the restricted host.
|
||||||
|
*/
|
||||||
|
export function restrictedSDKIdentity(source: string): string {
|
||||||
|
if (Buffer.byteLength(source) > 2 * 1024 * 1024) throw Error("RESTRICTED_SDK_ANALYSIS_LIMIT");
|
||||||
|
const syntax = parse(source, { sourceType: "module", attachComment: false });
|
||||||
|
const metadata = new Set([
|
||||||
|
"start",
|
||||||
|
"end",
|
||||||
|
"loc",
|
||||||
|
"comments",
|
||||||
|
"leadingComments",
|
||||||
|
"innerComments",
|
||||||
|
"trailingComments",
|
||||||
|
"parenStart",
|
||||||
|
]);
|
||||||
|
let nodes = 0;
|
||||||
|
const encoded = JSON.stringify(syntax, (key, value) => {
|
||||||
|
if (metadata.has(key)) return undefined;
|
||||||
|
if (value && typeof value === "object" && typeof value.type === "string" && ++nodes > 100000)
|
||||||
|
throw Error("RESTRICTED_SDK_ANALYSIS_LIMIT");
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
return hexDigest({ codec: "quixos-restricted-sdk-syntax-v1", syntax: JSON.parse(encoded) });
|
||||||
|
}
|
||||||
|
|
||||||
/** Mechanical candidate checks, not authorization or an engine-safety proof.
|
/** Mechanical candidate checks, not authorization or an engine-safety proof.
|
||||||
* The caller must first verify the manifest and all declared file digests. */
|
* The caller must first verify the manifest and all declared file digests. */
|
||||||
export async function inspectRestrictedImplementation(
|
export async function inspectRestrictedImplementation(
|
||||||
revision: string,
|
revision: string,
|
||||||
implementation: ArtifactImplementation,
|
implementation: ArtifactImplementation,
|
||||||
read: ArtifactReader,
|
read: ArtifactReader,
|
||||||
qualifiedSDKDigest?: string,
|
qualifiedSDKIdentity?: string,
|
||||||
): Promise<ImportFinding[]> {
|
): Promise<ImportFinding[]> {
|
||||||
const entry = implementation.files.find((file) => file.path === implementation.path);
|
const entry = implementation.files.find((file) => file.path === implementation.path);
|
||||||
const sdk = implementation.files.find((file) => file.path === restrictedSDKPath);
|
const sdk = implementation.files.find((file) => file.path === restrictedSDKPath);
|
||||||
@@ -48,10 +76,18 @@ export async function inspectRestrictedImplementation(
|
|||||||
!/^[$A-Z_a-z][$\w]*$/.test(implementation.registryExport ?? "")
|
!/^[$A-Z_a-z][$\w]*$/.test(implementation.registryExport ?? "")
|
||||||
)
|
)
|
||||||
return [{ code: "RESTRICTED_CLOSURE", detail: "Expected one closed module and the restricted SDK", line: null }];
|
return [{ code: "RESTRICTED_CLOSURE", detail: "Expected one closed module and the restricted SDK", line: null }];
|
||||||
if (qualifiedSDKDigest && sdk.digest !== qualifiedSDKDigest)
|
|
||||||
return [{ code: "RESTRICTED_SDK_UNSUPPORTED", detail: "SDK differs from the qualified host profile", line: null }];
|
|
||||||
if (entry.bytes > 1024 * 1024 || sdk.bytes > 2 * 1024 * 1024)
|
if (entry.bytes > 1024 * 1024 || sdk.bytes > 2 * 1024 * 1024)
|
||||||
return [{ code: "ANALYSIS_LIMIT", detail: "Restricted closure exceeds the source budget", line: null }];
|
return [{ code: "ANALYSIS_LIMIT", detail: "Restricted closure exceeds the source budget", line: null }];
|
||||||
|
if (qualifiedSDKIdentity) {
|
||||||
|
const source = Buffer.from(await read(revision, sdk.path, sdk.bytes)).toString("utf8");
|
||||||
|
try {
|
||||||
|
if (restrictedSDKIdentity(source) !== qualifiedSDKIdentity) throw Error("SDK syntax differs");
|
||||||
|
} catch {
|
||||||
|
return [
|
||||||
|
{ code: "RESTRICTED_SDK_UNSUPPORTED", detail: "SDK differs from the qualified host profile", line: null },
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
return inspectJavaScriptSurface(Buffer.from(await read(revision, entry.path, entry.bytes)).toString("utf8"), [
|
return inspectJavaScriptSurface(Buffer.from(await read(revision, entry.path, entry.bytes)).toString("utf8"), [
|
||||||
restrictedSDKSpecifier,
|
restrictedSDKSpecifier,
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -6,10 +6,64 @@ import {
|
|||||||
compileExecutionPlan,
|
compileExecutionPlan,
|
||||||
hexDigest,
|
hexDigest,
|
||||||
inspectJavaScriptSurface,
|
inspectJavaScriptSurface,
|
||||||
|
inspectRestrictedImplementation,
|
||||||
|
restrictedSDKIdentity,
|
||||||
|
restrictedSDKPath,
|
||||||
|
type ArtifactImplementation,
|
||||||
reportExecutionPlacement,
|
reportExecutionPlacement,
|
||||||
type ExecutionInstallation,
|
type ExecutionInstallation,
|
||||||
} from "../src/execution-world/index.js";
|
} from "../src/execution-world/index.js";
|
||||||
|
|
||||||
|
test("restricted SDK identity ignores build comments and positions, preserving executable syntax", () => {
|
||||||
|
const source = 'export function value() { return ("hello"); }';
|
||||||
|
const x86 = "// /nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-core/dist/portable.js\n" + source;
|
||||||
|
const arm = "// /nix/store/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-core/dist/portable.js\n\n" + source;
|
||||||
|
assert.equal(restrictedSDKIdentity(x86), restrictedSDKIdentity(arm));
|
||||||
|
for (const changed of [
|
||||||
|
'export function value() { return ("goodbye"); }',
|
||||||
|
'export function value() { return\n("hello"); }',
|
||||||
|
'export function value() { fetch("hello"); }',
|
||||||
|
'import x from "node:fs"; ' + source,
|
||||||
|
'export function value() { return "/nix/store/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-core"; }',
|
||||||
|
])
|
||||||
|
assert.notEqual(restrictedSDKIdentity(source), restrictedSDKIdentity(changed));
|
||||||
|
assert.notEqual(restrictedSDKIdentity('"use strict";'), restrictedSDKIdentity('"use\\x20strict";'));
|
||||||
|
assert.notEqual(restrictedSDKIdentity('const value="// comment";'), restrictedSDKIdentity('const value="";'));
|
||||||
|
assert.throws(() => restrictedSDKIdentity("const ???"));
|
||||||
|
assert.throws(() => restrictedSDKIdentity(" ".repeat(2 * 1024 * 1024 + 1)), /ANALYSIS_LIMIT/);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("restricted admission accepts the same SDK syntax and rejects changed SDK behavior", async () => {
|
||||||
|
const source = "export const registry = {};";
|
||||||
|
const sdk = 'export const value = "trusted";';
|
||||||
|
const implementation: ArtifactImplementation = {
|
||||||
|
id: "restricted",
|
||||||
|
target: "server",
|
||||||
|
compatibility: "quixos-restricted-module-v1",
|
||||||
|
path: "module.mjs",
|
||||||
|
ports: "quixos-transaction-ports-v1",
|
||||||
|
registryExport: "registry",
|
||||||
|
exports: ["value"],
|
||||||
|
contentDigest: "a".repeat(64),
|
||||||
|
files: [
|
||||||
|
{ path: "module.mjs", digest: "b".repeat(64), bytes: source.length, mediaType: "text/javascript" },
|
||||||
|
{ path: restrictedSDKPath, digest: "c".repeat(64), bytes: sdk.length, mediaType: "text/javascript" },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const check = (candidateSDK: string) => {
|
||||||
|
implementation.files[1]!.bytes = Buffer.byteLength(candidateSDK);
|
||||||
|
return inspectRestrictedImplementation(
|
||||||
|
"package:test@1",
|
||||||
|
implementation,
|
||||||
|
async (_revision, path) => Buffer.from(path === restrictedSDKPath ? candidateSDK : source),
|
||||||
|
restrictedSDKIdentity(sdk),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
assert.deepEqual(await check("// a different build path\n" + sdk), []);
|
||||||
|
assert.equal((await check('export const value = "untrusted";'))[0]?.code, "RESTRICTED_SDK_UNSUPPORTED");
|
||||||
|
assert.equal((await check("export const ???"))[0]?.code, "RESTRICTED_SDK_UNSUPPORTED");
|
||||||
|
});
|
||||||
|
|
||||||
test("compiled-source inspection finds authority imports and ambient facilities without executing code", () => {
|
test("compiled-source inspection finds authority imports and ambient facilities without executing code", () => {
|
||||||
const surface = inspectJavaScriptSurface(
|
const surface = inspectJavaScriptSurface(
|
||||||
`
|
`
|
||||||
|
|||||||
Reference in New Issue
Block a user