@blinklabs/dingo 0.85.0 → 0.87.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.
@@ -1,8 +1,8 @@
1
1
  {
2
- "dingo-v0.85.0-darwin-amd64.tar.gz": "270c3751a34f24e2a8cd5fb93112dce6af60ef02c12e4064f4f9c2cfa6151cfe",
3
- "dingo-v0.85.0-darwin-arm64.tar.gz": "b77938960005799c108464e62a8384c99fc9d7df74f1ecf63a61660585759c1c",
4
- "dingo-v0.85.0-freebsd-amd64.tar.gz": "1cafd0d142b0c95236814f57ecc58cfca75eac8242121ac55bc7288ec0390db3",
5
- "dingo-v0.85.0-freebsd-arm64.tar.gz": "2ea82ef8ac5945417726d5c9c9e068dbafb7361444147bba327ceca455d54bd2",
6
- "dingo-v0.85.0-linux-amd64.tar.gz": "e3b14ae3d05e4b8dd3eda1f869f8fabdf2214f61a1fc185df54f4e7272c7f34d",
7
- "dingo-v0.85.0-linux-arm64.tar.gz": "b7d45e45b7a2809f2e4d671a978c1697b1c17e53e785166b6875867c81714657"
2
+ "dingo-v0.87.1-darwin-amd64.tar.gz": "3f2be9c9d7c6b39b2d21dc6e0a790377abad16a26ad8ee90df4ab3f01bf99489",
3
+ "dingo-v0.87.1-darwin-arm64.tar.gz": "bfd4f467cdd3b76a3bb4b68cb141a8515821aa73dea5abdb395544a5ec170454",
4
+ "dingo-v0.87.1-freebsd-amd64.tar.gz": "a07ca436f12c27cb8ea8d02a454f5ec7f9a21da02fde9f1b6c1426f33dbf8f1a",
5
+ "dingo-v0.87.1-freebsd-arm64.tar.gz": "4df1766807ff002cda6be85922706ef26197d884602c3311683ae477430b32e9",
6
+ "dingo-v0.87.1-linux-amd64.tar.gz": "7d6ecaefa1aee4de0713fb31af4cb2566126c8c865e26c1a6dbacf672ea73a2e",
7
+ "dingo-v0.87.1-linux-arm64.tar.gz": "f703da349302257e441478d7b9290e85e35a6853ff3b65cdb44727845d573e2a"
8
8
  }
package/npm/install.js CHANGED
@@ -13,9 +13,11 @@
13
13
  'use strict';
14
14
 
15
15
  const crypto = require('node:crypto');
16
+ const dns = require('node:dns');
16
17
  const fs = require('node:fs');
17
18
  const fsp = require('node:fs/promises');
18
19
  const https = require('node:https');
20
+ const net = require('node:net');
19
21
  const os = require('node:os');
20
22
  const path = require('node:path');
21
23
  const { spawn } = require('node:child_process');
@@ -25,6 +27,97 @@ const { pipeline } = require('node:stream/promises');
25
27
  const MAX_ARCHIVE_BYTES = 256 * 1024 * 1024;
26
28
  const MAX_BINARY_BYTES = 512 * 1024 * 1024;
27
29
  const RETRY_DELAYS_MS = [100, 500];
30
+ const RELEASE_HOSTS = new Set([
31
+ 'github.com',
32
+ 'github-releases.githubusercontent.com',
33
+ 'objects.githubusercontent.com',
34
+ 'release-assets.githubusercontent.com',
35
+ ]);
36
+
37
+ // The two families are kept in separate lists because a Node BlockList matches
38
+ // every IPv4 address against an IPv6 rule covering ::ffff:0:0/96, which would
39
+ // reject all IPv4 answers. IPv4-mapped IPv6 answers are unmapped instead and
40
+ // checked against the IPv4 rules.
41
+ const blockedIPv4 = new net.BlockList();
42
+ for (const [network, prefix] of [
43
+ ['0.0.0.0', 8], ['10.0.0.0', 8], ['100.64.0.0', 10], ['127.0.0.0', 8],
44
+ ['169.254.0.0', 16], ['172.16.0.0', 12], ['192.0.0.0', 24], ['192.0.2.0', 24],
45
+ ['192.88.99.0', 24], ['192.168.0.0', 16], ['198.18.0.0', 15],
46
+ ['198.51.100.0', 24], ['203.0.113.0', 24], ['224.0.0.0', 4], ['240.0.0.0', 4],
47
+ ]) blockedIPv4.addSubnet(network, prefix, 'ipv4');
48
+ const blockedIPv6 = new net.BlockList();
49
+ for (const [network, prefix] of [
50
+ ['::', 128], ['::1', 128], ['64:ff9b::', 96], ['64:ff9b:1::', 48],
51
+ ['100::', 64], ['2001:2::', 48], ['2001:db8::', 32], ['2001:10::', 28],
52
+ ['fc00::', 7], ['fe80::', 10], ['ff00::', 8],
53
+ ]) blockedIPv6.addSubnet(network, prefix, 'ipv6');
54
+
55
+ function mappedIPv4(address) {
56
+ let host;
57
+ try {
58
+ // URL serialization canonicalizes every IPv6 spelling to compressed hex.
59
+ host = new URL(`http://[${address}]`).hostname;
60
+ } catch {
61
+ return null;
62
+ }
63
+ const match = /^\[::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})\]$/.exec(host);
64
+ if (!match) return null;
65
+ const high = parseInt(match[1], 16);
66
+ const low = parseInt(match[2], 16);
67
+ return `${high >> 8}.${high & 255}.${low >> 8}.${low & 255}`;
68
+ }
69
+
70
+ function disallowedAddress(address) {
71
+ try {
72
+ if (net.isIPv4(address)) return blockedIPv4.check(address, 'ipv4');
73
+ if (!net.isIPv6(address)) return true;
74
+ const ipv4 = mappedIPv4(address);
75
+ if (ipv4) return blockedIPv4.check(ipv4, 'ipv4');
76
+ return blockedIPv6.check(address, 'ipv6');
77
+ } catch {
78
+ return true;
79
+ }
80
+ }
81
+
82
+ function validateReleaseURL(parsed) {
83
+ if (parsed.protocol !== 'https:') {
84
+ throw new Error('Dingo release downloads require HTTPS');
85
+ }
86
+ if (parsed.username || parsed.password || (parsed.port && parsed.port !== '443')) {
87
+ throw new Error('Dingo release download URL is not allowed');
88
+ }
89
+ if (!RELEASE_HOSTS.has(parsed.hostname.toLowerCase())) {
90
+ throw new Error('Dingo release download host is not allowed');
91
+ }
92
+ }
93
+
94
+ function publicLookup(hostname, options, callback) {
95
+ const lookupOptions = typeof options === 'object' ? options : {};
96
+ dns.lookup(hostname, {
97
+ family: lookupOptions.family,
98
+ hints: lookupOptions.hints,
99
+ all: true,
100
+ verbatim: true,
101
+ }, (error, addresses) => {
102
+ if (error) {
103
+ callback(error);
104
+ return;
105
+ }
106
+ if (addresses.length === 0 || addresses.some(({ address }) => disallowedAddress(address))) {
107
+ callback(new Error('Dingo release host resolved to a disallowed address'));
108
+ return;
109
+ }
110
+ if (lookupOptions.all) {
111
+ callback(null, addresses);
112
+ return;
113
+ }
114
+ callback(null, addresses[0].address, addresses[0].family);
115
+ });
116
+ }
117
+
118
+ function secureGet(url, callback) {
119
+ return https.get(url, { lookup: publicLookup }, callback);
120
+ }
28
121
 
29
122
  function releaseTarget(version, platform = process.platform, arch = process.arch) {
30
123
  const targets = {
@@ -64,9 +157,12 @@ function byteLimit(maxBytes, description) {
64
157
 
65
158
  function downloadOnce(url, destination, get, redirects, maxBytes) {
66
159
  return new Promise((resolve, reject) => {
67
- const parsed = new URL(url);
68
- if (parsed.protocol !== 'https:') {
69
- reject(new Error('Dingo release downloads require HTTPS'));
160
+ let parsed;
161
+ try {
162
+ parsed = new URL(url);
163
+ validateReleaseURL(parsed);
164
+ } catch (error) {
165
+ reject(error);
70
166
  return;
71
167
  }
72
168
  const request = get(parsed, (response) => {
@@ -109,7 +205,7 @@ function downloadOnce(url, destination, get, redirects, maxBytes) {
109
205
  });
110
206
  }
111
207
 
112
- async function download(url, destination, get = https.get, options = {}) {
208
+ async function download(url, destination, get = secureGet, options = {}) {
113
209
  const delays = options.retryDelays ?? RETRY_DELAYS_MS;
114
210
  const wait = options.wait ?? ((milliseconds) => new Promise((resolve) => {
115
211
  setTimeout(resolve, milliseconds);
@@ -208,4 +304,4 @@ async function install(packageRoot, options = {}) {
208
304
  }
209
305
  }
210
306
 
211
- module.exports = { binaryPath, download, install, releaseURL };
307
+ module.exports = { binaryPath, download, install, publicLookup, releaseURL };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blinklabs/dingo",
3
- "version": "0.85.0",
3
+ "version": "0.87.1",
4
4
  "description": "Dingo is a Cardano blockchain data node",
5
5
  "bin": {
6
6
  "dingo": "npm/bin/dingo.js"