From f0dffe64747eeca6df7e680c5507d0fadcbb54b7 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Fri, 15 May 2026 21:52:52 -0400 Subject: [PATCH] Update security docs Signed-off-by: Andrew Stein --- SECURITY.md | 53 +++++++++++++++++++ docs/md/FAQ.md | 32 +++++++++++ .../perspective-js/src/ts/perspective.node.ts | 12 +++++ .../perspective/handlers/aiohttp.py | 8 +++ .../perspective/handlers/starlette.py | 8 +++ .../perspective/handlers/tornado.py | 8 +++ 6 files changed, 121 insertions(+) diff --git a/SECURITY.md b/SECURITY.md index 953adb3555..af6179448b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -23,3 +23,56 @@ escalate to the OpenJS Foundation CNA at `security@lists.openjsf.org`. If the project acknowledges your report but does not provide any further response or engagement within 14 days, escalation is also appropriate. + +## Threat Model + +The Perspective WebSocket `Server` (the Python `tornado.py`/`aiohttp.py`/ +`starlette.py` adapters and the Node `WebSocketServer`) is not a security +boundary against its `Client`. Any `Client` that can send messages to a +`Server` is treated as the author of the queries it submits, and is permitted +to create or delete `Table`/`View` resources, author arbitrary +[expression columns](./docs/md/explanation/view/config/expressions.md), and — +for `Virtual Server` backends (DuckDB, ClickHouse, Polars, custom +`VirtualServerHandler`) — author SQL fragments executed under the configured +database role. The `Virtual Server` SQL builder does not parameterize or +validate client-supplied identifiers, expressions, or operators, because +there is no privilege boundary inside the engine for it to enforce. + +The bundled WebSocket adapters above are reference integrations: they do not +implement authentication, authorization, CSRF protection, rate limiting, or +origin enforcement, and are not intended to be exposed directly to untrusted +networks. Production deployments must place an authenticating reverse proxy, +application-framework middleware, or API gateway between the network and the +`Server`. + +### In-browser WASM deployments are not affected + +This applies only when the `Server` runs in a separate process reached +over a network transport (WebSocket). In-browser deployments — including +`perspective` running entirely in a Web Worker, the +[`perspective-server` WASM build](./docs/md/explanation/architecture.md), +[`duckdb-wasm`](./docs/md/how_to/javascript/virtual_server/duckdb.md), +and any other `Virtual Server` whose backend executes inside the browser +tab — do not have this concern. The `Client` and `Server` share a single +security context (the browser tab, under the same-origin policy of the +embedding page), there is no network transport for a third-party principal +to reach, and the only principal who can submit queries is the same user who +loaded the page. SQL or expression "injection" by that user against a backend +running inside their own tab is not a privilege escalation. + +### In scope + +The following remain in scope for security reports: + +- Memory-safety bugs in the C++ engine, Rust crates, or WASM module. +- Bugs in the `` Shadow DOM, CSS, or sanitization paths + that allow injected markup or styles to escape the component or affect + the embedding page. +- Crashes, hangs, panics, or denial-of-service in the engine reachable from + well-formed protobuf messages. +- Breaches of the trust model above — for example, a `Client` causing effects + on a different `Client`'s `Server` state in a configuration where those + `Client`s share a `Server` but are intended to be isolated, or an + expression column reaching state outside the `Server` it was authored + against. +- Vulnerabilities in the published artifacts themselves (supply-chain). diff --git a/docs/md/FAQ.md b/docs/md/FAQ.md index 9d5705da51..2174498cbb 100644 --- a/docs/md/FAQ.md +++ b/docs/md/FAQ.md @@ -328,6 +328,38 @@ of each mode. +### Is the WebSocket Perspective `Server` safe to expose to untrusted clients? + +No. The WebSocket `Server` is not a security boundary. Every connected `Client` +is treated as the author of the queries it submits, and is permitted to create +and delete `Table`/`View` resources, author arbitrary +[expression columns](./explanation/view/config/expressions.md), and — for +[Virtual Server](./explanation/virtual_servers.md) backends like DuckDB or +ClickHouse — author SQL fragments executed under the configured database +role. The bundled WebSocket adapters +(`tornado.py`/`aiohttp.py`/`starlette.py`/`WebSocketServer`) are reference +integrations and do not authenticate, authorize, or enforce origin policy. + +WebSocket Deployments that need per-user isolation must put an authenticating +proxy in front of the `Server`, run a least-privileged database role for any +`Virtual Server` backend, and/or isolate users into separate `Server` +instances. See [`SECURITY.md`](../../SECURITY.md) for the full threat model +and deployment guidance. + +Obviously, none of this applies to WASM DBs like Perspective and DuckDB. + +### Does Perspective sanitize SQL `Virtual Server`s? + +No, by design. [Virtual Server](./explanation/virtual_servers.md) backends +interpolate client-supplied `view_id`, `table_id`, `column_name`, expression +strings, and filter operators directly into SQL templates without +parameterization or whitelist validation. The `Client` is the author of the +queries — there is no privilege boundary inside the engine for sanitization +to enforce. If your deployment needs to restrict the SQL surface area exposed +to a `Client`, the supported boundary is the database role the `Virtual Server` +is configured with (read-only etc), or better complete isolation via WASM +backend. + ### How do I set up WebSocket authentication? The [`WebSocketServer`](./how_to/javascript/nodejs_server.md) does not include diff --git a/rust/perspective-js/src/ts/perspective.node.ts b/rust/perspective-js/src/ts/perspective.node.ts index c9bcb03f4a..c7faf1dd1f 100644 --- a/rust/perspective-js/src/ts/perspective.node.ts +++ b/rust/perspective-js/src/ts/perspective.node.ts @@ -193,6 +193,18 @@ function buffer_to_arraybuffer( } } +/** + * A simple Node `http`-based WebSocket adapter that exposes a + * `PerspectiveServer` over the wire. + * + * @remarks + * + * **Security.** `WebSocketServer` is a reference integration with no + * authentication, authorization, origin enforcement, or rate limiting, and + * is not safe to expose to untrusted networks — see + * [`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md) + * for the full threat model. + */ export class WebSocketServer { _server: http.Server | any; // stoppable has no type ... _wss: HttpWebSocketServer; diff --git a/rust/perspective-python/perspective/handlers/aiohttp.py b/rust/perspective-python/perspective/handlers/aiohttp.py index 85dd200e90..3822bfc362 100644 --- a/rust/perspective-python/perspective/handlers/aiohttp.py +++ b/rust/perspective-python/perspective/handlers/aiohttp.py @@ -24,6 +24,14 @@ class PerspectiveAIOHTTPHandler(object): The Perspective client and server will automatically keep the Websocket alive without timing out. + # Security + + `PerspectiveAIOHTTPHandler` is a reference integration with no + authentication, authorization, origin enforcement, or rate limiting, + and is not safe to expose to untrusted networks — see + [`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md) + for the full threat model. + # Examples >>> server = Server() diff --git a/rust/perspective-python/perspective/handlers/starlette.py b/rust/perspective-python/perspective/handlers/starlette.py index e6d437d55a..792eb33a0c 100644 --- a/rust/perspective-python/perspective/handlers/starlette.py +++ b/rust/perspective-python/perspective/handlers/starlette.py @@ -17,6 +17,14 @@ class PerspectiveStarletteHandler(object): """`PerspectiveStarletteHandler` is a drop-in implementation of Perspective. + # Security + + `PerspectiveStarletteHandler` is a reference integration with no + authentication, authorization, origin enforcement, or rate limiting, + and is not safe to expose to untrusted networks — see + [`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md) + for the full threat model. + # Examples >>> server = Server() diff --git a/rust/perspective-python/perspective/handlers/tornado.py b/rust/perspective-python/perspective/handlers/tornado.py index adceb22ef5..7132ab7dd2 100644 --- a/rust/perspective-python/perspective/handlers/tornado.py +++ b/rust/perspective-python/perspective/handlers/tornado.py @@ -122,6 +122,14 @@ class PerspectiveTornadoHandler(WebSocketHandler): to the `tornado.web.Application` constructor, as well as provide the `max_buffer_size` optional arg, for large datasets. + # Security + + `PerspectiveTornadoHandler` is a reference integration with no + authentication, authorization, origin enforcement, or rate limiting, + and is not safe to expose to untrusted networks — see + [`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md) + for the full threat model. + # Arguments - `loop`: An optional `IOLoop` instance to use for scheduling IO calls,