Skip to content

Commit 048a0cf

Browse files
authored
Merge pull request #3173 from perspective-dev/thread-model
Update security docs
2 parents d282b71 + f0dffe6 commit 048a0cf

6 files changed

Lines changed: 121 additions & 0 deletions

File tree

SECURITY.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,56 @@ escalate to the OpenJS Foundation CNA at `security@lists.openjsf.org`.
2323

2424
If the project acknowledges your report but does not provide any further
2525
response or engagement within 14 days, escalation is also appropriate.
26+
27+
## Threat Model
28+
29+
The Perspective WebSocket `Server` (the Python `tornado.py`/`aiohttp.py`/
30+
`starlette.py` adapters and the Node `WebSocketServer`) is not a security
31+
boundary against its `Client`. Any `Client` that can send messages to a
32+
`Server` is treated as the author of the queries it submits, and is permitted
33+
to create or delete `Table`/`View` resources, author arbitrary
34+
[expression columns](./docs/md/explanation/view/config/expressions.md), and —
35+
for `Virtual Server` backends (DuckDB, ClickHouse, Polars, custom
36+
`VirtualServerHandler`) — author SQL fragments executed under the configured
37+
database role. The `Virtual Server` SQL builder does not parameterize or
38+
validate client-supplied identifiers, expressions, or operators, because
39+
there is no privilege boundary inside the engine for it to enforce.
40+
41+
The bundled WebSocket adapters above are reference integrations: they do not
42+
implement authentication, authorization, CSRF protection, rate limiting, or
43+
origin enforcement, and are not intended to be exposed directly to untrusted
44+
networks. Production deployments must place an authenticating reverse proxy,
45+
application-framework middleware, or API gateway between the network and the
46+
`Server`.
47+
48+
### In-browser WASM deployments are not affected
49+
50+
This applies only when the `Server` runs in a separate process reached
51+
over a network transport (WebSocket). In-browser deployments — including
52+
`perspective` running entirely in a Web Worker, the
53+
[`perspective-server` WASM build](./docs/md/explanation/architecture.md),
54+
[`duckdb-wasm`](./docs/md/how_to/javascript/virtual_server/duckdb.md),
55+
and any other `Virtual Server` whose backend executes inside the browser
56+
tab — do not have this concern. The `Client` and `Server` share a single
57+
security context (the browser tab, under the same-origin policy of the
58+
embedding page), there is no network transport for a third-party principal
59+
to reach, and the only principal who can submit queries is the same user who
60+
loaded the page. SQL or expression "injection" by that user against a backend
61+
running inside their own tab is not a privilege escalation.
62+
63+
### In scope
64+
65+
The following remain in scope for security reports:
66+
67+
- Memory-safety bugs in the C++ engine, Rust crates, or WASM module.
68+
- Bugs in the `<perspective-viewer>` Shadow DOM, CSS, or sanitization paths
69+
that allow injected markup or styles to escape the component or affect
70+
the embedding page.
71+
- Crashes, hangs, panics, or denial-of-service in the engine reachable from
72+
well-formed protobuf messages.
73+
- Breaches of the trust model above — for example, a `Client` causing effects
74+
on a different `Client`'s `Server` state in a configuration where those
75+
`Client`s share a `Server` but are intended to be isolated, or an
76+
expression column reaching state outside the `Server` it was authored
77+
against.
78+
- Vulnerabilities in the published artifacts themselves (supply-chain).

docs/md/FAQ.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,38 @@ of each mode.
328328
<!-- _Related:
329329
[#2916](https://github.com/perspective-dev/perspective/discussions/2916)_ -->
330330

331+
### Is the WebSocket Perspective `Server` safe to expose to untrusted clients?
332+
333+
No. The WebSocket `Server` is not a security boundary. Every connected `Client`
334+
is treated as the author of the queries it submits, and is permitted to create
335+
and delete `Table`/`View` resources, author arbitrary
336+
[expression columns](./explanation/view/config/expressions.md), and — for
337+
[Virtual Server](./explanation/virtual_servers.md) backends like DuckDB or
338+
ClickHouse — author SQL fragments executed under the configured database
339+
role. The bundled WebSocket adapters
340+
(`tornado.py`/`aiohttp.py`/`starlette.py`/`WebSocketServer`) are reference
341+
integrations and do not authenticate, authorize, or enforce origin policy.
342+
343+
WebSocket Deployments that need per-user isolation must put an authenticating
344+
proxy in front of the `Server`, run a least-privileged database role for any
345+
`Virtual Server` backend, and/or isolate users into separate `Server`
346+
instances. See [`SECURITY.md`](../../SECURITY.md) for the full threat model
347+
and deployment guidance.
348+
349+
Obviously, none of this applies to WASM DBs like Perspective and DuckDB.
350+
351+
### Does Perspective sanitize SQL `Virtual Server`s?
352+
353+
No, by design. [Virtual Server](./explanation/virtual_servers.md) backends
354+
interpolate client-supplied `view_id`, `table_id`, `column_name`, expression
355+
strings, and filter operators directly into SQL templates without
356+
parameterization or whitelist validation. The `Client` is the author of the
357+
queries — there is no privilege boundary inside the engine for sanitization
358+
to enforce. If your deployment needs to restrict the SQL surface area exposed
359+
to a `Client`, the supported boundary is the database role the `Virtual Server`
360+
is configured with (read-only etc), or better complete isolation via WASM
361+
backend.
362+
331363
### How do I set up WebSocket authentication?
332364

333365
The [`WebSocketServer`](./how_to/javascript/nodejs_server.md) does not include

rust/perspective-js/src/ts/perspective.node.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ function buffer_to_arraybuffer(
193193
}
194194
}
195195

196+
/**
197+
* A simple Node `http`-based WebSocket adapter that exposes a
198+
* `PerspectiveServer` over the wire.
199+
*
200+
* @remarks
201+
*
202+
* **Security.** `WebSocketServer` is a reference integration with no
203+
* authentication, authorization, origin enforcement, or rate limiting, and
204+
* is not safe to expose to untrusted networks — see
205+
* [`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md)
206+
* for the full threat model.
207+
*/
196208
export class WebSocketServer {
197209
_server: http.Server | any; // stoppable has no type ...
198210
_wss: HttpWebSocketServer;

rust/perspective-python/perspective/handlers/aiohttp.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ class PerspectiveAIOHTTPHandler(object):
2424
The Perspective client and server will automatically keep the Websocket
2525
alive without timing out.
2626
27+
# Security
28+
29+
`PerspectiveAIOHTTPHandler` is a reference integration with no
30+
authentication, authorization, origin enforcement, or rate limiting,
31+
and is not safe to expose to untrusted networks — see
32+
[`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md)
33+
for the full threat model.
34+
2735
# Examples
2836
2937
>>> server = Server()

rust/perspective-python/perspective/handlers/starlette.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@
1717
class PerspectiveStarletteHandler(object):
1818
"""`PerspectiveStarletteHandler` is a drop-in implementation of Perspective.
1919
20+
# Security
21+
22+
`PerspectiveStarletteHandler` is a reference integration with no
23+
authentication, authorization, origin enforcement, or rate limiting,
24+
and is not safe to expose to untrusted networks — see
25+
[`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md)
26+
for the full threat model.
27+
2028
# Examples
2129
2230
>>> server = Server()

rust/perspective-python/perspective/handlers/tornado.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,14 @@ class PerspectiveTornadoHandler(WebSocketHandler):
122122
to the `tornado.web.Application` constructor, as well as provide the
123123
`max_buffer_size` optional arg, for large datasets.
124124
125+
# Security
126+
127+
`PerspectiveTornadoHandler` is a reference integration with no
128+
authentication, authorization, origin enforcement, or rate limiting,
129+
and is not safe to expose to untrusted networks — see
130+
[`SECURITY.md`](https://github.com/perspective-dev/perspective/blob/master/SECURITY.md)
131+
for the full threat model.
132+
125133
# Arguments
126134
127135
- `loop`: An optional `IOLoop` instance to use for scheduling IO calls,

0 commit comments

Comments
 (0)