develop · main · master · release/*
thresholdsseverity ≥ minor · confidence ≥ medium · cap 12
max diff0.4 MB
pollevery 300s
Paired file-transfer relay of the ninja-remote system: HMAC-signed upload and download legs stream a file through memory, never persisted. Python (ftsv2/aiohttp, deployed) + parallel Go implementation (fts_go).
An internet-facing service in the ninja-remote system that relays a file between an
uploader and a downloader leg paired by transfer id — streamed chunk-by-chunk
through memory, never persisted. Authorization per leg = HMAC-SHA256-signed URLs
(nonce + hex timestamp + shared gateway secret from env/AWS SSM), same scheme family as
websocket-switch.
Two implementations live side by side in this repo — review each diff with the
matching language skill (per FILE, not per repo):
ftsv2/ — Python 3.12 / aiohttp, asyncio (the deployed service; python-review).fts_go/ — Go / net/http parallel implementation (go-review).fts/ — legacy Python; changes here are suspect — ask (in the summary) whether thelive implementations need the same fix.
download endpoint (streamed GET) keyed by transfer id + signed leg URL, plus /status.
machine (idle → ready → in-progress → completed/failed) and a bounded queue/channel
the uploader feeds and the downloader drains, chunked (~2 MB).
reaps completed/failed/stale pairs.
response headers. Deployment: Docker; secrets via AWS SSM.
signed material coverage, constant-time HMAC comparison, timestamp expiry actually
rejecting, and path-normalization alternates that could bypass the check. The Python
and Go validators must stay behaviorally identical — a divergence is itself a finding.
moment: pair creation races on the same transfer id, a downloader waiting for an
uploader that never comes (bounded waits, not forever), cleanup reaping a pair the
other leg still uses, entries removed on EVERY exit path. In Go this is also channel
discipline: who closes, double-close panics, goroutines blocked on a channel nobody
drains after a disconnect. In Python: state shared across await points.
full-file buffering in memory, multipart size limits actually enforced, one slow or
dead leg must not pin memory or stall the loop (asyncio) / leak goroutines (Go).
Content-Disposition — header injection / traversal characters must be sanitized;
filesize claims must not be trusted for allocation.
openapi.yaml must agree on paths, params, statuscodes, and error shapes — clients treat them as one service.
Signature validation in both languages; gateway-secret handling (never logged);
filename/header echo; anything that would persist or log transferred content (it must
remain a pass-through relay); unbounded buffering.
Ninja backend, and agents/NinjaRemote clients on OLD versions keep using the current
shape.
(filename/size) that clients parse.
ftsv2 and fts_go behavior, and the OpenAPI spec staying truthful.polling loops with capped retries — keep waits bounded, don't introduce infinite ones.
logrus for logging; config from YAML + SSM lookups.
don't spend findings on it.
at most a minor test finding.
_Orientation only — verify current specifics (names, paths, constants) in the checkout._
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.
speak the NCRP wire protocol (protocol/format changes must stay compatible across
client and server versions).
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.
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.
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.
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.