[Init] Initial commit - NetMesh terminal manager
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
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
This commit is contained in:
133
electron/plugins/nativePermissionDecision.test.cjs
Normal file
133
electron/plugins/nativePermissionDecision.test.cjs
Normal file
@@ -0,0 +1,133 @@
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
|
||||
const {
|
||||
MAX_VISIBLE_FIELD_LENGTH,
|
||||
MAX_VISIBLE_RESOURCES,
|
||||
createNativePermissionDecisionProvider,
|
||||
describePermissionRequest,
|
||||
escapePermissionText,
|
||||
} = require("./nativePermissionDecision.cjs");
|
||||
|
||||
function permissionRequest(overrides = {}) {
|
||||
return {
|
||||
requestId: "request-1",
|
||||
pluginId: "com.example.permissions",
|
||||
pluginName: "Permissions",
|
||||
publisher: "example",
|
||||
permission: "filesystem.read",
|
||||
resources: ["/allowed"],
|
||||
resourceKinds: ["directory"],
|
||||
reason: "Read project files",
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("native permission decisions expose every host-owned grant scope", async () => {
|
||||
for (const { sessionId, response, expected } of [
|
||||
{ response: 0, expected: { requestId: "request-1", decision: "deny" } },
|
||||
{ response: 1, expected: { requestId: "request-1", decision: "allow", scope: "once" } },
|
||||
{ sessionId: "session-1", response: 2, expected: {
|
||||
requestId: "request-1", decision: "allow", scope: "session",
|
||||
} },
|
||||
{ response: 2, expected: {
|
||||
requestId: "request-1", decision: "allow", scope: "application",
|
||||
} },
|
||||
{ response: 3, expected: {
|
||||
requestId: "request-1", decision: "allow", scope: "always",
|
||||
} },
|
||||
]) {
|
||||
let captured;
|
||||
const signal = new AbortController().signal;
|
||||
const provider = createNativePermissionDecisionProvider({
|
||||
dialog: {
|
||||
async showMessageBox(_window, options) {
|
||||
captured = options;
|
||||
return { response };
|
||||
},
|
||||
},
|
||||
window: {},
|
||||
});
|
||||
assert.deepEqual(await provider(permissionRequest({ sessionId }), { signal }), expected);
|
||||
assert.equal(captured.signal, signal);
|
||||
assert.equal(captured.cancelId, 0);
|
||||
assert.match(captured.detail, /directory and descendants/u);
|
||||
}
|
||||
});
|
||||
|
||||
test("native permission decisions fail closed on cancellation and abort", async () => {
|
||||
const provider = createNativePermissionDecisionProvider({
|
||||
dialog: { showMessageBox: async () => ({ response: 999 }) },
|
||||
});
|
||||
assert.deepEqual(await provider(permissionRequest()), {
|
||||
requestId: "request-1",
|
||||
decision: "deny",
|
||||
});
|
||||
const controller = new AbortController();
|
||||
controller.abort(new Error("runtime stopped"));
|
||||
await assert.rejects(provider(permissionRequest(), { signal: controller.signal }), /runtime stopped/);
|
||||
});
|
||||
|
||||
test("terminal interceptor permission prompts omit the unsupported one-use choice", async () => {
|
||||
let captured;
|
||||
const provider = createNativePermissionDecisionProvider({
|
||||
dialog: {
|
||||
async showMessageBox(options) {
|
||||
captured = options;
|
||||
return { response: 1 };
|
||||
},
|
||||
},
|
||||
});
|
||||
assert.deepEqual(await provider(permissionRequest({
|
||||
sessionId: "session-1",
|
||||
operationId: `terminal.interceptor.input:com.example.${"x".repeat(170)}`,
|
||||
allowedScopes: ["session", "application", "always"],
|
||||
})), {
|
||||
requestId: "request-1",
|
||||
decision: "allow",
|
||||
scope: "session",
|
||||
});
|
||||
assert.deepEqual(captured.buttons, [
|
||||
"Deny",
|
||||
"Allow for Session",
|
||||
"Allow for Application",
|
||||
"Always Allow",
|
||||
]);
|
||||
});
|
||||
|
||||
test("native permission request details are bounded and preserve resource kinds", () => {
|
||||
const resources = Array.from({ length: MAX_VISIBLE_RESOURCES + 5 }, (_, index) => `/path/${index}`);
|
||||
const detail = describePermissionRequest(permissionRequest({
|
||||
resources,
|
||||
resourceKinds: resources.map(() => "exact"),
|
||||
}));
|
||||
assert.match(detail, /and 5 more/u);
|
||||
assert.doesNotMatch(detail, /\/path\/24/u);
|
||||
});
|
||||
|
||||
test("native permission dialogs visibly escape control and bidi characters", async () => {
|
||||
let captured;
|
||||
const provider = createNativePermissionDecisionProvider({
|
||||
dialog: {
|
||||
async showMessageBox(options) {
|
||||
captured = options;
|
||||
return { response: 0 };
|
||||
},
|
||||
},
|
||||
});
|
||||
await provider(permissionRequest({
|
||||
pluginName: "Plugin\nPermission: vault.credentials",
|
||||
publisher: "publisher\u202e",
|
||||
reason: "Read this\r\nAlways Allow",
|
||||
resources: ["/safe\nPermission: runtime.advanced"],
|
||||
resourceKinds: ["exact"],
|
||||
}));
|
||||
assert.match(captured.message, /Plugin\\nPermission: vault\.credentials/u);
|
||||
assert.match(captured.detail, /publisher\\u202e/u);
|
||||
assert.match(captured.detail, /Read this\\r\\nAlways Allow/u);
|
||||
assert.match(captured.detail, /\/safe\\nPermission: runtime\.advanced/u);
|
||||
assert.equal(captured.detail.split("\n").filter((line) => line.startsWith("Permission:")).length, 1);
|
||||
assert.equal(escapePermissionText("x".repeat(MAX_VISIBLE_FIELD_LENGTH + 20)).length, MAX_VISIBLE_FIELD_LENGTH);
|
||||
});
|
||||
Reference in New Issue
Block a user