1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-05 03:03:26 +00:00

feat: ban unserializable args

This commit is contained in:
Andreas Coroiu
2025-10-27 10:30:15 +01:00
parent bd8033cdd5
commit 39e5ecc586
2 changed files with 103 additions and 2 deletions

View File

@@ -16,6 +16,89 @@ export type RemoteValue<T> = T extends { free(): void }
? Promise<R>
: Promise<T>;
export type RemoteFunction<T extends (...args: any[]) => any> = (
...args: Parameters<T>
export type RemoteFunction<T extends (...args: any[]) => any> = <A extends Parameters<T>>(
...args: SerializableArgs<A>
) => RemoteValue<ReturnType<T>>;
// Serializable type rules to mirror `isSerializable` from rpc/server.ts
// - Primitives: string | number | boolean | null
// - Arrays: elements must be Serializable
// - Plain objects: all non-function properties must be Serializable
// - Everything else (functions, class instances, Date, Map, Set, etc.) is NOT serializable
type IsAny<T> = 0 extends 1 & T ? true : false;
type IsNever<T> = [T] extends [never] ? true : false;
type IsFunction<T> = T extends (...args: any[]) => any ? true : false;
type IsArray<T> = T extends readonly any[] ? true : false;
type IsSerializablePrimitive<T> = [T] extends [string | number | boolean | null] ? true : false;
type IsSerializableArray<T> = T extends readonly (infer U)[] ? IsSerializable<U> : false;
type PropsAreSerializable<T> =
Exclude<
{
[K in keyof T]-?: IsFunction<T[K]> extends true ? false : IsSerializable<T[K]>;
}[keyof T],
true
> extends never
? true
: false;
type IsSpecialObject<T> = T extends
| Date
| RegExp
| Map<any, any>
| Set<any>
| WeakMap<any, any>
| WeakSet<any>
? true
: false;
type IsSerializableObject<T> =
IsFunction<T> extends true
? false
: IsArray<T> extends true
? false
: IsSpecialObject<T> extends true
? false
: T extends object
? PropsAreSerializable<T>
: false;
export type IsSerializable<T> =
IsAny<T> extends true
? false // discourage any; use explicit types
: IsNever<T> extends true
? true
: IsSerializablePrimitive<T> extends true
? true
: IsArray<T> extends true
? IsSerializableArray<T>
: IsSerializableObject<T>;
// Public helper alias for consumers
export type Serializable<T> = IsSerializable<T> extends true ? T : never;
// Human-readable reason per kind
type NonSerializableReason<T> =
IsFunction<T> extends true
? "functions are not serializable"
: IsArray<T> extends true
? "array contains non-serializable element(s)"
: IsSpecialObject<T> extends true
? "class instances / special objects (Date/Map/Set/RegExp/...) are not serializable"
: T extends object
? "object contains non-serializable property"
: "type is not serializable";
// Tuple-literal error type so TS prints a helpful message at the callsite
type EnsureSerializableWithMessage<T, C extends string> =
IsSerializable<T> extends true
? T
: ["Non-serializable RPC argument", C, NonSerializableReason<T>];
type IndexLabel<K> = K extends string | number ? `${K}` : "?";
type SerializableArgs<A extends any[]> = {
[K in keyof A]: EnsureSerializableWithMessage<A[K], `arg[${IndexLabel<K>}]`>;
};

View File

@@ -2,6 +2,7 @@ import { firstValueFrom, map, Observable } from "rxjs";
import { RpcClient, RpcRequestChannel } from "./client";
import { Command, Response } from "./protocol";
import { RpcObjectReference } from "./proxies";
import { RpcServer } from "./server";
describe("RpcServer", () => {
@@ -45,8 +46,21 @@ describe("RpcServer", () => {
const wasmObj = await remoteInstance.getWasmGreeting("Wasm World");
const greeting = await wasmObj.greet();
expect(wasmObj).toBeInstanceOf(RpcObjectReference);
expect(greeting).toBe("Hello, Wasm World!");
});
it("returns plain objects by value", async () => {
const remoteInstance = await firstValueFrom(client.getRoot());
const result = await remoteInstance.echo({
message: "Hello, World!",
array: [1, "2", null],
});
expect(result).not.toBeInstanceOf(RpcObjectReference);
expect(result).toEqual({ message: "Hello, World!", array: [1, "2", null] });
});
});
class TestClass {
@@ -63,6 +77,10 @@ class TestClass {
getWasmGreeting(name: string): WasmLikeObject {
return new WasmLikeObject(name);
}
echo<T>(obj: T): T {
return obj;
}
}
class WasmLikeObject {