kryptheon-night 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/trouble.js +56 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kryptheon-night",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Attacks a copy of your Supabase database and tells you in plain English what got in.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
package/trouble.js CHANGED
@@ -314,6 +314,34 @@ const BY_SYSCALL = {
314
314
  ],
315
315
  };
316
316
 
317
+ /**
318
+ * Is this Supabase's direct connection, and if so whose?
319
+ *
320
+ * `db.<ref>.supabase.co` is the direct connection. Supabase stopped giving it
321
+ * an IPv4 address, so today it resolves to an AAAA record and nothing else -
322
+ * and on a machine with no IPv6 the name does not resolve at all. The failure
323
+ * that produces is ENOTFOUND: not "unreachable", which would at least hint at
324
+ * the network, but "no such name", which reads as a typo.
325
+ *
326
+ * Measured on a real project: A = ENODATA, AAAA = 2406:da1c:..., and no
327
+ * global IPv6 on the machine. The scan said "there is a typo in the address,
328
+ * or this computer is not online... if your project was paused or deleted",
329
+ * and all three of those were false. That sends somebody to reset a password
330
+ * or rebuild a project that was never broken.
331
+ *
332
+ * The ref is handed back so the pooler string can be spelled out with their
333
+ * own project in it rather than as a shape to fill in.
334
+ */
335
+ function supabaseDirectRef(raw) {
336
+ try {
337
+ const host = new URL(String(raw).trim().replace(/^["']|["']$/g, '')).hostname;
338
+ const match = /^db\.([a-z0-9]+)\.supabase\.(co|com)$/i.exec(host);
339
+ return match ? match[1] : null;
340
+ } catch (err) {
341
+ return null;
342
+ }
343
+ }
344
+
317
345
  /**
318
346
  * One failure, in plain English.
319
347
  *
@@ -326,6 +354,33 @@ const BY_SYSCALL = {
326
354
  function explain(err, raw) {
327
355
  const code = err && err.code ? String(err.code) : '';
328
356
 
357
+ // Checked before the general ENOTFOUND, because for this one address the
358
+ // general answer is wrong in every one of its three guesses.
359
+ const ref = code === 'ENOTFOUND' ? supabaseDirectRef(raw) : null;
360
+ if (ref) {
361
+ return [
362
+ 'I could not find that server - and I think I know why.',
363
+ '',
364
+ 'That address is Supabase’s direct connection, and it now answers only',
365
+ 'over IPv6. Plenty of home and office networks have no IPv6 at all, and on',
366
+ 'those the name does not resolve to anything - which is what just',
367
+ 'happened here.',
368
+ '',
369
+ 'There is nothing wrong with your project or your password. Use the',
370
+ 'session pooler instead, which answers over the ordinary internet:',
371
+ '',
372
+ ' Supabase dashboard -> Project Settings -> Database ->',
373
+ ' Connection string -> URI -> and pick "Session pooler".',
374
+ '',
375
+ 'It looks like this, with your password in the middle:',
376
+ '',
377
+ ' postgresql://postgres.' + ref + ':PASSWORD@aws-0-REGION.pooler.supabase.com:5432/postgres',
378
+ '',
379
+ 'Take the port 5432 one. The other pooler, on 6543, hands every statement',
380
+ 'a different connection and these attacks have to stay on one.',
381
+ ];
382
+ }
383
+
329
384
  if (BY_CODE[code]) return BY_CODE[code].slice();
330
385
  if (BY_SYSCALL[code]) return BY_SYSCALL[code].slice();
331
386
 
@@ -372,6 +427,7 @@ module.exports = {
372
427
  readConnectionString: readConnectionString,
373
428
  poolerWarning: poolerWarning,
374
429
  explain: explain,
430
+ supabaseDirectRef: supabaseDirectRef,
375
431
  withoutSecret: withoutSecret,
376
432
  PLACEHOLDERS: PLACEHOLDERS,
377
433
  };