From 9c7d26e0312b7e133801fe9a512e31908c2b29da Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Thu, 29 Oct 2026 16:40:23 -0700 Subject: apps: require a hex NWC pubkey and return only public fields for payment users The app pubkey names both the app record and its payment list, so a non-key value could point a connection at another account's payments. Reject anything but a 64-character hex key on create and lookup, and trim the user attached to each payment to the public profile fields. Co-Authored-By: Claude Opus 5.5 --- routes/users.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/routes/users.ts b/routes/users.ts index 8d902987..e8fdddd5 100644 --- a/routes/users.ts +++ b/routes/users.ts @@ -1264,6 +1264,8 @@ export default { async app(req, res) { const { pubkey } = req.params; const { user } = req; + if (!/^[0-9a-f]{64}$/.test(pubkey)) + return res.code(404).send({ error: "connection not found" }); const app = await g(`app:${pubkey}`); // Missing records must 404, not crash into a 500: clients (Damus one-click // setup) probe this endpoint and treat 404 as "not configured yet, create @@ -1278,7 +1280,7 @@ export default { const payments = await Promise.all( pids.map(async (pid) => { const p = await gf(`payment:${pid}`); - if (p) p.user = await g(`user:${p.uid}`); + if (p) p.user = pick((await g(`user:${p.uid}`)) || {}, fields); return p; }), ); @@ -1341,6 +1343,11 @@ export default { const uid = user.id; if (secret) pubkey = getPublicKey(hexToBytes(secret)); + // The pubkey names `app:` and `:payments`, so it must be + // a real NWC key: a user id here would hand the caller that user's + // payment list through GET /app. + if (!/^[0-9a-f]{64}$/.test(pubkey)) fail("Invalid pubkey"); + // App records live under `app:`. Looking up the bare pubkey made // this ownership check ineffective for normal NWC keys and allowed an // authenticated user to overwrite another connection's configuration. -- cgit v1.2.3