mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-03 07:03:18 +00:00
fix(plugins): close subscription gaps found in review
- Losing daemon.manage now releases the session's plugin subscriptions; binary events bypass the outbound permission check, so streams would otherwise outlive the revocation. - A subscribe that times out sends unsubscribe, since the plugin may still start the handler after the daemon gave up. - The byte limit leaves room for the largest frame header and routing, and lives in assertPluginWireValue so events, outputs and inputs share it. - A release right after the host accepted keeps `ready` resolved, so a handler that returns at once reports closed rather than an error. - Session trace logging tolerates loggers without isLevelEnabled.
This commit is contained in:
@@ -8,6 +8,8 @@ import {
|
||||
encodePluginInputFrame,
|
||||
encodePluginOutputFrame,
|
||||
FileTransferOpcode,
|
||||
MAX_PLUGIN_PAYLOAD_BYTES,
|
||||
MAX_ROUTED_PAYLOAD_BYTES,
|
||||
PluginFrameOpcode,
|
||||
TerminalStreamOpcode,
|
||||
} from "./index.js";
|
||||
@@ -70,6 +72,21 @@ describe("the plugin byte rule", () => {
|
||||
expect(() => assertPluginWireValue(new Uint8Array(4), "event")).not.toThrow();
|
||||
});
|
||||
|
||||
it("rejects bytes that would not fit in one frame with the largest header", () => {
|
||||
const bytes = new Uint8Array(MAX_PLUGIN_PAYLOAD_BYTES + 1);
|
||||
expect(() => assertPluginWireValue(bytes.subarray(1), "event")).not.toThrow();
|
||||
expect(() => assertPluginWireValue(bytes, "event")).toThrow(
|
||||
`event is ${MAX_PLUGIN_PAYLOAD_BYTES + 1} bytes; one value carries at most ${MAX_PLUGIN_PAYLOAD_BYTES} bytes`,
|
||||
);
|
||||
const frame = encodePluginInputFrame({
|
||||
requestId: "r".repeat(255),
|
||||
pluginId: "p",
|
||||
method: "m".repeat(0xffff - 30),
|
||||
payload: bytes.subarray(1),
|
||||
});
|
||||
expect(frame.byteLength).toBeLessThanOrEqual(MAX_ROUTED_PAYLOAD_BYTES);
|
||||
});
|
||||
|
||||
it("rejects bytes nested in a value and names the rule and the path", () => {
|
||||
expect(() => assertPluginWireValue({ frames: [{ data: new Uint8Array(1) }] }, "event")).toThrow(
|
||||
"event.frames[0].data is a Uint8Array. Bytes cross the wire only as the whole value: send the Uint8Array itself, or encode it into the value.",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { MAX_ROUTED_PAYLOAD_BYTES } from "./host-route.js";
|
||||
|
||||
/**
|
||||
* Plugin values that are a `Uint8Array` travel as binary frames. Opcodes 0x20-0x2f are
|
||||
* reserved for plugin traffic. Every frame is `[opcode][id length u8][id]...` and the payload
|
||||
@@ -14,6 +16,12 @@ export const PluginFrameOpcode = {
|
||||
|
||||
export type PluginFrameOpcode = (typeof PluginFrameOpcode)[keyof typeof PluginFrameOpcode];
|
||||
|
||||
/** The largest frame header: an input's opcode, ID and header JSON, each at its maximum length. */
|
||||
const MAX_PLUGIN_FRAME_HEADER_BYTES = 2 + 0xff + 2 + 0xffff;
|
||||
|
||||
/** The largest `Uint8Array` a plugin value carries, so its frame fits in one routed message. */
|
||||
export const MAX_PLUGIN_PAYLOAD_BYTES = MAX_ROUTED_PAYLOAD_BYTES - MAX_PLUGIN_FRAME_HEADER_BYTES;
|
||||
|
||||
export type PluginFrame =
|
||||
| { opcode: typeof PluginFrameOpcode.Event; subscriptionId: string; payload: Uint8Array }
|
||||
| { opcode: typeof PluginFrameOpcode.Output; requestId: string; payload: Uint8Array }
|
||||
@@ -112,7 +120,13 @@ function decodeInputHeader(bytes: Uint8Array): { pluginId: string; method: strin
|
||||
* with no `Uint8Array` inside it. Checks object structure only; never reads the bytes.
|
||||
*/
|
||||
export function assertPluginWireValue(value: unknown, label: string): void {
|
||||
if (value instanceof Uint8Array) return;
|
||||
if (value instanceof Uint8Array) {
|
||||
if (value.byteLength > MAX_PLUGIN_PAYLOAD_BYTES)
|
||||
throw new RangeError(
|
||||
`${label} is ${value.byteLength} bytes; one value carries at most ${MAX_PLUGIN_PAYLOAD_BYTES} bytes`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const nested = findNestedBytes(value, label);
|
||||
if (nested)
|
||||
throw new TypeError(
|
||||
|
||||
Reference in New Issue
Block a user