← dashboard

websocket-switch

model: opus-4-8 python static analysis off 0 PR(s) seen
workspaceninjarmm jira projectNJ review targetsdevelop · main · master · release/* thresholdsseverity ≥ minor · confidence ≥ medium · cap 12 max diff0.4 MB pollevery 300s

What this is

WebSocket pairing relay of the ninja-remote system: two clients join via HMAC-signed URLs, the service pairs them and forwards frames verbatim. Python (Twisted/Autobahn on PyPy), AWS-hosted.

Project primer

Durable orientation injected into every review as .ai-review/project.md.

websocket-switch — WebSocket pairing relay (review primer)

A small internet-facing Python (Twisted + Autobahn, runs on PyPy) service in the
ninja-remote system: two WebSocket clients join a session (a "left" and a "right" peer
via signed join URLs), the service pairs them and relays frames **verbatim in both
directions** — no persistence, no payload inspection. Confidentiality lives in the peers.

Architecture (durable map)

registries of pending (one peer arrived) and active (both arrived) pairs, all in
process memory — the service is stateful per session and scales only behind
session-affine load balancing.

arrive before the pair completes are buffered and flushed on pairing.

(session id + nonce + hex timestamp) with a shared gateway secret (env / AWS SSM);
the timestamp gives the URL an expiry. Pending pairs expire after a short timeout.

What matters most when reviewing here (durable risk areas)

path parser and signature verification before anything else. Scrutinize: exactly what
material is signed, constant-time comparison (hmac.compare_digest), timestamp expiry
actually rejecting, and path-parsing alternates (leading slash, case, encoding) that
could bypass or confuse verification.

calls, DNS, file I/O, or CPU-heavy work in a callback stalls the whole relay.

on EVERY exit path (clean close, error, timeout, half-open peer); the close of one leg
must close/notify the other; the pre-pairing backlog flush interleaving with a peer
closing mid-flush is the classic race. Leaked pairs/protocol objects = memory growth
on a long-running process.

checks; a slow reader grows the TCP/userspace buffers. Changes that add buffering or
size limits must not break large legitimate transfers; changes that add unbounded
buffering are findings.

never interleave two messages on one connection, and binary/text + multi-frame
semantics must survive any refactor — peers depend on exact framing.

Security-sensitive zones

Signature verification + its path parsing; gateway-secret handling (never in logs or
error text); anything that would make the relay inspect, transform, or log relayed
payload (it must stay a dumb pipe); DoS surfaces (per-connection buffers, pending-pair
registry growth under join spam).

Compatibility surfaces (contracts that must not silently break)

URLs are minted by the Ninja backend/gateway, so both sides must agree byte-for-byte
on the signed material.

Conventions & gotchas

a logging-framework migration as a finding, and never log relayed payload or secrets.

"unauthorized" — tightening is welcome, but a swallowed error that turns a reject into
an accept is a security finding.

new dependencies must have PyPy-compatible wheels (checked against the Dockerfile).

tests warrant at most a minor test finding, not a blocker.

_Orientation only — verify current specifics (names, paths, constants) in the checkout._

Ecosystem map

Shared cross-project relationships, injected as .ai-review/ecosystem.md.

NinjaRemote / NinjaOne project ecosystem (relationship map)

How the monitored repos relate — so a change in one repo is reviewed with its blast
radius in mind (a change upstream of ncpeer can break ncpeer even if it looks local).
Orientation only; verify specifics in code.

NinjaRemote product

speak the NCRP wire protocol (protocol/format changes must stay compatible across
client and server versions).

Cloud-side relays of the ninja-remote system (AWS-hosted, Python/Go)

URLs, it pairs them and forwards frames verbatim. Signed join URLs are minted by the
Ninja backend — the signature scheme is a backend⇄relay contract, and the frame
semantics are a peer⇄peer contract.

a signed upload leg streams to a signed download leg through memory (never stored).
Two parallel implementations (Python ftsv2/ + Go fts_go/) must stay behaviorally
identical; the URL/signature scheme is shared-family with websocket-switch.
NinjaRemote file-transfer features ride on these relays when peers can't go direct.

Pulled INTO ncpeer (and sometimes nmsnj) — upstream of the products

or behaviour change here affects both consumers.

build against**. A dependency bump changes what those products link (security/ABI/build).

through the ncpeer_vcpkg overlay.

Largely independent

shares nccommon and ncpeer_vcpkg with ncpeer.

part of the remote-desktop family). Runs as a child process of the NinjaRMM agent (the
parent owns install/upgrade/policy/launch); NinjaFlow owns the CLI contract, config schema,
credential precedence, DB schema/migrations, and exit-code meanings the parent consumes
those are cross-process contracts. Submits telemetry to the Ninja Backend. Shares no code with
ncpeer/nmsnj; adjacent to nmsnj only in domain (network), not in implementation.

not part of any product's build. It consumes the SBOMs the other products' pipelines emit
(Ninja-Remote/ncpeer, NMS/nmsnj, NinjaFlow all appear in its scope maps), scans them for CVEs
and files Jira issues — a downstream *security-scanning* relationship, not a code dependency.
Shares no code with the others; has its own DB/schema and a toolbox submodule.

authentication component: its pam_ninja.so gates SSH reverse-tunnel access into
Ninja infrastructure with a time-based OTP. Shares no code with the other repos; its
blast radius is operational (who can SSH-tunnel into hosts), not code-level. The client OTP
generators and the module must agree on the OTP scheme + shared secret.

Review implications

these are *upstream* of ncpeer (and nccommon/vcpkg also of nmsnj) — weigh the consumer
side: API/ABI compatibility, build impact, and cross-platform reach.

contract.

Also in every review's context

The reviewer instructions CLAUDE.md (rules, house C++ style, output schema); the PR/ticket block pr.md (title, description, linked Jira + acceptance criteria, commits, changed files); the full diff.patch.