@kernhq/module-quire 0.15.0 → 0.16.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.
- package/dist/server/export/markdown.d.ts.map +1 -1
- package/dist/server/export/markdown.js +44 -0
- package/dist/server/export/markdown.js.map +1 -1
- package/dist/server/render.d.ts +33 -2
- package/dist/server/render.d.ts.map +1 -1
- package/dist/server/render.js +173 -0
- package/dist/server/render.js.map +1 -1
- package/dist/server/services/objects.d.ts +61 -0
- package/dist/server/services/objects.d.ts.map +1 -0
- package/dist/server/services/objects.js +110 -0
- package/dist/server/services/objects.js.map +1 -0
- package/dist/server/services/unfurl.d.ts +154 -0
- package/dist/server/services/unfurl.d.ts.map +1 -0
- package/dist/server/services/unfurl.js +593 -0
- package/dist/server/services/unfurl.js.map +1 -0
- package/package.json +5 -8
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The server fetching a URL somebody typed into a page.
|
|
3
|
+
*
|
|
4
|
+
* This is the most dangerous thing in the module, and it is worth saying why before anything else.
|
|
5
|
+
* Core listens on :4000 and Postgres on :5432 **on the same host as this process**, and a cloud
|
|
6
|
+
* instance sits behind a metadata service at 169.254.169.254 that hands out credentials to anyone
|
|
7
|
+
* who asks. A fetcher that will retrieve any address a user supplies is a way to make Kern read all
|
|
8
|
+
* of that on the attacker's behalf and hand back what it found — which is what "unfurl this link"
|
|
9
|
+
* would be, written the obvious way.
|
|
10
|
+
*
|
|
11
|
+
* So the defences come first and the feature second. In order, and every one of them is load-bearing:
|
|
12
|
+
*
|
|
13
|
+
* 1. **Scheme.** `http:` and `https:` only. `file:`, `gopher:` and `data:` are not addresses on
|
|
14
|
+
* the web, and a redirect to one is a redirect out of this function's assumptions.
|
|
15
|
+
* 2. **No credentials in the URL.** `http://user:pass@host/` sends those to the host, and a URL
|
|
16
|
+
* with them in is either an accident or a way to reach something that asked for them.
|
|
17
|
+
* 3. **Resolve, then judge.** The hostname is resolved and *every* address it answers with has to
|
|
18
|
+
* be public. `evil.example` resolving to 127.0.0.1 is the ordinary way past a name-based check,
|
|
19
|
+
* and a name-based check is the only kind that can run before DNS.
|
|
20
|
+
* 4. **Then the allow-list**, on the hostname, *after* the address check rather than instead of
|
|
21
|
+
* it. Order matters: an allow-list entry must never be a way *past* the address rule, so a host
|
|
22
|
+
* an administrator allowed which today resolves into private space is still refused.
|
|
23
|
+
* 5. **The connection is pinned to the addresses that were checked.** The guard is installed as
|
|
24
|
+
* the socket's own `lookup`, so there is no second resolution between the check and the
|
|
25
|
+
* connection for a DNS rebind to win — which is why this uses `node:http` rather than `fetch`,
|
|
26
|
+
* whose dispatcher cannot be given one without pulling undici in.
|
|
27
|
+
* 6. **Every redirect is a new request and gets the whole check again.** The first response is
|
|
28
|
+
* public and the second one is the loopback interface: that is the entire trick, and it is why
|
|
29
|
+
* redirects are followed here by hand rather than by the HTTP client.
|
|
30
|
+
* 7. **A ceiling on the body, the time and the number of hops**, because a fetcher with none is a
|
|
31
|
+
* way to hold a worker open on a socket that trickles bytes for ever.
|
|
32
|
+
*
|
|
33
|
+
* **The allow-list is empty by default, and every unfurl is refused until an operator sets one.**
|
|
34
|
+
* That is deliberate rather than unfinished. Fetching arbitrary addresses on a user's say-so is a
|
|
35
|
+
* capability an instance's operator should have to grant on purpose, and a self-hosted Kern sitting
|
|
36
|
+
* inside somebody's private network is exactly the deployment where granting it by default would be
|
|
37
|
+
* worst. `QUIRE_EMBED_HOSTS` is the switch; the procedure says so when it refuses, so an
|
|
38
|
+
* administrator reads a sentence about a setting rather than "something went wrong".
|
|
39
|
+
*
|
|
40
|
+
* What this file is **not** for: Kern's own objects. A page, an issue or a channel is named by
|
|
41
|
+
* reference and resolved through `objectTypes`/`resolvers` against whoever is reading — see
|
|
42
|
+
* `objects.ts`. Unfurling our own URL would point this fetcher at core, and would freeze a
|
|
43
|
+
* permission question into a stored answer.
|
|
44
|
+
*/
|
|
45
|
+
import { lookup as dnsLookup } from 'node:dns';
|
|
46
|
+
import { request as httpRequest } from 'node:http';
|
|
47
|
+
import { request as httpsRequest } from 'node:https';
|
|
48
|
+
import { isIP } from 'node:net';
|
|
49
|
+
import { KernError } from '@kernhq/kernel';
|
|
50
|
+
import { PAGE_EMBED_MAX_DESCRIPTION, PAGE_EMBED_MAX_SITE, PAGE_EMBED_MAX_TITLE, PAGE_EMBED_MAX_URL, } from '@kernhq/ui/editor/page-doc';
|
|
51
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
52
|
+
/* Refusals */
|
|
53
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
54
|
+
/**
|
|
55
|
+
* Why an address was not fetched.
|
|
56
|
+
*
|
|
57
|
+
* Named rather than free text, because the caller has to be able to tell an operator's problem
|
|
58
|
+
* ("nobody has allowed any host") from a writer's ("that address is not one of them") from the
|
|
59
|
+
* site's ("it never answered"), and because the tests assert on them: a refusal for the wrong
|
|
60
|
+
* reason is a check that happened to catch something the *next* URL will walk past.
|
|
61
|
+
*/
|
|
62
|
+
export const UNFURL_REFUSALS = [
|
|
63
|
+
'no_allowlist',
|
|
64
|
+
'not_allowed',
|
|
65
|
+
'private_address',
|
|
66
|
+
'unresolvable',
|
|
67
|
+
'bad_url',
|
|
68
|
+
'too_many_redirects',
|
|
69
|
+
'unreachable',
|
|
70
|
+
'not_readable',
|
|
71
|
+
];
|
|
72
|
+
export class UnfurlRefused extends Error {
|
|
73
|
+
refusal;
|
|
74
|
+
constructor(refusal) {
|
|
75
|
+
super(refusal);
|
|
76
|
+
this.refusal = refusal;
|
|
77
|
+
this.name = 'UnfurlRefused';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
81
|
+
/* Address space */
|
|
82
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
83
|
+
function ipv4Parts(ip) {
|
|
84
|
+
const parts = ip.split('.');
|
|
85
|
+
if (parts.length !== 4)
|
|
86
|
+
return null;
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const part of parts) {
|
|
89
|
+
if (!/^\d{1,3}$/.test(part))
|
|
90
|
+
return null;
|
|
91
|
+
const value = Number(part);
|
|
92
|
+
if (value > 255)
|
|
93
|
+
return null;
|
|
94
|
+
out.push(value);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Everything IPv4 reserves, refused.
|
|
100
|
+
*
|
|
101
|
+
* Written out rather than reduced to "not 10/8, 172.16/12, 192.168/16", because the ones people
|
|
102
|
+
* forget are the ones that matter here: `169.254.169.254` is a cloud metadata service, `0.0.0.0` is
|
|
103
|
+
* *this host* on Linux, and `100.64/10` is a carrier's inside. A decimal or hex-encoded address —
|
|
104
|
+
* `http://2130706433/` — never reaches this function looking like one: the WHATWG URL parser
|
|
105
|
+
* normalises a special-scheme host to dotted quad before anything here sees it.
|
|
106
|
+
*/
|
|
107
|
+
function publicV4(parts) {
|
|
108
|
+
const [a = 0, b = 0, c = 0] = parts;
|
|
109
|
+
if (a === 0)
|
|
110
|
+
return false; // 0.0.0.0/8 — "this network", and 0.0.0.0 is every local interface
|
|
111
|
+
if (a === 10)
|
|
112
|
+
return false;
|
|
113
|
+
if (a === 127)
|
|
114
|
+
return false; // loopback
|
|
115
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
116
|
+
return false; // 100.64/10, carrier-grade NAT
|
|
117
|
+
if (a === 169 && b === 254)
|
|
118
|
+
return false; // link-local, which is where the metadata service lives
|
|
119
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
120
|
+
return false;
|
|
121
|
+
if (a === 192 && b === 0 && (c === 0 || c === 2))
|
|
122
|
+
return false; // IETF protocol assignments, TEST-NET-1
|
|
123
|
+
if (a === 192 && b === 168)
|
|
124
|
+
return false;
|
|
125
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
126
|
+
return false; // benchmarking
|
|
127
|
+
if (a === 198 && b === 51 && c === 100)
|
|
128
|
+
return false; // TEST-NET-2
|
|
129
|
+
if (a === 203 && b === 0 && c === 113)
|
|
130
|
+
return false; // TEST-NET-3
|
|
131
|
+
if (a >= 224)
|
|
132
|
+
return false; // multicast, reserved, and 255.255.255.255
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The same, for IPv6, and the four traps that make it harder.
|
|
137
|
+
*
|
|
138
|
+
* `::1` is loopback and everybody checks it. What gets missed is that IPv6 has three separate ways
|
|
139
|
+
* to *carry* an IPv4 address — `::ffff:127.0.0.1` (mapped), `64:ff9b::7f00:1` (NAT64) and
|
|
140
|
+
* `2002::/16` (6to4) — and a check that only refuses `fc00::/7` and `fe80::/10` lets every one of
|
|
141
|
+
* them through to the loopback interface. So an embedded address is unwrapped and judged as IPv4.
|
|
142
|
+
*/
|
|
143
|
+
function publicV6(groups) {
|
|
144
|
+
const [g0 = 0, g1 = 0, g2 = 0, g3 = 0, g4 = 0, g5 = 0, g6 = 0, g7 = 0] = groups;
|
|
145
|
+
const embedded = (high, low) => [high >> 8, high & 0xff, low >> 8, low & 0xff];
|
|
146
|
+
// `::`, `::1`, and the deprecated `::a.b.c.d` — the first six groups zero means an embedded v4.
|
|
147
|
+
if (g0 === 0 && g1 === 0 && g2 === 0 && g3 === 0 && g4 === 0) {
|
|
148
|
+
if (g5 === 0xffff)
|
|
149
|
+
return publicV4(embedded(g6, g7)); // IPv4-mapped
|
|
150
|
+
if (g5 === 0) {
|
|
151
|
+
if (g6 === 0 && g7 <= 1)
|
|
152
|
+
return false; // :: and ::1
|
|
153
|
+
return publicV4(embedded(g6, g7)); // IPv4-compatible
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (g0 === 0x0064 && g1 === 0xff9b)
|
|
157
|
+
return publicV4(embedded(g6, g7)); // NAT64 well-known prefix
|
|
158
|
+
if (g0 === 0x2002)
|
|
159
|
+
return publicV4(embedded(g1, g2)); // 6to4
|
|
160
|
+
if (g0 === 0x0100 && g1 === 0 && g2 === 0 && g3 === 0)
|
|
161
|
+
return false; // discard-only
|
|
162
|
+
if ((g0 & 0xfe00) === 0xfc00)
|
|
163
|
+
return false; // unique local
|
|
164
|
+
if ((g0 & 0xffc0) === 0xfe80)
|
|
165
|
+
return false; // link-local
|
|
166
|
+
if ((g0 & 0xff00) === 0xff00)
|
|
167
|
+
return false; // multicast
|
|
168
|
+
if (g0 === 0x2001 && g1 === 0x0db8)
|
|
169
|
+
return false; // documentation
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
function ipv6Groups(ip) {
|
|
173
|
+
let text = (ip.split('%')[0] ?? '').toLowerCase();
|
|
174
|
+
if (text.startsWith('[') && text.endsWith(']'))
|
|
175
|
+
text = text.slice(1, -1);
|
|
176
|
+
if (!text.includes(':'))
|
|
177
|
+
return null;
|
|
178
|
+
// A trailing dotted quad is two groups written the other way round.
|
|
179
|
+
const dotted = /(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(text);
|
|
180
|
+
if (dotted) {
|
|
181
|
+
const quad = ipv4Parts(dotted[1]);
|
|
182
|
+
if (!quad)
|
|
183
|
+
return null;
|
|
184
|
+
const high = ((quad[0] << 8) | quad[1]).toString(16);
|
|
185
|
+
const low = ((quad[2] << 8) | quad[3]).toString(16);
|
|
186
|
+
text = `${text.slice(0, dotted.index)}${high}:${low}`;
|
|
187
|
+
}
|
|
188
|
+
const halves = text.split('::');
|
|
189
|
+
if (halves.length > 2)
|
|
190
|
+
return null;
|
|
191
|
+
const head = halves[0] ? halves[0].split(':') : [];
|
|
192
|
+
const tail = halves.length === 2 && halves[1] ? halves[1].split(':') : [];
|
|
193
|
+
let parts;
|
|
194
|
+
if (halves.length === 1) {
|
|
195
|
+
if (head.length !== 8)
|
|
196
|
+
return null;
|
|
197
|
+
parts = head;
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
const missing = 8 - head.length - tail.length;
|
|
201
|
+
if (missing < 0)
|
|
202
|
+
return null;
|
|
203
|
+
parts = [...head, ...Array.from({ length: missing }, () => '0'), ...tail];
|
|
204
|
+
}
|
|
205
|
+
const groups = [];
|
|
206
|
+
for (const part of parts) {
|
|
207
|
+
if (!/^[0-9a-f]{1,4}$/.test(part))
|
|
208
|
+
return null;
|
|
209
|
+
groups.push(Number.parseInt(part, 16));
|
|
210
|
+
}
|
|
211
|
+
return groups;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Is this address one the public internet can route to?
|
|
215
|
+
*
|
|
216
|
+
* **Anything this function cannot parse is not public.** That is the direction the default has to
|
|
217
|
+
* fall: an address shape nobody here anticipated is exactly the one an attacker went looking for.
|
|
218
|
+
*/
|
|
219
|
+
export function isPublicAddress(ip) {
|
|
220
|
+
const trimmed = ip.trim();
|
|
221
|
+
const v4 = ipv4Parts(trimmed);
|
|
222
|
+
if (v4)
|
|
223
|
+
return publicV4(v4);
|
|
224
|
+
const v6 = ipv6Groups(trimmed);
|
|
225
|
+
if (v6)
|
|
226
|
+
return publicV6(v6);
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
230
|
+
/* The allow-list */
|
|
231
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
232
|
+
/**
|
|
233
|
+
* `QUIRE_EMBED_HOSTS`, as a list.
|
|
234
|
+
*
|
|
235
|
+
* `example.com` allows exactly that host. `.example.com` allows it and everything under it — spelled
|
|
236
|
+
* with a leading dot rather than inferred, because "allow github.com" and "allow every host anybody
|
|
237
|
+
* can create under github.io" are different decisions and an operator should have to make the second
|
|
238
|
+
* one on purpose.
|
|
239
|
+
*/
|
|
240
|
+
export function parseHostAllowlist(raw) {
|
|
241
|
+
return (raw ?? '')
|
|
242
|
+
.split(/[\s,]+/)
|
|
243
|
+
.map((entry) => entry.trim().toLowerCase().replace(/\.$/, ''))
|
|
244
|
+
.filter((entry) => entry.length > 0 && /^\.?[a-z0-9.-]+$/.test(entry));
|
|
245
|
+
}
|
|
246
|
+
export function hostAllowed(hostname, allowlist) {
|
|
247
|
+
const host = hostname.trim().toLowerCase().replace(/\.$/, '');
|
|
248
|
+
if (!host)
|
|
249
|
+
return false;
|
|
250
|
+
for (const entry of allowlist) {
|
|
251
|
+
if (entry.startsWith('.')) {
|
|
252
|
+
if (host === entry.slice(1) || host.endsWith(entry))
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
else if (host === entry)
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
const DEFAULT_REDIRECTS = 3;
|
|
261
|
+
const DEFAULT_TIMEOUT_MS = 5_000;
|
|
262
|
+
const DEFAULT_MAX_BYTES = 128 * 1024;
|
|
263
|
+
const resolveWithDns = (hostname) => new Promise((resolve, reject) => {
|
|
264
|
+
dnsLookup(hostname, { all: true, verbatim: true }, (err, addresses) => {
|
|
265
|
+
if (err)
|
|
266
|
+
reject(new UnfurlRefused('unresolvable'));
|
|
267
|
+
else
|
|
268
|
+
resolve(addresses.map((a) => a.address));
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
/**
|
|
272
|
+
* Everything that has to be true before a request is made, in the order it has to be true in.
|
|
273
|
+
*
|
|
274
|
+
* Returns the addresses, because the connection is then pinned to exactly these — checking an
|
|
275
|
+
* address and then letting the socket resolve the name again is checking a different address from
|
|
276
|
+
* the one that gets connected to.
|
|
277
|
+
*/
|
|
278
|
+
export async function checkTarget(url, policy) {
|
|
279
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
280
|
+
throw new UnfurlRefused('bad_url');
|
|
281
|
+
if (url.username || url.password)
|
|
282
|
+
throw new UnfurlRefused('bad_url');
|
|
283
|
+
if (url.href.length > PAGE_EMBED_MAX_URL)
|
|
284
|
+
throw new UnfurlRefused('bad_url');
|
|
285
|
+
if (policy.allowHosts.length === 0)
|
|
286
|
+
throw new UnfurlRefused('no_allowlist');
|
|
287
|
+
const hostname = url.hostname.replace(/^\[|\]$/g, '');
|
|
288
|
+
/*
|
|
289
|
+
* A literal address is its own resolution, so there is nothing to look up — and it is checked
|
|
290
|
+
* before the allow-list for the reason given at the top of this file: the allow-list must never
|
|
291
|
+
* be a way past the address rule.
|
|
292
|
+
*/
|
|
293
|
+
const addresses = isIP(hostname) ? [hostname] : await (policy.resolve ?? resolveWithDns)(hostname);
|
|
294
|
+
if (addresses.length === 0)
|
|
295
|
+
throw new UnfurlRefused('unresolvable');
|
|
296
|
+
for (const address of addresses)
|
|
297
|
+
if (!isPublicAddress(address))
|
|
298
|
+
throw new UnfurlRefused('private_address');
|
|
299
|
+
if (!hostAllowed(hostname, policy.allowHosts))
|
|
300
|
+
throw new UnfurlRefused('not_allowed');
|
|
301
|
+
return addresses;
|
|
302
|
+
}
|
|
303
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
304
|
+
/* One request */
|
|
305
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
306
|
+
/**
|
|
307
|
+
* One HTTP request that does not follow redirects, capped in bytes and in time.
|
|
308
|
+
*
|
|
309
|
+
* `node:http` rather than `fetch`, and the reason is the `lookup` below: it is the only place a
|
|
310
|
+
* resolution can be pinned to addresses that have already been judged, and Node's `fetch` gives no
|
|
311
|
+
* way to install one without adding undici as a dependency. `agent: false` because a pooled socket
|
|
312
|
+
* is keyed on host and port and *not* on the lookup, so pooling would happily hand back a connection
|
|
313
|
+
* that was opened under someone else's guard.
|
|
314
|
+
*/
|
|
315
|
+
export async function httpHop(url, addresses, policy) {
|
|
316
|
+
const send = url.protocol === 'https:' ? httpsRequest : httpRequest;
|
|
317
|
+
return new Promise((resolve, reject) => {
|
|
318
|
+
let settled = false;
|
|
319
|
+
const done = (fn) => {
|
|
320
|
+
if (settled)
|
|
321
|
+
return;
|
|
322
|
+
settled = true;
|
|
323
|
+
fn();
|
|
324
|
+
};
|
|
325
|
+
const req = send(url, {
|
|
326
|
+
method: 'GET',
|
|
327
|
+
agent: false,
|
|
328
|
+
// Pinned. The socket connects to an address this request has already had judged, and there
|
|
329
|
+
// is no second resolution between the check and the connection.
|
|
330
|
+
lookup: (_hostname, options, callback) => {
|
|
331
|
+
const chosen = addresses.map((address) => ({
|
|
332
|
+
address,
|
|
333
|
+
family: isIP(address) === 6 ? 6 : 4,
|
|
334
|
+
}));
|
|
335
|
+
if (typeof options === 'object' && options?.all)
|
|
336
|
+
callback(null, chosen);
|
|
337
|
+
else {
|
|
338
|
+
const first = chosen[0];
|
|
339
|
+
callback(null, first.address, first.family);
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
headers: {
|
|
343
|
+
accept: 'text/html,application/xhtml+xml;q=0.9,*/*;q=0.1',
|
|
344
|
+
'accept-language': 'en',
|
|
345
|
+
'user-agent': 'Kern (+https://kernaio.com)',
|
|
346
|
+
},
|
|
347
|
+
}, (res) => {
|
|
348
|
+
const status = res.statusCode ?? 0;
|
|
349
|
+
const contentType = (res.headers['content-type'] ?? null);
|
|
350
|
+
const location = (res.headers.location ?? null);
|
|
351
|
+
/*
|
|
352
|
+
* A redirect and a non-HTML body are both answered without reading anything: there is no
|
|
353
|
+
* metadata in a 3 GB video, and reading one is how a fetcher becomes a way to make the
|
|
354
|
+
* server download things.
|
|
355
|
+
*/
|
|
356
|
+
if (status >= 300 && status < 400) {
|
|
357
|
+
res.destroy();
|
|
358
|
+
return done(() => resolve({ status, location, contentType, body: '' }));
|
|
359
|
+
}
|
|
360
|
+
if (contentType && !/^\s*(text\/html|application\/xhtml\+xml|text\/plain)/i.test(contentType)) {
|
|
361
|
+
res.destroy();
|
|
362
|
+
return done(() => resolve({ status, location: null, contentType, body: '' }));
|
|
363
|
+
}
|
|
364
|
+
let size = 0;
|
|
365
|
+
const chunks = [];
|
|
366
|
+
res.on('data', (chunk) => {
|
|
367
|
+
size += chunk.length;
|
|
368
|
+
chunks.push(chunk);
|
|
369
|
+
// Enough to hold a `<head>`; the rest of the page is not metadata and is not read.
|
|
370
|
+
if (size >= policy.maxBytes)
|
|
371
|
+
res.destroy();
|
|
372
|
+
});
|
|
373
|
+
const finish = () => done(() => resolve({
|
|
374
|
+
status,
|
|
375
|
+
location: null,
|
|
376
|
+
contentType,
|
|
377
|
+
body: Buffer.concat(chunks).subarray(0, policy.maxBytes).toString('utf8'),
|
|
378
|
+
}));
|
|
379
|
+
res.on('end', finish);
|
|
380
|
+
// A destroyed response is the cap doing its job, not a failure: keep what was read.
|
|
381
|
+
res.on('close', finish);
|
|
382
|
+
res.on('error', finish);
|
|
383
|
+
});
|
|
384
|
+
const timer = setTimeout(() => {
|
|
385
|
+
req.destroy();
|
|
386
|
+
done(() => reject(new UnfurlRefused('unreachable')));
|
|
387
|
+
}, policy.timeoutMs);
|
|
388
|
+
timer.unref?.();
|
|
389
|
+
req.on('error', () => done(() => reject(new UnfurlRefused('unreachable'))));
|
|
390
|
+
req.on('close', () => clearTimeout(timer));
|
|
391
|
+
req.end();
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
395
|
+
/* Reading the metadata */
|
|
396
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
397
|
+
const ENTITIES = {
|
|
398
|
+
amp: '&',
|
|
399
|
+
lt: '<',
|
|
400
|
+
gt: '>',
|
|
401
|
+
quot: '"',
|
|
402
|
+
apos: "'",
|
|
403
|
+
nbsp: ' ',
|
|
404
|
+
'#39': "'",
|
|
405
|
+
'#x27': "'",
|
|
406
|
+
};
|
|
407
|
+
/**
|
|
408
|
+
* Enough entity decoding to read a headline, and no more.
|
|
409
|
+
*
|
|
410
|
+
* The result is escaped again by the renderer before it reaches a page, so this is about the words
|
|
411
|
+
* being right rather than about safety — `AT&T` should be stored as `AT&T` and not as five
|
|
412
|
+
* characters of markup somebody has to read past.
|
|
413
|
+
*/
|
|
414
|
+
function decodeEntities(text) {
|
|
415
|
+
return text.replace(/&(#x?[0-9a-f]+|[a-z]+);/gi, (whole, name) => {
|
|
416
|
+
const key = name.toLowerCase();
|
|
417
|
+
const known = ENTITIES[key];
|
|
418
|
+
if (known)
|
|
419
|
+
return known;
|
|
420
|
+
if (key.startsWith('#x')) {
|
|
421
|
+
const code = Number.parseInt(key.slice(2), 16);
|
|
422
|
+
return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : whole;
|
|
423
|
+
}
|
|
424
|
+
if (key.startsWith('#')) {
|
|
425
|
+
const code = Number.parseInt(key.slice(1), 10);
|
|
426
|
+
return Number.isFinite(code) && code > 0 && code < 0x110000 ? String.fromCodePoint(code) : whole;
|
|
427
|
+
}
|
|
428
|
+
return whole;
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
const clean = (text, max) => {
|
|
432
|
+
if (!text)
|
|
433
|
+
return null;
|
|
434
|
+
const out = decodeEntities(text).replace(/\s+/g, ' ').trim().slice(0, max);
|
|
435
|
+
return out || null;
|
|
436
|
+
};
|
|
437
|
+
/** `<meta property="og:title" content="…">`, either attribute order, either quote. */
|
|
438
|
+
function metaContent(html, key) {
|
|
439
|
+
const named = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
440
|
+
const patterns = [
|
|
441
|
+
new RegExp(`<meta[^>]+(?:property|name)\\s*=\\s*["']${named}["'][^>]*?content\\s*=\\s*["']([^"']*)["']`, 'i'),
|
|
442
|
+
new RegExp(`<meta[^>]+content\\s*=\\s*["']([^"']*)["'][^>]*?(?:property|name)\\s*=\\s*["']${named}["']`, 'i'),
|
|
443
|
+
];
|
|
444
|
+
for (const pattern of patterns) {
|
|
445
|
+
const found = pattern.exec(html);
|
|
446
|
+
if (found?.[1])
|
|
447
|
+
return found[1];
|
|
448
|
+
}
|
|
449
|
+
return null;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* What an HTML page says about itself.
|
|
453
|
+
*
|
|
454
|
+
* Open Graph first because it is what a page puts there deliberately, `<title>` and the description
|
|
455
|
+
* meta as the fallback, and the hostname as the site of last resort — a card with an address and
|
|
456
|
+
* nothing else is still a card, and it is what the renderer draws when there is no title at all.
|
|
457
|
+
*/
|
|
458
|
+
export function metadataFrom(html, url) {
|
|
459
|
+
const head = html.split(/<\/head\s*>/i)[0] ?? html;
|
|
460
|
+
const titleTag = /<title[^>]*>([\s\S]{0,400}?)<\/title\s*>/i.exec(head)?.[1] ?? null;
|
|
461
|
+
return {
|
|
462
|
+
url: url.href,
|
|
463
|
+
title: clean(metaContent(head, 'og:title'), PAGE_EMBED_MAX_TITLE) ??
|
|
464
|
+
clean(metaContent(head, 'twitter:title'), PAGE_EMBED_MAX_TITLE) ??
|
|
465
|
+
clean(titleTag?.replace(/<[^>]*>/g, ''), PAGE_EMBED_MAX_TITLE),
|
|
466
|
+
description: clean(metaContent(head, 'og:description'), PAGE_EMBED_MAX_DESCRIPTION) ??
|
|
467
|
+
clean(metaContent(head, 'description'), PAGE_EMBED_MAX_DESCRIPTION),
|
|
468
|
+
siteName: clean(metaContent(head, 'og:site_name'), PAGE_EMBED_MAX_SITE) ??
|
|
469
|
+
clean(url.hostname, PAGE_EMBED_MAX_SITE),
|
|
470
|
+
};
|
|
471
|
+
}
|
|
472
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
473
|
+
/* The whole thing */
|
|
474
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
475
|
+
/**
|
|
476
|
+
* Fetch a URL and read what it says about itself, or refuse and say which rule refused it.
|
|
477
|
+
*
|
|
478
|
+
* The redirect loop is the part worth reading. Every hop — including the first — goes through
|
|
479
|
+
* `checkTarget` *before* it is requested, so a 302 from a public site to `http://127.0.0.1:4000` is
|
|
480
|
+
* refused at the second check, with the second request never made. That is the only arrangement
|
|
481
|
+
* that works: an HTTP client following redirects on its own has already made the request by the
|
|
482
|
+
* time anything here could look at where it went.
|
|
483
|
+
*/
|
|
484
|
+
export async function unfurl(rawUrl, policy) {
|
|
485
|
+
const full = {
|
|
486
|
+
allowHosts: policy.allowHosts,
|
|
487
|
+
maxRedirects: policy.maxRedirects ?? DEFAULT_REDIRECTS,
|
|
488
|
+
timeoutMs: policy.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
489
|
+
maxBytes: policy.maxBytes ?? DEFAULT_MAX_BYTES,
|
|
490
|
+
resolve: policy.resolve ?? resolveWithDns,
|
|
491
|
+
hop: policy.hop ?? httpHop,
|
|
492
|
+
};
|
|
493
|
+
let url;
|
|
494
|
+
try {
|
|
495
|
+
url = new URL(rawUrl);
|
|
496
|
+
}
|
|
497
|
+
catch {
|
|
498
|
+
throw new UnfurlRefused('bad_url');
|
|
499
|
+
}
|
|
500
|
+
const deadline = Date.now() + full.timeoutMs;
|
|
501
|
+
for (let redirects = 0; redirects <= full.maxRedirects; redirects++) {
|
|
502
|
+
const remaining = deadline - Date.now();
|
|
503
|
+
if (remaining <= 0)
|
|
504
|
+
throw new UnfurlRefused('unreachable');
|
|
505
|
+
const addresses = await checkTarget(url, full);
|
|
506
|
+
const answer = await full.hop(url, addresses, { ...full, timeoutMs: remaining });
|
|
507
|
+
if (answer.status >= 300 && answer.status < 400 && answer.location) {
|
|
508
|
+
let next;
|
|
509
|
+
try {
|
|
510
|
+
next = new URL(answer.location, url);
|
|
511
|
+
}
|
|
512
|
+
catch {
|
|
513
|
+
throw new UnfurlRefused('bad_url');
|
|
514
|
+
}
|
|
515
|
+
url = next;
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
if (answer.status < 200 || answer.status >= 300)
|
|
519
|
+
throw new UnfurlRefused('not_readable');
|
|
520
|
+
if (!answer.body) {
|
|
521
|
+
// Nothing to read, but the address answered: a card with the URL on it is still honest.
|
|
522
|
+
return { url: url.href, title: null, description: null, siteName: url.hostname };
|
|
523
|
+
}
|
|
524
|
+
return metadataFrom(answer.body, url);
|
|
525
|
+
}
|
|
526
|
+
throw new UnfurlRefused('too_many_redirects');
|
|
527
|
+
}
|
|
528
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
529
|
+
/* The service */
|
|
530
|
+
/* ---------------------------------------------------------------------------------------------- */
|
|
531
|
+
/** How long an answer is kept. A headline does not change in ten minutes, and a page render is hot. */
|
|
532
|
+
const CACHE_TTL_MS = 10 * 60_000;
|
|
533
|
+
const CACHE_MAX = 500;
|
|
534
|
+
/**
|
|
535
|
+
* The words a refusal is reported with.
|
|
536
|
+
*
|
|
537
|
+
* Two of the eight are an *operator's* problem rather than the writer's, and they get their own
|
|
538
|
+
* sentence: somebody pasting a link into a page cannot be expected to work out that an environment
|
|
539
|
+
* variable is empty, and "that address is not allowed" would send them looking for a typo.
|
|
540
|
+
*/
|
|
541
|
+
const REFUSAL_MESSAGES = {
|
|
542
|
+
no_allowlist: 'No sites have been allowed for embedding on this instance. An administrator sets QUIRE_EMBED_HOSTS.',
|
|
543
|
+
not_allowed: 'This site is not one of the sites allowed for embedding on this instance.',
|
|
544
|
+
private_address: 'That address is on a private network, so it will not be fetched.',
|
|
545
|
+
unresolvable: 'That address could not be resolved.',
|
|
546
|
+
bad_url: 'That is not a web address that can be embedded.',
|
|
547
|
+
too_many_redirects: 'That address redirected too many times.',
|
|
548
|
+
unreachable: 'That site did not answer.',
|
|
549
|
+
not_readable: 'That page could not be read.',
|
|
550
|
+
};
|
|
551
|
+
export function quireUnfurl(kernel) {
|
|
552
|
+
const cache = new Map();
|
|
553
|
+
/*
|
|
554
|
+
* Read on every call rather than at boot, so an operator who sets the variable and restarts one
|
|
555
|
+
* process does not have to wonder which of them picked it up — and so a test can set it.
|
|
556
|
+
*/
|
|
557
|
+
const allowHosts = () => parseHostAllowlist(process.env.QUIRE_EMBED_HOSTS);
|
|
558
|
+
return {
|
|
559
|
+
allowHosts,
|
|
560
|
+
/**
|
|
561
|
+
* Unfurl one address, refusing with a sentence rather than a stack trace.
|
|
562
|
+
*
|
|
563
|
+
* Cached by the URL the writer typed. The cache is per process and small: it exists so that a
|
|
564
|
+
* page whose writer pastes the same link twice is one request, not so that an unfurl is free.
|
|
565
|
+
*/
|
|
566
|
+
async get(rawUrl) {
|
|
567
|
+
const key = rawUrl.trim();
|
|
568
|
+
const hit = cache.get(key);
|
|
569
|
+
if (hit && Date.now() - hit.at < CACHE_TTL_MS)
|
|
570
|
+
return hit.value;
|
|
571
|
+
try {
|
|
572
|
+
const value = await unfurl(key, { allowHosts: allowHosts() });
|
|
573
|
+
if (cache.size >= CACHE_MAX)
|
|
574
|
+
cache.clear();
|
|
575
|
+
cache.set(key, { at: Date.now(), value });
|
|
576
|
+
return value;
|
|
577
|
+
}
|
|
578
|
+
catch (err) {
|
|
579
|
+
if (err instanceof UnfurlRefused) {
|
|
580
|
+
/*
|
|
581
|
+
* Logged at info rather than warn: a refusal is this working, and a workspace with a
|
|
582
|
+
* strict allow-list would otherwise fill an operator's log with warnings about the
|
|
583
|
+
* feature behaving exactly as configured.
|
|
584
|
+
*/
|
|
585
|
+
kernel.log.info({ refusal: err.refusal }, 'quire: refused to unfurl an address');
|
|
586
|
+
throw new KernError('BAD_REQUEST', REFUSAL_MESSAGES[err.refusal]);
|
|
587
|
+
}
|
|
588
|
+
throw err;
|
|
589
|
+
}
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
//# sourceMappingURL=unfurl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"unfurl.js","sourceRoot":"","sources":["../../../src/server/services/unfurl.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernhq/module-quire",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Kern Quire: collaborative documents, spaces and page trees",
|
|
5
5
|
"homepage": "https://github.com/KernAIO/module-quire#readme",
|
|
6
6
|
"license": "AGPL-3.0-only",
|
|
@@ -53,15 +53,12 @@
|
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
55
|
"@kernhq/contracts": "^0.7.0",
|
|
56
|
-
"@kernhq/kernel": "^0.
|
|
57
|
-
"@kernhq/ui": "^0.
|
|
56
|
+
"@kernhq/kernel": "^0.9.0",
|
|
57
|
+
"@kernhq/ui": "^0.14.0",
|
|
58
58
|
"@tanstack/svelte-query": "^6.1.0",
|
|
59
59
|
"svelte": "^5.46.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependenciesMeta": {
|
|
62
|
-
"@kernhq/ui": {
|
|
63
|
-
"optional": true
|
|
64
|
-
},
|
|
65
62
|
"@tanstack/svelte-query": {
|
|
66
63
|
"optional": true
|
|
67
64
|
},
|
|
@@ -73,9 +70,9 @@
|
|
|
73
70
|
"@biomejs/biome": "^2.0.0",
|
|
74
71
|
"@changesets/cli": "^2.27.0",
|
|
75
72
|
"@kernhq/contracts": "^0.7.0",
|
|
76
|
-
"@kernhq/kernel": "^0.
|
|
73
|
+
"@kernhq/kernel": "^0.9.0",
|
|
77
74
|
"@kernhq/tsconfig": "^0.1.0",
|
|
78
|
-
"@kernhq/ui": "^0.
|
|
75
|
+
"@kernhq/ui": "^0.14.0",
|
|
79
76
|
"@tanstack/svelte-query": "^6.1.0",
|
|
80
77
|
"@types/node": "^24.0.0",
|
|
81
78
|
"@types/pg": "^8.15.0",
|