[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:
187
electron/plugins/secureCapabilities.cjs
Normal file
187
electron/plugins/secureCapabilities.cjs
Normal file
@@ -0,0 +1,187 @@
|
||||
"use strict";
|
||||
|
||||
const { PluginRpcError, RPC_ERRORS } = require("./rpcRouter.cjs");
|
||||
const { assertSecretKey } = require("./secretStore.cjs");
|
||||
|
||||
function assertSecretParams(params, options = {}) {
|
||||
if (!params || typeof params !== "object" || Array.isArray(params)) {
|
||||
throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Plugin secret parameters are invalid");
|
||||
}
|
||||
const key = assertSecretKey(params.key);
|
||||
if (options.value) {
|
||||
if (typeof params.value !== "string") {
|
||||
throw new PluginRpcError(RPC_ERRORS.invalidArgument, "Plugin secret value is invalid");
|
||||
}
|
||||
return { key, value: params.value };
|
||||
}
|
||||
return { key };
|
||||
}
|
||||
|
||||
function secretAuthorization(params, action) {
|
||||
const value = assertSecretParams(params);
|
||||
return {
|
||||
permission: "secrets",
|
||||
resources: [`secret:${value.key}`],
|
||||
reason: `${action} plugin secret ${value.key}`,
|
||||
operationId: `secret:${action.toLowerCase()}:${value.key}`,
|
||||
};
|
||||
}
|
||||
|
||||
function registerSecurePluginCapabilities(registry, options) {
|
||||
registry.use(options.quotaManager.createMiddleware());
|
||||
registry.use(options.permissionEngine.createMiddleware());
|
||||
|
||||
registry.registerRequest("secrets.get", async ({ key }, context) => {
|
||||
const secret = options.secretStore.getReference(context.pluginId, key);
|
||||
return secret ? { found: true, secret } : { found: false };
|
||||
}, {
|
||||
metadata: { capability: "secrets", mutating: false, permission: "secrets" },
|
||||
validateParams: (params) => assertSecretParams(params),
|
||||
authorization: (params) => secretAuthorization(params, "Read"),
|
||||
});
|
||||
registry.registerRequest("secrets.set", async ({ key, value }, context) => {
|
||||
await context.assertActive();
|
||||
return { secret: options.secretStore.set(context.pluginId, key, value) };
|
||||
}, {
|
||||
metadata: { capability: "secrets", mutating: true, permission: "secrets" },
|
||||
validateParams: (params) => assertSecretParams(params, { value: true }),
|
||||
authorization: (params) => secretAuthorization(params, "Store"),
|
||||
});
|
||||
registry.registerRequest("secrets.delete", async ({ key }, context) => {
|
||||
await context.assertActive();
|
||||
options.secretStore.delete(context.pluginId, key);
|
||||
return null;
|
||||
}, {
|
||||
metadata: { capability: "secrets", mutating: true, permission: "secrets" },
|
||||
validateParams: (params) => assertSecretParams(params),
|
||||
authorization: (params) => secretAuthorization(params, "Delete"),
|
||||
});
|
||||
|
||||
registry.registerRequest("credentials.createLease", (params, context) => (
|
||||
options.credentialBroker.createLease(params, context)
|
||||
), {
|
||||
metadata: { capability: "credentials", mutating: true, permission: "secrets" },
|
||||
validateParams: options.assertLeaseParams,
|
||||
authorization: (params) => options.credentialBroker.describeAuthorization(params),
|
||||
});
|
||||
|
||||
registry.registerRequest("network.request", async (params, context) => {
|
||||
// params are already registry-validated (preserve credentialLease/authorization).
|
||||
const validated = params;
|
||||
const lease = validated?.credentialLease;
|
||||
if (lease === undefined) {
|
||||
return options.networkBroker.request(validated, context);
|
||||
}
|
||||
if (!options.credentialBroker || typeof options.credentialBroker.consumeLease !== "function") {
|
||||
throw new PluginRpcError(RPC_ERRORS.unavailable, "Plugin credential leases are unavailable");
|
||||
}
|
||||
// Bind consumption to the host-owned network operation id (network:<origin>),
|
||||
// not the plugin-echoed lease.operationId, so leases cannot be replayed across ops.
|
||||
const hostOperationId = options.networkBroker.describeAuthorization(validated).operationId;
|
||||
const plaintext = await options.credentialBroker.consumeLease(
|
||||
context,
|
||||
lease,
|
||||
hostOperationId,
|
||||
);
|
||||
if (typeof plaintext !== "string" || plaintext.length < 1) {
|
||||
throw new PluginRpcError(RPC_ERRORS.dataLoss, "Plugin credential lease resolved to an empty secret");
|
||||
}
|
||||
const authorization = validated?.authorization && typeof validated.authorization === "object"
|
||||
&& !Array.isArray(validated.authorization)
|
||||
? validated.authorization
|
||||
: { scheme: "Bearer" };
|
||||
const scheme = authorization.scheme === "Basic" ? "Basic" : "Bearer";
|
||||
const headers = { ...(validated.headers ?? {}) };
|
||||
if (scheme === "Basic") {
|
||||
const username = typeof authorization.username === "string" ? authorization.username : "";
|
||||
headers.authorization = `Basic ${Buffer.from(`${username}:${plaintext}`, "utf8").toString("base64")}`;
|
||||
} else {
|
||||
headers.authorization = `Bearer ${plaintext}`;
|
||||
}
|
||||
const { credentialLease: _lease, authorization: _auth, ...requestParams } = validated;
|
||||
return options.networkBroker.request({
|
||||
...requestParams,
|
||||
headers,
|
||||
}, context);
|
||||
}, {
|
||||
metadata: { capability: "network", mutating: true, permission: "network" },
|
||||
validateParams: (params) => options.networkBroker.validate(params),
|
||||
authorization: (params) => options.networkBroker.describeAuthorization(params),
|
||||
});
|
||||
|
||||
registry.registerRequest("filesystem.readFile", (params, context) => (
|
||||
options.filesystemBroker.readFile(params, context)
|
||||
), {
|
||||
metadata: { capability: "filesystem", mutating: false, permission: "filesystem.read" },
|
||||
validateParams: (params) => options.filesystemBroker.validateRead(params),
|
||||
authorization: (params) => options.filesystemBroker.describeReadAuthorization(params, "exact"),
|
||||
});
|
||||
registry.registerRequest("filesystem.stat", (params, context) => (
|
||||
options.filesystemBroker.stat(params, context)
|
||||
), {
|
||||
metadata: { capability: "filesystem", mutating: false, permission: "filesystem.read" },
|
||||
validateParams: (params) => options.filesystemBroker.validatePath(params),
|
||||
authorization: (params) => options.filesystemBroker.describeReadAuthorization(params, "exact"),
|
||||
});
|
||||
registry.registerRequest("filesystem.readDirectory", (params, context) => (
|
||||
options.filesystemBroker.readDirectory(params, context)
|
||||
), {
|
||||
metadata: { capability: "filesystem", mutating: false, permission: "filesystem.read" },
|
||||
validateParams: (params) => options.filesystemBroker.validatePath(params),
|
||||
authorization: (params) => options.filesystemBroker.describeReadAuthorization(params, "directory"),
|
||||
});
|
||||
registry.registerRequest("filesystem.writeFile", (params, context) => (
|
||||
options.filesystemBroker.writeFile(params, context)
|
||||
), {
|
||||
metadata: { capability: "filesystem", mutating: true, permission: "filesystem.write" },
|
||||
validateParams: (params) => options.filesystemBroker.validateWrite(params),
|
||||
authorization: (params) => options.filesystemBroker.describeWriteAuthorization(params),
|
||||
});
|
||||
|
||||
registry.registerRequest("companion.start", (params, context) => (
|
||||
options.companionSupervisor.start(params, context)
|
||||
), {
|
||||
metadata: { capability: "companion", mutating: true, permission: "companion.execute" },
|
||||
validateParams: (params) => options.companionSupervisor.validateStart(params),
|
||||
authorization: (params, context) => (
|
||||
options.companionSupervisor.describeStartAuthorization(params, context)
|
||||
),
|
||||
});
|
||||
registry.registerRequest("companion.request", async (params, context) => {
|
||||
const validated = options.companionSupervisor.validateRequest(params);
|
||||
if (validated.credentialLeases === undefined) {
|
||||
return options.companionSupervisor.request(validated, context);
|
||||
}
|
||||
const credentials = {};
|
||||
for (const [name, lease] of Object.entries(validated.credentialLeases)) {
|
||||
credentials[name] = await options.credentialBroker.consumeLease(
|
||||
context,
|
||||
lease,
|
||||
validated.operationId,
|
||||
);
|
||||
}
|
||||
await context.assertActive();
|
||||
return options.companionSupervisor.request({
|
||||
handleId: validated.handleId,
|
||||
method: validated.method,
|
||||
params: Object.freeze({
|
||||
payload: validated.params ?? null,
|
||||
credentials: Object.freeze(credentials),
|
||||
}),
|
||||
timeoutMs: validated.timeoutMs,
|
||||
}, context);
|
||||
}, {
|
||||
metadata: { capability: "companion", mutating: true, permission: "companion.execute" },
|
||||
validateParams: (params) => options.companionSupervisor.validateRequest(params),
|
||||
authorization: (params, context) => options.companionSupervisor.describeHandleAuthorization(params, context),
|
||||
});
|
||||
registry.registerRequest("companion.stop", (params, context) => (
|
||||
options.companionSupervisor.stop(params, context)
|
||||
), {
|
||||
metadata: { capability: "companion", mutating: true, permission: "companion.execute" },
|
||||
validateParams: (params) => options.companionSupervisor.validateStop(params),
|
||||
authorization: (params, context) => options.companionSupervisor.describeHandleAuthorization(params, context),
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { assertSecretParams, registerSecurePluginCapabilities, secretAuthorization };
|
||||
Reference in New Issue
Block a user