Follow-up from the encryption audit of the meshd DWN records. Splitting this out from the admin-dapp redesign (#204) because it is a protocol + key-delivery change, not a one-line encryption flag, and it belongs in its own focused PR.
Current encryption posture (audit result)
Encrypted (role-audience / recipient-delivered):
Plaintext (deliberate — see #2):
Never on the DWN: WireGuard private keys (derived locally from each node's vault).
The invite → node join flow is fully encrypted already; the items below only affect the owner-adds-own-device flow and the pre-key bootstrapping records.
#1 — Encrypt nodeApproval
nodeApproval is written plaintext (admin.ts, approveMeshdNodeRequest, owner-scoped branch; and Go writeNodeApprovalRefresh) and read plaintext by the joining device via Go FindOwnerNodeApproval (internal/mesh/owner_request.go:159, called from cmd/meshd/main.go:4277). It carries ownerDID, nodeDID, network name/CIDR/endpoint, meshIP, member/node record IDs, label, expiresAt.
The nodeApproval protocol path has no $keyAgreement/$encryption (only $size + $actions), so encryption: true would throw ("does not have encryption configured"). Encrypting it properly requires:
- Add encryption directives to the
nodeApproval path in protocols/wireguard-mesh.json (+ the Go/TS InjectEncryptionDirectives that derive $keyAgreement).
- Role-audience key delivery to the recipient device (same machinery as node records: the DWN-less did:jwk device supplies its role-path key in the request, the owner mints
$encryption/audience + provisions $encryption/delivery).
- Update the Go read path (
FindOwnerNodeApproval) to decrypt instead of reading JSON directly, and update both writers (dashboard + writeNodeApprovalRefresh).
Scope is comparable to the node-record delivery work in #187/#192 — a real protocol+delivery change, not a flag.
#2 — Document the plaintext bootstrapping records
network and network/nodeRequest are plaintext by necessity: a joining device reads them before it has any keys (it needs the network CIDR/endpoint to bootstrap, and the join request is how it announces itself and delivers its own role keys). The HMAC join proof in nodeRequest is a keyed proof, not a secret. This is an intentional decision — capture it as code comments at the write sites (internal/mesh/preauth.go WritePreAuthNodeRequest, and the network write in admin.ts) plus a short note in the protocol docs, so it is not mistaken for a missed encryption.
Follow-up from the encryption audit of the meshd DWN records. Splitting this out from the admin-dapp redesign (#204) because it is a protocol + key-delivery change, not a one-line encryption flag, and it belongs in its own focused PR.
Current encryption posture (audit result)
Encrypted (role-audience / recipient-delivered):
network/preAuthKey— the invite secret.network/member,network/member/node— peer records (meshIP, expiresAt, DIDs, label), encrypted to the reading-role audience with role-audience key delivery (Deliver role-audience keys to endpoint-less did:jwk nodes + fail-fast on provisioning failures #187/did:jwk peer nodes can't decrypt each other: dashboard delivers held-role audience, not reading-role audience #192).network/member/noderecord.Plaintext (deliberate — see #2):
network— name / mesh CIDR / anchor endpoint.network/nodeRequest— the join request metadata + HMAC join proof.nodeApproval— owner-device approval metadata (see Fix CI: handle missing GH_TOKEN for private dependency auth #1).Never on the DWN: WireGuard private keys (derived locally from each node's vault).
The invite → node join flow is fully encrypted already; the items below only affect the owner-adds-own-device flow and the pre-key bootstrapping records.
#1 — Encrypt
nodeApprovalnodeApprovalis written plaintext (admin.ts,approveMeshdNodeRequest, owner-scoped branch; and GowriteNodeApprovalRefresh) and read plaintext by the joining device via GoFindOwnerNodeApproval(internal/mesh/owner_request.go:159, called fromcmd/meshd/main.go:4277). It carries ownerDID, nodeDID, network name/CIDR/endpoint, meshIP, member/node record IDs, label, expiresAt.The
nodeApprovalprotocol path has no$keyAgreement/$encryption(only$size+$actions), soencryption: truewould throw ("does not have encryption configured"). Encrypting it properly requires:nodeApprovalpath inprotocols/wireguard-mesh.json(+ the Go/TSInjectEncryptionDirectivesthat derive$keyAgreement).$encryption/audience+ provisions$encryption/delivery).FindOwnerNodeApproval) to decrypt instead of reading JSON directly, and update both writers (dashboard +writeNodeApprovalRefresh).Scope is comparable to the node-record delivery work in #187/#192 — a real protocol+delivery change, not a flag.
#2 — Document the plaintext bootstrapping records
networkandnetwork/nodeRequestare plaintext by necessity: a joining device reads them before it has any keys (it needs the network CIDR/endpoint to bootstrap, and the join request is how it announces itself and delivers its own role keys). The HMAC join proof innodeRequestis a keyed proof, not a secret. This is an intentional decision — capture it as code comments at the write sites (internal/mesh/preauth.goWritePreAuthNodeRequest, and thenetworkwrite in admin.ts) plus a short note in the protocol docs, so it is not mistaken for a missed encryption.