Initial commit: NetMesh smart ops platform with 3D topology, inspection reports, RCA and AI ops assistant
Some checks failed
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled

This commit is contained in:
2026-10-15 13:53:04 +08:00
commit 2f9f8da48e
3299 changed files with 926841 additions and 0 deletions

View File

@@ -0,0 +1,943 @@
import assert from "node:assert/strict";
import { execFile } from "node:child_process";
import { createHash } from "node:crypto";
import {
mkdir,
mkdtemp,
readFile,
rm,
symlink,
truncate,
writeFile,
} from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { promisify } from "node:util";
import { fileURLToPath } from "node:url";
import { deflateRawSync } from "node:zlib";
import test from "node:test";
import {
assertManifestSnapshotMatches,
buildPluginPackage,
extractPluginPackage,
hashFile,
validatePluginDirectory,
validatePluginPackage,
} from "./archive.ts";
import { checkPluginCompatibility } from "./compatibility.ts";
import { PACKAGE_LIMITS } from "./constants.ts";
import { initPlugin } from "./commands.ts";
import {
parseAndValidateManifestContents,
readAndValidateManifest,
validateManifestValue,
} from "./manifest.ts";
import { assertSafePackagePath, PackagePathRegistry } from "./packagePath.ts";
const execFileAsync = promisify(execFile);
const cliPath = fileURLToPath(new URL("./cli.ts", import.meta.url));
const repositoryRoot = fileURLToPath(new URL("../../../", import.meta.url));
function crc32(contents: Buffer): number {
let crc = 0xffffffff;
for (const byte of contents) {
crc ^= byte;
for (let bit = 0; bit < 8; bit += 1) {
crc = (crc & 1) === 1 ? 0xedb88320 ^ (crc >>> 1) : crc >>> 1;
}
}
return (crc ^ 0xffffffff) >>> 0;
}
function createZipEntry(
entryName: string,
contents: Buffer,
options: {
readonly compressionMethod?: 0 | 8;
readonly declaredUncompressedSize?: number;
} = {},
): Buffer {
const compressionMethod = options.compressionMethod ?? 0;
const encodedContents = compressionMethod === 8 ? deflateRawSync(contents) : contents;
const declaredUncompressedSize = options.declaredUncompressedSize ?? contents.byteLength;
const encodedName = Buffer.from(entryName);
const checksum = crc32(contents);
const localHeader = Buffer.alloc(30 + encodedName.byteLength);
localHeader.writeUInt32LE(0x04034b50, 0);
localHeader.writeUInt16LE(20, 4);
localHeader.writeUInt16LE(0x0800, 6);
localHeader.writeUInt16LE(compressionMethod, 8);
localHeader.writeUInt16LE(0, 10);
localHeader.writeUInt16LE(33, 12);
localHeader.writeUInt32LE(checksum, 14);
localHeader.writeUInt32LE(encodedContents.byteLength, 18);
localHeader.writeUInt32LE(declaredUncompressedSize, 22);
localHeader.writeUInt16LE(encodedName.byteLength, 26);
encodedName.copy(localHeader, 30);
const centralHeader = Buffer.alloc(46 + encodedName.byteLength);
centralHeader.writeUInt32LE(0x02014b50, 0);
centralHeader.writeUInt16LE(20, 4);
centralHeader.writeUInt16LE(20, 6);
centralHeader.writeUInt16LE(0x0800, 8);
centralHeader.writeUInt16LE(compressionMethod, 10);
centralHeader.writeUInt16LE(0, 12);
centralHeader.writeUInt16LE(33, 14);
centralHeader.writeUInt32LE(checksum, 16);
centralHeader.writeUInt32LE(encodedContents.byteLength, 20);
centralHeader.writeUInt32LE(declaredUncompressedSize, 24);
centralHeader.writeUInt16LE(encodedName.byteLength, 28);
centralHeader.writeUInt32LE((0o100644 << 16) >>> 0, 38);
centralHeader.writeUInt32LE(0, 42);
encodedName.copy(centralHeader, 46);
const centralOffset = localHeader.byteLength + encodedContents.byteLength;
const end = Buffer.alloc(22);
end.writeUInt32LE(0x06054b50, 0);
end.writeUInt16LE(1, 8);
end.writeUInt16LE(1, 10);
end.writeUInt32LE(centralHeader.byteLength, 12);
end.writeUInt32LE(centralOffset, 16);
return Buffer.concat([localHeader, encodedContents, centralHeader, end]);
}
function replaceCentralEntryMode(archive: Buffer, entryName: string, mode: number): Buffer {
const result = Buffer.from(archive);
const endOffset = result.lastIndexOf(Buffer.from([0x50, 0x4b, 0x05, 0x06]));
if (endOffset < 0) throw new Error("Missing ZIP end record");
const entryCount = result.readUInt16LE(endOffset + 10);
let offset = result.readUInt32LE(endOffset + 16);
for (let entry = 0; entry < entryCount; entry += 1) {
if (result.readUInt32LE(offset) !== 0x02014b50) throw new Error("Invalid central ZIP entry");
const nameLength = result.readUInt16LE(offset + 28);
const extraLength = result.readUInt16LE(offset + 30);
const commentLength = result.readUInt16LE(offset + 32);
const name = result.subarray(offset + 46, offset + 46 + nameLength).toString("utf8");
if (name === entryName) {
result.writeUInt32LE((mode << 16) >>> 0, offset + 38);
return result;
}
offset += 46 + nameLength + extraLength + commentLength;
}
throw new Error(`Missing central ZIP entry: ${entryName}`);
}
function manifest(overrides: Record<string, unknown> = {}) {
return {
manifestVersion: 1,
id: "com.example.package-test",
name: "package-test",
version: "1.0.0",
publisher: "example",
engines: { netcatty: ">=1.0.0 <2.0.0", api: ">=0.1.0-internal <0.2.0" },
main: { browser: "dist/index.js" },
...overrides,
};
}
async function createPlugin(root: string): Promise<string> {
const directory = path.join(root, "plugin");
await mkdir(path.join(directory, "dist"), { recursive: true });
await Promise.all([
writeFile(
path.join(directory, "netcatty.plugin.json"),
`${JSON.stringify(manifest(), null, 2)}\n`,
),
writeFile(path.join(directory, "dist/index.js"), "export default {};\n"),
writeFile(path.join(directory, "README.md"), "# Package test\n"),
]);
return directory;
}
test("path validation rejects traversal, platform aliases, and duplicates", () => {
for (const unsafe of [
"../escape",
"/absolute",
"C:/drive",
"a\\b",
"a/../b",
"CON",
"assets/PRN.txt",
"file.",
"folder/file ",
"folder/file?.js",
"a//b",
"a/b",
"a\b",
"../x",
"..",
"C:/x",
"assets/CON.txt",
"file.",
"😀".repeat(129),
]) {
assert.throws(() => assertSafePackagePath(unsafe));
}
assert.equal(assertSafePackagePath("😀".repeat(128)), "😀".repeat(128));
assert.equal(assertSafePackagePath("assets/fullwidth-S.txt"), "assets/fullwidth-S.txt");
const registry = new PackagePathRegistry();
registry.add("dist/Plugin.js");
assert.throws(() => registry.add("dist/plugin.js"), /case-colliding/);
for (const [first, second] of [
["assets/Straße.txt", "assets/STRASSE.txt"],
["assets/fullwidth-S.txt", "assets/fullwidth-S.txt"],
]) {
const unicodeRegistry = new PackagePathRegistry();
unicodeRegistry.add(first);
assert.throws(() => unicodeRegistry.add(second), /case-colliding/);
}
for (const paths of [
["dist", "dist/index.js"],
["dist/index.js", "dist"],
["DIST", "dist/index.js"],
["dist/index.js", "DIST"],
]) {
const collisionRegistry = new PackagePathRegistry();
collisionRegistry.add(paths[0]);
assert.throws(
() => collisionRegistry.add(paths[1]),
/File\/directory package path collision/,
);
}
});
test("manifest validation reports permission and contribution mistakes", () => {
const result = validateManifestValue(manifest({
permissions: {
required: [{ permission: "network", resources: ["https://example.com"] }],
optional: ["network"],
},
contributes: {
commands: [{ id: "com.example.package-test.run", title: "Run" }],
menus: [{ command: "com.example.package-test.missing", location: "commandPalette" }],
},
}));
assert.equal(result.valid, false);
assert.match(result.errors.join("\n"), /both required and optional/);
assert.match(result.errors.join("\n"), /undeclared command/);
});
test("text setting defaults must satisfy their declared pattern", () => {
const result = validateManifestValue(manifest({
permissions: { required: ["settings.read", "settings.write"] },
contributes: {
settings: [{
id: "com.example.package-test.channel",
label: "Channel",
control: "text",
scope: "application",
pattern: "^(stable|beta)$",
default: "nightly",
}],
},
}));
assert.equal(result.valid, false);
assert.match(result.errors.join("\n"), /default does not match its pattern/u);
});
test("resource-scoped required permissions must declare activation-time bounds", () => {
for (const [permission, resource] of [
["network", "https://example.com"],
["filesystem.read", "/tmp/plugin-read"],
["filesystem.write", "/tmp/plugin-write"],
["companion.execute", "com.example.package-test.helper"],
] as const) {
const unbounded = validateManifestValue(manifest({
permissions: { required: [permission] },
}));
assert.equal(unbounded.valid, false, permission);
assert.match(unbounded.errors.join("\n"), /must declare explicit resources/u);
const bounded = validateManifestValue(manifest({
permissions: { required: [{ permission, resources: [resource] }] },
}));
assert.equal(bounded.valid, true, bounded.errors.join("\n"));
const wildcard = validateManifestValue(manifest({
permissions: { required: [{ permission, resources: ["*"] }] },
}));
assert.equal(wildcard.valid, false, permission);
assert.match(wildcard.errors.join("\n"), /must not use the wildcard resource/u);
const optional = validateManifestValue(manifest({
permissions: { optional: [permission] },
}));
assert.equal(optional.valid, true, optional.errors.join("\n"));
}
});
test("manifest validation applies the runtime resource limit for each permission", () => {
for (const [permission, length, expectedLimit] of [
["network", 2_049, 2_048],
["storage", 2_049, 2_048],
["companion.execute", 193, 192],
] as const) {
const result = validateManifestValue(manifest({
permissions: permission === "storage"
? { optional: [{ permission, resources: ["x".repeat(length)] }] }
: { required: [{ permission, resources: ["x".repeat(length)] }] },
}));
assert.equal(result.valid, false);
assert.match(
result.errors.join("\n"),
new RegExp(`Permission resource for ${permission.replace(".", "\\.")} exceeds ${expectedLimit}`),
);
}
const filesystem = validateManifestValue(manifest({
permissions: {
required: [{ permission: "filesystem.read", resources: [`/${"x".repeat(4_096)}`] }],
},
}));
assert.equal(filesystem.valid, true, filesystem.errors.join("\n"));
});
test("Node utility entrypoints require the explicit advanced-runtime permission", () => {
const missing = validateManifestValue(manifest({
main: { node: "dist/index.js" },
}));
assert.equal(missing.valid, false);
assert.match(missing.errors.join("\n"), /requires declared permission: runtime\.advanced/);
const declared = validateManifestValue(manifest({
main: { node: "dist/index.js" },
permissions: { required: ["runtime.advanced"] },
}));
assert.equal(declared.valid, true, declared.errors.join("\n"));
});
test("manifest byte parsing rejects invalid UTF-8 before JSON validation", () => {
const validContents = new TextEncoder().encode(JSON.stringify(manifest()));
assert.equal(parseAndValidateManifestContents(validContents).id, "com.example.package-test");
assert.throws(
() => parseAndValidateManifestContents(Uint8Array.from([0x7b, 0x22, 0xff, 0x22, 0x7d])),
/not valid UTF-8 JSON/,
);
});
test("manifest validation safely rejects excessive JSON nesting", () => {
let nested: unknown = null;
for (let depth = 0; depth < 5_000; depth += 1) {
nested = [nested];
}
const result = validateManifestValue(manifest({
permissions: { required: ["commands"] },
contributes: {
commands: [{ id: "com.example.package-test.run", title: "Run" }],
keybindings: [{
command: "com.example.package-test.run",
key: "ctrl+x",
args: nested,
}],
},
}));
assert.equal(result.valid, false);
assert.match(result.errors.join("\n"), /must not exceed .* levels/);
const wideResult = validateManifestValue(manifest({
permissions: { required: ["commands"] },
contributes: {
commands: [{ id: "com.example.package-test.run", title: "Run" }],
keybindings: [{
command: "com.example.package-test.run",
key: "ctrl+x",
args: Array.from({ length: 100_000 }, () => null),
}],
},
}));
assert.equal(wideResult.valid, false);
assert.match(wideResult.errors.join("\n"), /must not contain more than .* nodes/);
});
test("manifest validation rejects duplicate companion executable paths", () => {
const result = validateManifestValue(manifest({
companionExecutables: [
{
id: "com.example.package-test.helper-one",
variants: [{
path: "bin/helper",
platforms: ["linux-x64"],
sha256: "0".repeat(64),
}],
},
{
id: "com.example.package-test.helper-two",
variants: [{
path: "bin/helper",
platforms: ["darwin-arm64"],
sha256: "1".repeat(64),
}],
},
],
}));
assert.equal(result.valid, false);
assert.match(result.errors.join("\n"), /Duplicate companion executable path: bin\/helper/);
});
test("manifest validation supports platform-specific companion variants", () => {
const result = validateManifestValue(manifest({
main: { browser: "dist/index.js", node: "dist/index.js" },
permissions: {
required: [
"runtime.advanced",
{
permission: "companion.execute",
resources: ["com.example.package-test.helper"],
},
],
},
companionExecutables: [{
id: "com.example.package-test.helper",
variants: [
{
path: "bin/helper-darwin",
platforms: ["darwin-arm64", "darwin-x64"],
sha256: "0".repeat(64),
},
{
path: "bin/helper-linux",
platforms: ["linux-arm64", "linux-x64"],
sha256: "1".repeat(64),
},
],
}],
}));
assert.equal(result.valid, true, result.errors.join("\n"));
const duplicatePlatform = validateManifestValue(manifest({
main: { browser: "dist/index.js", node: "dist/index.js" },
permissions: {
required: [
"runtime.advanced",
{
permission: "companion.execute",
resources: ["com.example.package-test.helper"],
},
],
},
companionExecutables: [{
id: "com.example.package-test.helper",
variants: [
{
path: "bin/helper-one",
platforms: ["linux-x64"],
sha256: "0".repeat(64),
},
{
path: "bin/helper-two",
platforms: ["linux-x64"],
sha256: "1".repeat(64),
},
],
}],
}));
assert.equal(duplicatePlatform.valid, false);
assert.match(duplicatePlatform.errors.join("\n"), /Duplicate companion platform/);
});
test("companion executables require an advanced utility placement", () => {
const companionExecutables = [{
id: "com.example.package-test.helper",
variants: [{
path: "bin/helper",
platforms: ["linux-x64"],
sha256: "0".repeat(64),
}],
}];
const companionPermission = {
permission: "companion.execute" as const,
resources: ["com.example.package-test.helper"],
};
const browserOnly = validateManifestValue(manifest({
permissions: { required: [companionPermission] },
companionExecutables,
}));
assert.equal(browserOnly.valid, false);
assert.match(browserOnly.errors.join("\n"), /require a Node utility entrypoint/u);
const missingAdvanced = validateManifestValue(manifest({
main: { browser: "dist/index.js", node: "dist/index.js" },
permissions: { required: [companionPermission] },
companionExecutables,
}));
assert.equal(missingAdvanced.valid, false);
assert.match(missingAdvanced.errors.join("\n"), /requires declared permission: runtime\.advanced/u);
});
test("packaging treats contributed package icons as required safe files", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-icons-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const manifestPath = path.join(directory, "netcatty.plugin.json");
await writeFile(manifestPath, `${JSON.stringify(manifest({
permissions: { required: ["commands"] },
contributes: {
commands: [{
id: "com.example.package-test.run",
title: "Run",
icon: {
kind: "package",
light: "assets/run-light.svg",
dark: "assets/run-dark.svg",
},
}],
},
}), null, 2)}\n`);
await assert.rejects(
buildPluginPackage(directory, path.join(root, "missing-icons.ncpkg")),
/missing package file: assets\/run-light\.svg/,
);
await mkdir(path.join(directory, "assets"));
await Promise.all([
writeFile(path.join(directory, "assets/run-light.svg"), "<svg></svg>"),
writeFile(path.join(directory, "assets/run-dark.svg"), "<svg></svg>"),
]);
const output = path.join(root, "with-icons.ncpkg");
await buildPluginPackage(directory, output);
const result = await validatePluginPackage(output);
assert.equal(result.manifest.id, "com.example.package-test");
});
test("compatibility checks engine ranges and negotiates declared features", () => {
const pluginManifest = manifest({
features: {
required: ["netcatty.rpc.progress"],
optional: ["netcatty.stream.binary", "netcatty.view.theme"],
},
});
const compatible = checkPluginCompatibility(pluginManifest, {
netcattyVersion: "1.4.0",
features: ["netcatty.rpc.progress", "netcatty.stream.binary"],
});
assert.equal(compatible.compatible, true);
assert.deepEqual(compatible.enabledFeatures, [
"netcatty.rpc.progress",
"netcatty.stream.binary",
]);
const incompatible = checkPluginCompatibility(pluginManifest, {
netcattyVersion: "2.0.0",
apiVersion: "0.2.0",
features: [],
});
assert.equal(incompatible.compatible, false);
assert.deepEqual(incompatible.missingRequiredFeatures, ["netcatty.rpc.progress"]);
assert.match(incompatible.errors.join("\n"), /does not satisfy/);
assert.match(incompatible.errors.join("\n"), /Missing required features/);
const nextApiPrerelease = checkPluginCompatibility(pluginManifest, {
netcattyVersion: "1.4.0",
apiVersion: "0.2.0-alpha.1",
features: ["netcatty.rpc.progress"],
});
assert.equal(nextApiPrerelease.compatible, false);
assert.match(nextApiPrerelease.errors.join("\n"), /plugin API version .* does not satisfy/);
});
test("compatibility CLI checks a validated plugin target", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-compatibility-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const compatible = await execFileAsync(process.execPath, [
"--import",
"tsx",
cliPath,
"compatibility",
directory,
"--netcatty",
"1.5.0",
]);
assert.match(compatible.stdout, /Compatible: com\.example\.package-test@1\.0\.0/);
await assert.rejects(
execFileAsync(process.execPath, [
"--import",
"tsx",
cliPath,
"compatibility",
directory,
"--netcatty",
"2.0.0",
]),
/Plugin is incompatible/,
);
});
test("example README commands use repository-root CLI paths", async () => {
const readme = await readFile(
path.join(repositoryRoot, "examples/plugins/hello-netcatty/README.md"),
"utf8",
);
assert.match(readme, /npm run build:plugin-packages/);
assert.match(
readme,
/npm exec -- netcatty-plugin validate examples\/plugins\/hello-netcatty/,
);
assert.match(
readme,
/npm exec -- netcatty-plugin compatibility examples\/plugins\/hello-netcatty --netcatty 0\.0\.0/,
);
assert.doesNotMatch(readme, /npm exec --workspace @netcatty\/plugin-cli/);
});
test("init creates a valid TypeScript plugin skeleton", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-init-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = path.join(root, "created");
await initPlugin(directory, { id: "com.example.created", name: "Created" });
const createdManifest = await readAndValidateManifest(directory);
assert.equal(createdManifest.id, "com.example.created");
assert.match(await readFile(path.join(directory, "src/index.ts"), "utf8"), /definePlugin/);
});
test("init safely serializes the display name in generated TypeScript", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-init-escape-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = path.join(root, "created");
const displayName = 'A "quoted" \\ plugin\nnext line';
await initPlugin(directory, { id: "com.example.escaped", name: displayName });
const source = await readFile(path.join(directory, "src/index.ts"), "utf8");
assert.ok(
source.includes(`context.logger.info(${JSON.stringify(`${displayName} activated`)});`),
);
});
test("packing is deterministic and the archive validates", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-pack-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const first = path.join(root, "first.ncpkg");
const second = path.join(root, "second.ncpkg");
const firstResult = await buildPluginPackage(directory, first);
await buildPluginPackage(directory, second);
const firstBytes = await readFile(first);
const secondBytes = await readFile(second);
assert.deepEqual(firstBytes, secondBytes);
assert.equal(
firstResult.sha256,
createHash("sha256").update(firstBytes).digest("hex"),
);
const validation = await validatePluginPackage(first);
const directoryValidation = await validatePluginDirectory(directory);
assert.equal(validation.manifest.id, "com.example.package-test");
assert.equal(validation.fileCount, 3);
assert.equal(firstResult.contentSha256, validation.contentSha256);
assert.equal(validation.contentSha256, directoryValidation.contentSha256);
assert.match(validation.contentSha256, /^[a-f0-9]{64}$/u);
});
test("logical content identity follows companion declarations across ZIP mode encoders", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-companion-mode-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const companionContents = Buffer.from("portable companion\n");
const companionPath = path.join(directory, "bin/helper");
await mkdir(path.dirname(companionPath), { recursive: true });
await writeFile(companionPath, companionContents);
await writeFile(
path.join(directory, "netcatty.plugin.json"),
`${JSON.stringify(manifest({
main: { browser: "dist/index.js", node: "dist/index.js" },
permissions: {
required: [
"runtime.advanced",
{
permission: "companion.execute",
resources: ["com.example.package-test.helper"],
},
],
},
companionExecutables: [{
id: "com.example.package-test.helper",
variants: [{
path: "bin/helper",
platforms: ["linux-x64"],
sha256: createHash("sha256").update(companionContents).digest("hex"),
}],
}],
}), null, 2)}\n`,
);
const builtPath = path.join(root, "built.ncpkg");
const portablePath = path.join(root, "portable.ncpkg");
const extracted = path.join(root, "extracted");
await buildPluginPackage(directory, builtPath);
await writeFile(
portablePath,
replaceCentralEntryMode(await readFile(builtPath), "bin/helper", 0o100644),
);
const archiveValidation = await extractPluginPackage(portablePath, extracted);
const directoryValidation = await validatePluginDirectory(extracted, {
allowIgnoredRootEntries: false,
});
assert.equal(archiveValidation.contentSha256, directoryValidation.contentSha256);
});
test("validated extraction creates an isolated tree and removes partial output on failure", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-extract-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const archive = path.join(root, "plugin.ncpkg");
const extracted = path.join(root, "extracted");
await buildPluginPackage(directory, archive);
const result = await extractPluginPackage(archive, extracted);
assert.equal(result.manifest.id, "com.example.package-test");
assert.equal(
await readFile(path.join(extracted, "dist/index.js"), "utf8"),
"export default {};\n",
);
await assert.rejects(extractPluginPackage(archive, extracted));
assert.equal(
await readFile(path.join(extracted, "dist/index.js"), "utf8"),
"export default {};\n",
);
const invalidArchive = path.join(root, "invalid.ncpkg");
const failedDestination = path.join(root, "failed-extraction");
await writeFile(invalidArchive, "not a zip");
await assert.rejects(extractPluginPackage(invalidArchive, failedDestination));
await assert.rejects(readFile(failedDestination), /ENOENT/);
});
test("archive validation rejects oversized manifests before buffering", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-archive-manifest-limit-"));
context.after(() => rm(root, { recursive: true, force: true }));
const oversizedPath = path.join(root, "oversized-manifest.ncpkg");
const oversizedBytes = createZipEntry(
"netcatty.plugin.json",
Buffer.alloc(PACKAGE_LIMITS.manifestBytes + 1, 0x20),
);
await writeFile(oversizedPath, oversizedBytes);
await assert.rejects(
validatePluginPackage(oversizedPath),
new RegExp(`Plugin manifest exceeds ${PACKAGE_LIMITS.manifestBytes} bytes`),
);
const forgedSizePath = path.join(root, "forged-size-manifest.ncpkg");
const forgedSizeBytes = createZipEntry(
"netcatty.plugin.json",
Buffer.alloc(PACKAGE_LIMITS.manifestBytes + 1, 0x20),
{
compressionMethod: 8,
declaredUncompressedSize: PACKAGE_LIMITS.manifestBytes,
},
);
await writeFile(forgedSizePath, forgedSizeBytes);
await assert.rejects(
validatePluginPackage(forgedSizePath),
new RegExp(
`too many bytes in the stream\\. expected ${PACKAGE_LIMITS.manifestBytes}\\. got at least ${PACKAGE_LIMITS.manifestBytes + 1}`,
),
);
});
test("archive validation rejects duplicate names and CRC corruption", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-archive-safety-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
await Promise.all([
writeFile(path.join(directory, "a.txt"), "first\n"),
writeFile(path.join(directory, "b.txt"), "second\n"),
]);
await mkdir(path.join(directory, "bbbbb"));
await Promise.all([
writeFile(path.join(directory, "aaaaa"), "parent-file\n"),
writeFile(path.join(directory, "bbbbb/file"), "child-file\n"),
]);
const validPath = path.join(root, "valid.ncpkg");
await buildPluginPackage(directory, validPath);
const validBytes = await readFile(validPath);
const duplicateBytes = Buffer.from(validBytes);
const originalName = Buffer.from("b.txt");
const duplicateName = Buffer.from("a.txt");
let replacements = 0;
for (let offset = duplicateBytes.indexOf(originalName); offset !== -1;) {
duplicateName.copy(duplicateBytes, offset);
replacements += 1;
offset = duplicateBytes.indexOf(originalName, offset + originalName.byteLength);
}
assert.equal(replacements, 2, "ZIP should contain the local and central entry names");
const duplicatePath = path.join(root, "duplicate.ncpkg");
await writeFile(duplicatePath, duplicateBytes);
await assert.rejects(validatePluginPackage(duplicatePath), /Duplicate or case-colliding/);
const prefixCollisionBytes = Buffer.from(validBytes);
let prefixReplacements = 0;
for (const [source, target] of [
[Buffer.from("aaaaa"), Buffer.from("distx")],
[Buffer.from("bbbbb/file"), Buffer.from("distx/file")],
]) {
for (let offset = prefixCollisionBytes.indexOf(source); offset !== -1;) {
target.copy(prefixCollisionBytes, offset);
prefixReplacements += 1;
offset = prefixCollisionBytes.indexOf(source, offset + source.byteLength);
}
}
assert.equal(prefixReplacements, 4, "ZIP should contain both local and central names");
const prefixCollisionPath = path.join(root, "prefix-collision.ncpkg");
await writeFile(prefixCollisionPath, prefixCollisionBytes);
await assert.rejects(
validatePluginPackage(prefixCollisionPath),
/File\/directory package path collision/,
);
const corruptedBytes = Buffer.from(validBytes);
const content = Buffer.from("# Package test\n");
const contentOffset = corruptedBytes.indexOf(content);
assert.notEqual(contentOffset, -1);
corruptedBytes[contentOffset] ^= 0x01;
const corruptedPath = path.join(root, "corrupted.ncpkg");
await writeFile(corruptedPath, corruptedBytes);
await assert.rejects(validatePluginPackage(corruptedPath), /integrity check failed/);
const splitNameBytes = Buffer.from(validBytes);
const localName = Buffer.from("README.md");
const conflictingLocalName = Buffer.from("renamed.x");
assert.equal(localName.byteLength, conflictingLocalName.byteLength);
const localNameOffset = splitNameBytes.indexOf(localName);
assert.notEqual(localNameOffset, -1);
conflictingLocalName.copy(splitNameBytes, localNameOffset);
const splitNamePath = path.join(root, "split-name.ncpkg");
await writeFile(splitNamePath, splitNameBytes);
await assert.rejects(
validatePluginPackage(splitNamePath),
/local and central entry names differ/,
);
});
test("packer rejects symbolic links and undeclared executables", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-safety-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
await assert.rejects(
buildPluginPackage(directory, path.join(root, "wrong-extension.zip")),
/\.ncpkg extension/,
);
if (process.platform !== "win32") {
await symlink("README.md", path.join(directory, "linked-readme"));
await assert.rejects(
buildPluginPackage(directory, path.join(root, "symlink.ncpkg")),
/Symbolic links/,
);
await rm(path.join(directory, "linked-readme"));
}
await mkdir(path.join(directory, "bin"));
const executablePath = path.join(directory, "bin/tool.exe");
await writeFile(executablePath, "not-a-real-executable\n");
await assert.rejects(
buildPluginPackage(directory, path.join(root, "executable.ncpkg")),
/not declared as a companion/,
);
});
test("packer ignores root dev artifacts without dropping nested runtime dependencies", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-runtime-deps-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
await Promise.all([
mkdir(path.join(directory, "node_modules/dev-only"), { recursive: true }),
mkdir(path.join(directory, "dist/node_modules/runtime-dependency"), { recursive: true }),
]);
await Promise.all([
writeFile(path.join(directory, "node_modules/dev-only/index.js"), "dev only\n"),
writeFile(
path.join(directory, "dist/node_modules/runtime-dependency/index.js"),
"export const runtime = true;\n",
),
]);
const packagePath = path.join(root, "runtime-deps.ncpkg");
const build = await buildPluginPackage(directory, packagePath);
const validation = await validatePluginPackage(packagePath);
assert.equal(build.fileCount, 4);
assert.equal(validation.fileCount, 4);
});
test("packer rejects outputs inside the plugin source tree", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-output-containment-"));
context.after(() => rm(root, { recursive: true, force: true }));
const directory = await createPlugin(root);
const nestedOutput = path.join(directory, "dist/plugin.ncpkg");
await writeFile(nestedOutput, "previous package output\n");
await assert.rejects(
buildPluginPackage(directory, nestedOutput),
/output must be outside the plugin source directory/,
);
if (process.platform !== "win32") {
const outputAlias = path.join(root, "output-alias");
await symlink(path.join(directory, "dist"), outputAlias);
await assert.rejects(
buildPluginPackage(directory, path.join(outputAlias, "plugin.ncpkg")),
/output must be outside the plugin source directory/,
);
}
});
test("manifest byte limit is enforced before JSON parsing", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-limit-"));
context.after(() => rm(root, { recursive: true, force: true }));
const manifestPath = path.join(root, "netcatty.plugin.json");
await writeFile(manifestPath, "{}");
await truncate(manifestPath, PACKAGE_LIMITS.manifestBytes * 4);
await assert.rejects(readAndValidateManifest(root), /manifest exceeds/);
});
test("manifest validation refuses symlinked source manifests", async (context) => {
if (process.platform === "win32") return;
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-manifest-link-"));
context.after(() => rm(root, { recursive: true, force: true }));
const target = path.join(root, "target.json");
await writeFile(target, JSON.stringify(manifest()));
await symlink(target, path.join(root, "netcatty.plugin.json"));
await assert.rejects(readAndValidateManifest(root), /must be a regular file/);
});
test("validated manifest snapshots reject changed package bytes", () => {
const snapshot = { size: 128, sha256: "a".repeat(64) };
assert.doesNotThrow(() => assertManifestSnapshotMatches(snapshot, snapshot));
assert.throws(
() => assertManifestSnapshotMatches(snapshot, { ...snapshot, size: 129 }),
/manifest changed after validation/,
);
assert.throws(
() => assertManifestSnapshotMatches(snapshot, { ...snapshot, sha256: "b".repeat(64) }),
/manifest changed after validation/,
);
assert.throws(
() => assertManifestSnapshotMatches(snapshot, undefined),
/manifest changed after validation/,
);
});
test("source hashing enforces its byte budget while reading", async (context) => {
const root = await mkdtemp(path.join(tmpdir(), "netcatty-plugin-source-limit-"));
context.after(() => rm(root, { recursive: true, force: true }));
const filePath = path.join(root, "growing.bin");
await writeFile(filePath, "1234");
await assert.rejects(hashFile(filePath, 3), /source exceeds 3 bytes while reading/);
assert.equal((await hashFile(filePath, 4)).size, 4);
});