Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
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
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
1085 lines
38 KiB
JavaScript
1085 lines
38 KiB
JavaScript
"use strict";
|
|
|
|
const assert = require("node:assert/strict");
|
|
const test = require("node:test");
|
|
|
|
const {
|
|
PluginExtensionProviderService,
|
|
STREAM_WINDOW_BYTES,
|
|
} = require("./extensionProviderService.cjs");
|
|
|
|
function fixture(options = {}) {
|
|
const identity = Object.freeze({
|
|
pluginId: "com.example.transport",
|
|
pluginVersion: "1.0.0",
|
|
runtimeId: "runtime-1",
|
|
runtimeKind: "browser",
|
|
securityPrincipal: "local:com.example.transport",
|
|
});
|
|
const providers = [
|
|
{
|
|
id: "com.example.transport.connection",
|
|
kind: "connection",
|
|
...(options.connectionConfigurationSchema === undefined
|
|
? {}
|
|
: { configurationSchema: options.connectionConfigurationSchema }),
|
|
},
|
|
{ id: "com.example.transport.auth", kind: "authentication" },
|
|
{ id: "com.example.transport.importer", kind: "importer" },
|
|
{
|
|
id: "com.example.transport.sync",
|
|
kind: "sync",
|
|
...(options.syncConfigurationSchema === undefined
|
|
? {}
|
|
: { configurationSchema: options.syncConfigurationSchema }),
|
|
},
|
|
];
|
|
const contributionService = {
|
|
listProviders({ kind }) {
|
|
return providers.filter((provider) => provider.kind === kind).map((provider) => ({
|
|
pluginId: provider.kind === "connection" && options.connectionOwnerPluginId
|
|
? options.connectionOwnerPluginId
|
|
: identity.pluginId,
|
|
pluginVersion: identity.pluginVersion,
|
|
provider,
|
|
}));
|
|
},
|
|
async activateProvider(providerId) {
|
|
const provider = providers.find((candidate) => candidate.id === providerId);
|
|
if (!provider) throw new Error("missing provider");
|
|
return {
|
|
plugin: {
|
|
id: identity.pluginId,
|
|
activeVersion: identity.pluginVersion,
|
|
manifest: { id: identity.pluginId },
|
|
},
|
|
provider,
|
|
identity,
|
|
};
|
|
},
|
|
};
|
|
const permissions = [];
|
|
const permissionEngine = {
|
|
async authorize(context, descriptor) {
|
|
if (typeof options.authorize === "function") {
|
|
return options.authorize(context, descriptor, permissions);
|
|
}
|
|
permissions.push({ context, descriptor });
|
|
return { scope: "application" };
|
|
},
|
|
};
|
|
const streamHandlers = [];
|
|
const rpcRegistry = {
|
|
registerIncomingStream(handler) { streamHandlers.push(handler); return { dispose() {} }; },
|
|
};
|
|
const runtimeListeners = [];
|
|
const writes = [];
|
|
const issuedLeases = [];
|
|
const revokedOperations = [];
|
|
const leaseStore = {
|
|
issue(params) {
|
|
issuedLeases.push(params);
|
|
return Object.freeze({
|
|
kind: "secret-lease",
|
|
id: "authentication-secret-lease-000000000001",
|
|
operationId: params.operationId,
|
|
expiresAt: Date.now() + (params.ttlMs ?? 30_000),
|
|
});
|
|
},
|
|
revokeOperation(pluginId, operationId) { revokedOperations.push({ pluginId, operationId }); },
|
|
};
|
|
const runtimeSupervisor = {
|
|
onDidChangeRuntime(listener) { runtimeListeners.push(listener); return { dispose() {} }; },
|
|
async request(pluginId, method, params, requestOptions) {
|
|
return options.request({ pluginId, method, params, requestOptions, identity, accept: streamHandlers[0] });
|
|
},
|
|
async openStream(_pluginId, streamId) {
|
|
return {
|
|
async write(bytes) { writes.push([streamId, Buffer.from(bytes)]); },
|
|
async end() { writes.push([streamId, "end"]); },
|
|
cancel() { writes.push([streamId, "cancel"]); },
|
|
};
|
|
},
|
|
};
|
|
const service = new PluginExtensionProviderService({
|
|
contributionService,
|
|
leaseStore,
|
|
permissionEngine,
|
|
rpcRegistry,
|
|
runtimeSupervisor,
|
|
...(options.maxImportRecordBytes === undefined
|
|
? {}
|
|
: { maxImportRecordBytes: options.maxImportRecordBytes }),
|
|
});
|
|
return { identity, issuedLeases, permissions, revokedOperations, runtimeListeners, service, writes };
|
|
}
|
|
|
|
function incoming(streamId, emit) {
|
|
return {
|
|
streamId,
|
|
signal: new AbortController().signal,
|
|
bind(handlers) { queueMicrotask(() => emit(handlers)); },
|
|
cancel() {},
|
|
};
|
|
}
|
|
|
|
test("connection providers bind bidirectional streams to an exact runtime and clean up on runtime exit", async () => {
|
|
let h;
|
|
const output = [];
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
await handlers.onChunk({ encoding: "binary", bytes: Uint8Array.from([65, 66]) }, () => {});
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: "connection-1", status: "connected" },
|
|
};
|
|
},
|
|
});
|
|
const opened = await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-1",
|
|
configuration: { endpoint: "example" },
|
|
columns: 120,
|
|
rows: 40,
|
|
}, { onData: (bytes) => output.push(...bytes) });
|
|
assert.deepEqual(opened, {
|
|
sessionId: "session-1",
|
|
providerId: "com.example.transport.connection",
|
|
status: "connected",
|
|
diagnostics: [],
|
|
});
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(output, [65, 66]);
|
|
await h.service.write("session-1", Uint8Array.from([1, 2]));
|
|
assert.deepEqual([...h.writes[0][1]], [1, 2]);
|
|
await h.service.write("session-1", "hello \u4e16\u754c");
|
|
assert.equal(h.writes[1][1].toString("utf8"), "hello \u4e16\u754c");
|
|
assert.equal(h.permissions[0].descriptor.permission, "provider.connection");
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
assert.equal(h.revokedOperations[0].pluginId, h.identity.pluginId);
|
|
assert.match(h.revokedOperations[0].operationId, /^connection:/u);
|
|
h.runtimeListeners[0]({ pluginId: h.identity.pluginId, runtimeId: h.identity.runtimeId, status: "stopped" });
|
|
assert.throws(() => h.service.getSession("session-1"), /not found/i);
|
|
});
|
|
|
|
test("connection terminal input is chunked within the negotiated stream window", async () => {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: "connection-large-input", status: "connected" },
|
|
};
|
|
},
|
|
});
|
|
await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-large-input",
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
});
|
|
const input = Buffer.alloc((STREAM_WINDOW_BYTES * 2) + 3, 0x61);
|
|
await h.service.write("session-large-input", input);
|
|
|
|
assert.equal(h.writes.length, 3);
|
|
assert.equal(h.writes[0][0], h.writes[1][0]);
|
|
assert.equal(h.writes[1][0], h.writes[2][0]);
|
|
assert.equal(h.writes[0][1].byteLength, STREAM_WINDOW_BYTES);
|
|
assert.equal(h.writes[1][1].byteLength, STREAM_WINDOW_BYTES);
|
|
assert.equal(h.writes[2][1].byteLength, 3);
|
|
});
|
|
|
|
test("importer detection trims raw samples to the bounded JSON payload budget", async () => {
|
|
const maxProviderJsonBytes = 128 * 1024;
|
|
let seenPayload;
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
seenPayload = params.payload;
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { confidence: 0.75, format: "json" },
|
|
};
|
|
},
|
|
});
|
|
const sample = Buffer.alloc(maxProviderJsonBytes, 0x61);
|
|
|
|
assert.deepEqual(await h.service.detectImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "vault-export.json",
|
|
mediaType: "application/json",
|
|
sample,
|
|
}), { confidence: 0.75, format: "json" });
|
|
|
|
assert.ok(seenPayload);
|
|
assert.equal(seenPayload.fileName, "vault-export.json");
|
|
assert.equal(seenPayload.mediaType, "application/json");
|
|
assert.ok(Buffer.byteLength(JSON.stringify(seenPayload), "utf8") <= maxProviderJsonBytes);
|
|
const decoded = Buffer.from(seenPayload.sample.data, "base64");
|
|
assert.ok(decoded.byteLength > 0);
|
|
assert.ok(decoded.byteLength < sample.byteLength);
|
|
});
|
|
|
|
test("connection controls preserve host-owned connection and operation identities", async () => {
|
|
let h;
|
|
const controls = [];
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
if (params.operation === "open") {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: "provider-connection-1", status: "connected" },
|
|
};
|
|
}
|
|
controls.push(params);
|
|
return { requestId: params.requestId, status: "ok", result: null };
|
|
},
|
|
});
|
|
await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-control",
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
});
|
|
await h.service.control("session-control", "resize", {
|
|
connectionId: "renderer-forged-connection",
|
|
operationId: "renderer-forged-operation",
|
|
columns: 100,
|
|
rows: 30,
|
|
});
|
|
assert.equal(controls.length, 1);
|
|
assert.equal(controls[0].payload.connectionId, "provider-connection-1");
|
|
assert.notEqual(controls[0].payload.operationId, "renderer-forged-operation");
|
|
assert.match(controls[0].payload.operationId, /^connection:resize:/u);
|
|
assert.deepEqual(h.revokedOperations.at(-1), {
|
|
pluginId: h.identity.pluginId,
|
|
operationId: controls[0].payload.operationId,
|
|
});
|
|
});
|
|
|
|
test("connection control operations must acknowledge with null", async () => {
|
|
for (const operation of ["resize", "signal", "reconnect", "close"]) {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
if (params.operation === "open") {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: `provider-${operation}`, status: "connected" },
|
|
};
|
|
}
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { unexpected: true },
|
|
};
|
|
},
|
|
});
|
|
await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: `session-${operation}`,
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
});
|
|
await assert.rejects(
|
|
h.service.control(`session-${operation}`, operation, operation === "resize"
|
|
? { columns: 100, rows: 30 }
|
|
: operation === "signal"
|
|
? { signal: "interrupt" }
|
|
: {}),
|
|
/must be null|failed validation/i,
|
|
);
|
|
if (operation === "close") {
|
|
assert.throws(() => h.service.getSession(`session-${operation}`), /not found/i);
|
|
} else {
|
|
assert.ok(h.service.getSession(`session-${operation}`));
|
|
}
|
|
}
|
|
});
|
|
|
|
test("stale connection owners cannot close a same-ID replacement", () => {
|
|
const h = fixture({ async request() { throw new Error("not used"); } });
|
|
const oldOwner = Symbol("old-owner");
|
|
const newOwner = Symbol("new-owner");
|
|
const cancelled = [];
|
|
h.service.sessions.set("session-replaced", {
|
|
sessionOwner: newOwner,
|
|
input: { cancel() { cancelled.push("input"); } },
|
|
output: { cancel() { cancelled.push("output"); } },
|
|
});
|
|
|
|
assert.equal(h.service.closeSessionLocal("session-replaced", undefined, oldOwner), false);
|
|
assert.throws(() => h.service.getSession("session-replaced", oldOwner), /replaced/i);
|
|
assert.equal(h.service.getSession("session-replaced", newOwner).sessionOwner, newOwner);
|
|
assert.equal(h.service.closeSessionLocal("session-replaced", undefined, newOwner), true);
|
|
assert.deepEqual(cancelled, ["input", "output"]);
|
|
});
|
|
|
|
test("a same-ID replacement can open while the previous Provider close is pending", async () => {
|
|
let releaseClose;
|
|
const closeGate = new Promise((resolve) => { releaseClose = resolve; });
|
|
let openCount = 0;
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
if (params.operation === "open") {
|
|
openCount += 1;
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: `provider-replacement-${openCount}`, status: "connected" },
|
|
};
|
|
}
|
|
assert.equal(params.operation, "close");
|
|
await closeGate;
|
|
return { requestId: params.requestId, status: "ok", result: null };
|
|
},
|
|
});
|
|
const oldOwner = Symbol("old-owner");
|
|
const newOwner = Symbol("new-owner");
|
|
const params = {
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-fast-reconnect",
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
};
|
|
|
|
await h.service.openConnection(params, { sessionOwner: oldOwner });
|
|
const closing = h.service.control(params.sessionId, "close", {}, { sessionOwner: oldOwner });
|
|
assert.throws(() => h.service.getSession(params.sessionId, oldOwner), /not found/i);
|
|
|
|
await h.service.openConnection(params, { sessionOwner: newOwner });
|
|
assert.equal(h.service.getSession(params.sessionId, newOwner).sessionOwner, newOwner);
|
|
|
|
releaseClose();
|
|
await closing;
|
|
assert.equal(h.service.getSession(params.sessionId, newOwner).sessionOwner, newOwner);
|
|
});
|
|
|
|
test("connection status results preserve structured diagnostics", async () => {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
if (params.operation === "open") {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: "provider-status-diagnostics", status: "connected" },
|
|
};
|
|
}
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "error",
|
|
message: "Handshake rejected",
|
|
diagnostics: [{ severity: "error", message: "Remote rejected host key" }],
|
|
},
|
|
};
|
|
},
|
|
});
|
|
await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-status-diagnostics",
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
});
|
|
assert.deepEqual(await h.service.control("session-status-diagnostics", "getStatus"), {
|
|
status: "error",
|
|
message: "Handshake rejected",
|
|
diagnostics: [{ severity: "error", message: "Remote rejected host key" }],
|
|
});
|
|
});
|
|
|
|
test("connection configuration is host-validated before invoking plugin code", async () => {
|
|
let requests = 0;
|
|
const h = fixture({
|
|
connectionConfigurationSchema: {
|
|
type: "object",
|
|
properties: {
|
|
endpoint: { type: "string", minLength: 1, maxLength: 64 },
|
|
},
|
|
required: ["endpoint"],
|
|
additionalProperties: false,
|
|
},
|
|
async request() {
|
|
requests += 1;
|
|
throw new Error("plugin code must not run for invalid configuration");
|
|
},
|
|
});
|
|
await assert.rejects(h.service.invoke({
|
|
kind: "connection",
|
|
providerId: "com.example.transport.connection",
|
|
operation: "validateConfiguration",
|
|
payload: { configuration: { endpoint: "", undeclared: true } },
|
|
}), /host schema validation/i);
|
|
assert.equal(requests, 0);
|
|
});
|
|
|
|
test("connection and authentication providers preserve explicit null configuration", async () => {
|
|
let h;
|
|
const seen = [];
|
|
h = fixture({
|
|
connectionConfigurationSchema: { type: "null" },
|
|
async request({ params, identity, accept }) {
|
|
seen.push({ kind: params.kind, operation: params.operation, configuration: params.payload.configuration });
|
|
if (params.kind === "connection") {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { connectionId: "connection-null", status: "connected" },
|
|
};
|
|
}
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "authenticated",
|
|
credential: { kind: "credential", id: "credential-null-reference-1234" },
|
|
},
|
|
};
|
|
},
|
|
});
|
|
|
|
await h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-null",
|
|
configuration: null,
|
|
columns: 80,
|
|
rows: 24,
|
|
});
|
|
await h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: null,
|
|
}, () => {
|
|
throw new Error("challenge renderer must not run");
|
|
});
|
|
|
|
assert.deepEqual(seen.map((entry) => entry.configuration), [null, null]);
|
|
});
|
|
|
|
test("authentication secret challenges enter the runtime only as operation-bound one-use leases", async () => {
|
|
const responses = [];
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
if (params.operation === "begin") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { status: "challenge", challenge: { id: "otp", kind: "otp", title: "One-time code" } },
|
|
};
|
|
}
|
|
responses.push(params.payload.response);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { status: "authenticated", credential: { kind: "credential", id: "credential-reference-1234" } },
|
|
};
|
|
},
|
|
});
|
|
const result = await h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async (challenge) => {
|
|
assert.equal(challenge.kind, "otp");
|
|
return "123456";
|
|
});
|
|
assert.equal(result.status, "authenticated");
|
|
assert.equal(responses.length, 1);
|
|
assert.equal(responses[0].kind, "secret-lease");
|
|
assert.equal(typeof responses[0].id, "string");
|
|
assert.equal(h.issuedLeases.length, 1);
|
|
assert.equal(h.issuedLeases[0].pluginId, h.identity.pluginId);
|
|
assert.equal(h.issuedLeases[0].runtimeId, h.identity.runtimeId);
|
|
assert.equal(h.issuedLeases[0].operationId, responses[0].operationId);
|
|
assert.deepEqual(h.issuedLeases[0].credential, {
|
|
kind: "authentication-challenge",
|
|
challengeId: "otp",
|
|
});
|
|
assert.equal(h.issuedLeases[0].resolveSecret(), "123456");
|
|
assert.deepEqual(h.revokedOperations, [{
|
|
pluginId: h.identity.pluginId,
|
|
operationId: responses[0].operationId,
|
|
}]);
|
|
assert.equal(h.permissions[0].descriptor.permission, "provider.authentication");
|
|
});
|
|
|
|
test("authentication providers fail closed on malformed nested challenge contracts", async () => {
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "challenge",
|
|
challenge: { id: "choice", kind: "choice", title: "Choose", choices: [{ label: "Missing value" }] },
|
|
},
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async () => "unused"), /failed validation|challenge/i);
|
|
});
|
|
|
|
test("authentication cancellation reaches the provider with a fresh bounded signal", async () => {
|
|
const controller = new AbortController();
|
|
const operations = [];
|
|
const h = fixture({
|
|
async request({ params, requestOptions }) {
|
|
operations.push(params.operation);
|
|
if (params.operation === "begin") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "challenge",
|
|
challenge: { id: "password", kind: "password", title: "Password" },
|
|
},
|
|
};
|
|
}
|
|
assert.equal(params.operation, "cancel");
|
|
assert.equal(requestOptions.signal.aborted, false);
|
|
assert.equal(params.deadlineMs, 1_000);
|
|
return { requestId: params.requestId, status: "ok", result: { status: "cancelled" } };
|
|
},
|
|
});
|
|
|
|
await assert.rejects(h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async () => {
|
|
controller.abort(new DOMException("Cancelled", "AbortError"));
|
|
throw controller.signal.reason;
|
|
}, { signal: controller.signal }), (error) => error?.name === "AbortError");
|
|
assert.deepEqual(operations, ["begin", "cancel"]);
|
|
});
|
|
|
|
test("authentication text responses use the public Unicode code-point limit", async () => {
|
|
const response = "😀".repeat(8_192);
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
if (params.operation === "begin") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "challenge",
|
|
challenge: { id: "text", kind: "text", title: "Input" },
|
|
},
|
|
};
|
|
}
|
|
assert.equal(params.operation, "respond");
|
|
assert.equal(params.payload.response, response);
|
|
return { requestId: params.requestId, status: "ok", result: { status: "authenticated" } };
|
|
},
|
|
});
|
|
|
|
const result = await h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async () => response);
|
|
assert.equal(result.status, "authenticated");
|
|
});
|
|
|
|
test("authentication responses are host-validated against the exact challenge before plugin delivery", async () => {
|
|
const operations = [];
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
operations.push(params.operation);
|
|
if (params.operation === "begin") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "challenge",
|
|
challenge: {
|
|
id: "choice",
|
|
kind: "choice",
|
|
title: "Choose",
|
|
choices: [{ id: "allowed", label: "Allowed" }],
|
|
},
|
|
},
|
|
};
|
|
}
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { status: "cancelled" },
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async () => "undeclared-choice"), /choice response is invalid/i);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.deepEqual(operations, ["begin", "cancel"]);
|
|
});
|
|
|
|
test("cross-plugin authentication cannot transfer a plugin-owned secret reference", async () => {
|
|
const h = fixture({
|
|
connectionOwnerPluginId: "org.example.connection",
|
|
async request({ params }) {
|
|
if (params.operation === "cancel") {
|
|
return { requestId: params.requestId, status: "ok", result: { status: "cancelled" } };
|
|
}
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
status: "authenticated",
|
|
credential: { kind: "secret", id: "plugin-secret-reference-0001", key: "token" },
|
|
},
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.authenticate({
|
|
providerId: "com.example.transport.auth",
|
|
connectionProviderId: "com.example.transport.connection",
|
|
configuration: {},
|
|
}, async () => true), /host-owned CredentialRef/i);
|
|
});
|
|
|
|
test("importer providers produce a validated preview without mutating Vault state", async () => {
|
|
let h;
|
|
const jsonl = [
|
|
JSON.stringify({ type: "progress", completed: 1, total: 2, message: "Reading source" }),
|
|
JSON.stringify({
|
|
type: "draft",
|
|
draft: { kind: "host", value: { label: "Imported", hostname: "imported.example.com" } },
|
|
}),
|
|
JSON.stringify({ type: "warning", message: "Missing optional color" }),
|
|
"",
|
|
].join("\n");
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
await handlers.onChunk({ encoding: "binary", bytes: new TextEncoder().encode(jsonl) }, () => {});
|
|
await handlers.onClose("end");
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 1, warnings: 1, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
const progress = [];
|
|
const preview = await h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
sourceByteLength: 6,
|
|
source: (async function* source() {
|
|
yield new TextEncoder().encode("sou");
|
|
yield new TextEncoder().encode("rce");
|
|
}()),
|
|
}, { onProgress: (record) => progress.push(record) });
|
|
assert.equal(preview.records.length, 3);
|
|
assert.equal(preview.records[0].type, "progress");
|
|
assert.equal(preview.records[1].type, "draft");
|
|
assert.equal(preview.records[2].type, "warning");
|
|
assert.deepEqual(progress, [{ type: "progress", completed: 1, total: 2, message: "Reading source" }]);
|
|
assert.equal(preview.result.parsed, 1);
|
|
assert.equal(Buffer.concat(h.writes.filter(([, value]) => Buffer.isBuffer(value)).map(([, value]) => value)).toString(), "source");
|
|
assert.equal(h.writes.at(-1)[1], "end");
|
|
assert.equal(h.permissions[0].descriptor.permission, "provider.importer");
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
assert.equal(h.revokedOperations[0].pluginId, h.identity.pluginId);
|
|
assert.match(h.revokedOperations[0].operationId, /^importer:/u);
|
|
});
|
|
|
|
test("import parsing rejects an unterminated JSONL record as soon as it exceeds the line cap", async () => {
|
|
let h;
|
|
h = fixture({
|
|
maxImportRecordBytes: 256 * 1024,
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
const first = new Uint8Array(192 * 1024).fill(0x61);
|
|
const second = new Uint8Array(65 * 1024).fill(0x62);
|
|
await handlers.onChunk({ encoding: "binary", bytes: first }, () => {});
|
|
assert.throws(
|
|
() => handlers.onChunk({ encoding: "binary", bytes: second }, () => {}),
|
|
/record limits/i,
|
|
);
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 0, warnings: 0, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
data: new TextEncoder().encode("source"),
|
|
}), /record limits/i);
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
assert.match(h.revokedOperations[0].operationId, /^importer:/u);
|
|
});
|
|
|
|
test("import parsing accepts public records larger than the old private line limit", async () => {
|
|
let h;
|
|
const jsonl = `${JSON.stringify({
|
|
type: "draft",
|
|
draft: {
|
|
kind: "snippet",
|
|
value: { label: "Large snippet", command: "x".repeat(300_000) },
|
|
},
|
|
})}\n`;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
await handlers.onChunk({ encoding: "binary", bytes: new TextEncoder().encode(jsonl) }, () => {});
|
|
await handlers.onClose("end");
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 1, warnings: 0, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
|
|
const preview = await h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
data: new TextEncoder().encode("source"),
|
|
});
|
|
assert.equal(preview.records[0].type, "draft");
|
|
assert.equal(preview.records[0].draft.value.command.length, 300_000);
|
|
});
|
|
|
|
test("importer input streams fail closed if the selected file changes size", async () => {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
await handlers.onClose("end");
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 0, warnings: 0, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
sourceByteLength: 5,
|
|
source: (async function* source() { yield new TextEncoder().encode("changed"); }()),
|
|
}), /changed while it was being read|unexpected size/i);
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
assert.match(h.revokedOperations[0].operationId, /^importer:/u);
|
|
});
|
|
|
|
test("importer providers cannot report completion counts that disagree with the bounded stream", async () => {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async (handlers) => {
|
|
await handlers.onChunk({
|
|
encoding: "binary",
|
|
bytes: new TextEncoder().encode(`${JSON.stringify({ type: "warning", message: "one" })}\n`),
|
|
}, () => {});
|
|
await handlers.onClose("end");
|
|
});
|
|
assert.equal(await accept(stream, identity), true);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 1, warnings: 0, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
data: new TextEncoder().encode("source"),
|
|
}), /counts do not match/i);
|
|
});
|
|
|
|
test("unsolicited plugin streams are not claimed by the extension Provider service", async () => {
|
|
const h = fixture({ request() { throw new Error("unused"); } });
|
|
const accepted = await h.service.acceptIncomingStream({ streamId: "unsolicited" }, h.identity);
|
|
assert.equal(accepted, false);
|
|
});
|
|
|
|
test("connection startup reports a provider rejection without waiting for the output-stream deadline", async () => {
|
|
const h = fixture({
|
|
async request() {
|
|
throw new Error("provider rejected open");
|
|
},
|
|
});
|
|
await assert.rejects(h.service.openConnection({
|
|
providerId: "com.example.transport.connection",
|
|
sessionId: "session-rejected",
|
|
configuration: {},
|
|
columns: 80,
|
|
rows: 24,
|
|
deadlineMs: 300_000,
|
|
}), /provider rejected open/);
|
|
assert.equal(h.service.expectations.size, 0);
|
|
});
|
|
|
|
test("import parsing reports a provider rejection without waiting for the output-stream deadline", async () => {
|
|
let sourceReturned = false;
|
|
const h = fixture({
|
|
async request() {
|
|
throw new Error("provider rejected parse");
|
|
},
|
|
});
|
|
const source = {
|
|
[Symbol.asyncIterator]() { return this; },
|
|
next() { return Promise.resolve({ value: new TextEncoder().encode("source"), done: false }); },
|
|
return() { sourceReturned = true; return Promise.resolve({ done: true }); },
|
|
};
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
source,
|
|
sourceByteLength: 6,
|
|
deadlineMs: 300_000,
|
|
}), /provider rejected parse/);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.equal(sourceReturned, true);
|
|
assert.equal(h.service.expectations.size, 0);
|
|
});
|
|
|
|
test("import parsing enforces its deadline when a successful provider forgets to end output", async () => {
|
|
let h;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { parsed: 0, warnings: 0, errors: 0 },
|
|
};
|
|
},
|
|
});
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
data: new TextEncoder().encode("source"),
|
|
deadlineMs: 25,
|
|
}), /output stream exceeded its deadline/i);
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
assert.match(h.revokedOperations[0].operationId, /^importer:/u);
|
|
});
|
|
|
|
test("import parsing enforces its deadline while the selected input source stalls", async () => {
|
|
let h;
|
|
let sourceReturned = false;
|
|
h = fixture({
|
|
async request({ params, identity, accept }) {
|
|
const stream = incoming(params.payload.outputStreamId, async () => {});
|
|
assert.equal(await accept(stream, identity), true);
|
|
return new Promise(() => {});
|
|
},
|
|
});
|
|
const source = {
|
|
[Symbol.asyncIterator]() { return this; },
|
|
next() { return new Promise(() => {}); },
|
|
return() { sourceReturned = true; return Promise.resolve({ done: true }); },
|
|
};
|
|
await assert.rejects(h.service.parseImporter({
|
|
providerId: "com.example.transport.importer",
|
|
fileName: "hosts.json",
|
|
source,
|
|
sourceByteLength: 6,
|
|
deadlineMs: 25,
|
|
}), /input source exceeded its deadline/i);
|
|
await new Promise((resolve) => setImmediate(resolve));
|
|
assert.equal(sourceReturned, true);
|
|
assert.equal(h.revokedOperations.length, 1);
|
|
});
|
|
|
|
test("sync providers require provider.sync and only exchange encrypted object bytes", async () => {
|
|
const cipher = Buffer.from([0x9b, 0x01, 0x02, 0x03]);
|
|
let writePayload;
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
assert.equal(params.kind, "sync");
|
|
if (params.operation === "connect") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { account: { id: "acct-1", name: "Plugin Sync" } },
|
|
};
|
|
}
|
|
if (params.operation === "getCapabilities") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
revisions: true,
|
|
conditionalWrites: true,
|
|
atomicReplacement: true,
|
|
maxObjectBytes: 1024,
|
|
},
|
|
};
|
|
}
|
|
if (params.operation === "readObject") {
|
|
assert.equal(params.payload.key, "netcatty-vault.json");
|
|
assert.equal(typeof params.payload.outputStreamId, "string");
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: {
|
|
found: true,
|
|
byteLength: cipher.byteLength,
|
|
encoding: "base64",
|
|
data: cipher.toString("base64"),
|
|
revision: "rev-1",
|
|
},
|
|
};
|
|
}
|
|
if (params.operation === "writeObject") {
|
|
writePayload = params.payload;
|
|
assert.equal(params.payload.key, "netcatty-vault.json");
|
|
assert.equal(params.payload.encoding, "base64");
|
|
assert.equal(Buffer.from(params.payload.data, "base64").equals(cipher), true);
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { created: true, revision: "rev-2" },
|
|
};
|
|
}
|
|
if (params.operation === "deleteObject") {
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { deleted: true },
|
|
};
|
|
}
|
|
if (params.operation === "disconnect") {
|
|
return { requestId: params.requestId, status: "ok", result: null };
|
|
}
|
|
throw new Error(`unexpected operation ${params.operation}`);
|
|
},
|
|
});
|
|
|
|
const connected = await h.service.connectSync({
|
|
providerId: "com.example.transport.sync",
|
|
configuration: { endpoint: "https://example.test" },
|
|
});
|
|
assert.equal(connected.account.id, "acct-1");
|
|
assert.equal(h.permissions[0].descriptor.permission, "provider.sync");
|
|
|
|
const caps = await h.service.getSyncCapabilities({
|
|
providerId: "com.example.transport.sync",
|
|
});
|
|
assert.equal(caps.conditionalWrites, true);
|
|
|
|
const read = await h.service.readSyncObject({
|
|
providerId: "com.example.transport.sync",
|
|
key: "netcatty-vault.json",
|
|
});
|
|
assert.equal(read.found, true);
|
|
assert.equal(Buffer.from(read.bytes).equals(cipher), true);
|
|
assert.equal(read.revision, "rev-1");
|
|
|
|
const written = await h.service.writeSyncObject({
|
|
providerId: "com.example.transport.sync",
|
|
key: "netcatty-vault.json",
|
|
bytes: cipher,
|
|
});
|
|
assert.equal(written.created, true);
|
|
assert.equal(written.revision, "rev-2");
|
|
assert.equal(writePayload.byteLength, cipher.byteLength);
|
|
|
|
const deleted = await h.service.deleteSyncObject({
|
|
providerId: "com.example.transport.sync",
|
|
key: "netcatty-vault.json",
|
|
});
|
|
assert.equal(deleted.deleted, true);
|
|
|
|
const disconnected = await h.service.disconnectSync({
|
|
providerId: "com.example.transport.sync",
|
|
});
|
|
assert.equal(disconnected, null);
|
|
});
|
|
|
|
test("sync providers fail closed without provider.sync permission", async () => {
|
|
const { PluginRpcError, RPC_ERRORS } = require("./rpcRouter.cjs");
|
|
const h = fixture({
|
|
async request() {
|
|
throw new Error("plugin should not be invoked");
|
|
},
|
|
async authorize() {
|
|
throw new PluginRpcError(RPC_ERRORS.permissionDenied, "missing provider.sync");
|
|
},
|
|
});
|
|
await assert.rejects(h.service.connectSync({
|
|
providerId: "com.example.transport.sync",
|
|
configuration: {},
|
|
}), /provider\.sync|permission/i);
|
|
});
|
|
|
|
test("sync writeObject streams large encrypted objects outside the JSON budget", async () => {
|
|
const { INLINE_SYNC_OBJECT_BYTES } = require("./extensionProviderService.cjs");
|
|
const large = Buffer.alloc(INLINE_SYNC_OBJECT_BYTES + 16, 0xab);
|
|
let sawStream = false;
|
|
const h = fixture({
|
|
async request({ params }) {
|
|
assert.equal(params.operation, "writeObject");
|
|
assert.equal(typeof params.payload.inputStreamId, "string");
|
|
assert.equal(params.payload.data, undefined);
|
|
sawStream = true;
|
|
return {
|
|
requestId: params.requestId,
|
|
status: "ok",
|
|
result: { created: false, revision: "stream-rev" },
|
|
};
|
|
},
|
|
});
|
|
const result = await h.service.writeSyncObject({
|
|
providerId: "com.example.transport.sync",
|
|
key: "netcatty-vault.json",
|
|
bytes: large,
|
|
});
|
|
assert.equal(sawStream, true);
|
|
assert.equal(result.revision, "stream-rev");
|
|
assert.ok(h.writes.some((entry) => entry[1] === "end"));
|
|
});
|