Stop falling back to a committed edge signing secret

`EDGE_JWT_SECRET` was read as `process.env.EDGE_JWT_SECRET ?? "dev-edge-secret-change-me"`
in two places. Anything holding that string can mint an edge JWT for any
`farm_id` it likes and open `/ws/edge/v1` as that farm's gateway: enough to
feed a farm false telemetry and to receive the commands meant for its
printers. It is in a git repository, so it is not a secret.

The `??` made it worse rather than better. It only catches null and
undefined, and the committed `.env.example` ships `EDGE_JWT_SECRET=` *empty* -
so a deployment that copied the template and filled in everything else signed
its edge JWTs with the empty string and never reached the fallback at all.
That is exactly the case the fallback existed to cover.

Both reads now come from `src/auth/edge_secret.ts`, which throws at module
load if the variable is unset or empty. Same shape as `verifier.ts` and for
the same reason: a backend that boots and then fails every gateway handshake
looks like a network problem somewhere else. There is no development default
on purpose - a secret that works out of the box is a secret that reaches
production, and `openssl rand -hex 32` is a one-time cost.

Tests: the empty-string case, since that is the one the template produced,
and a grep for both shapes that reintroduce the hole - a literal passed to
`jwt()`, and a defaulted read of the variable - plus an assertion that the
variable is read in exactly one place. 60 pass.
This commit is contained in:
2026-10-15 21:43:16 +00:00
parent b5382ab47c
commit 1db96caa91
5 changed files with 81 additions and 3 deletions
+2 -1
View File
@@ -10,6 +10,7 @@ import {
import { db } from "../../db";
import { printers } from "../../db/schema";
import { enqueueFrameIngestionBatch } from "../../queue";
import { edgeJwtSecret } from "../../auth/edge_secret";
/**
* Edge-gateway uplink. A continuum-proxy gateway connects here with a
@@ -217,7 +218,7 @@ export function isGatewayOnline(farmId: string): boolean {
}
export const edgeWsRoute = new Elysia()
.use(jwt({ name: "edgeJwt", secret: process.env.EDGE_JWT_SECRET ?? "dev-edge-secret-change-me" }))
.use(jwt({ name: "edgeJwt", secret: edgeJwtSecret }))
.ws("/ws/edge/v1", {
body: EdgeClientMessageSchema,