@wakeshift/openclaw 1.4.38 → 1.4.40
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/plugin.js +8 -13
- package/dist/transport.js +70 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/plugin.js
CHANGED
|
@@ -32,7 +32,7 @@ import { acceptable, BeaconError, beaconIn, findBeacon, jwkKey, mcpServer, mcpTo
|
|
|
32
32
|
import { Conversation, Ledger } from "./deliveries.js";
|
|
33
33
|
import { agentOf } from "./hooks.js";
|
|
34
34
|
import { linkKey, PINS_UNREADABLE } from "./store.js";
|
|
35
|
-
import { Transport } from "./transport.js";
|
|
35
|
+
import { checkStreamLink, Transport } from "./transport.js";
|
|
36
36
|
import { CLIENT_HEADER, clientHeader } from "./version.js";
|
|
37
37
|
/** The connected note's start (harnesses.Connected): agents look for it. */
|
|
38
38
|
export const CONNECTED = "Wakeshift: connected";
|
|
@@ -86,16 +86,7 @@ export function stripBeacon(v) {
|
|
|
86
86
|
* ws for http), as the plugin trusts only links from the pinned Wakeshift.
|
|
87
87
|
*/
|
|
88
88
|
export function linkOnIssuer(link, issuer) {
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
l = new URL(link);
|
|
92
|
-
i = new URL(issuer);
|
|
93
|
-
}
|
|
94
|
-
catch {
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
const scheme = { "wss:": "https:", "https:": "https:", "ws:": "http:", "http:": "http:" };
|
|
98
|
-
return !!l.host && scheme[l.protocol] !== undefined && scheme[l.protocol] === i.protocol && l.host.toLowerCase() === i.host.toLowerCase();
|
|
89
|
+
return checkStreamLink(link, issuer) === undefined;
|
|
99
90
|
}
|
|
100
91
|
/** How often delivery records are pruned while the gateway runs. */
|
|
101
92
|
export const PRUNE_EVERY = 3600 * 1000;
|
|
@@ -300,8 +291,11 @@ export class Wakeshift {
|
|
|
300
291
|
return NOTE_TAKEN; // the link it had stays
|
|
301
292
|
return noteFailed(e.message);
|
|
302
293
|
}
|
|
303
|
-
|
|
304
|
-
|
|
294
|
+
const refused = checkStreamLink(link, this.o.issuer);
|
|
295
|
+
if (refused) {
|
|
296
|
+
this.o.host.log.warn(`wakeshift (${sessionKey}): ${refused}`);
|
|
297
|
+
return noteFailed(refused);
|
|
298
|
+
}
|
|
305
299
|
// A new stream ID: what the link it replaces brought isn't this one's to acknowledge.
|
|
306
300
|
const saved = { conversation, sessionKey, agentId: call.agentId ?? "", link, issuer: this.o.issuer, app: claims.app, stream: randomBytes(8).toString("hex") };
|
|
307
301
|
this.o.store.saveLink(saved);
|
|
@@ -478,6 +472,7 @@ export class Wakeshift {
|
|
|
478
472
|
t = new Transport({
|
|
479
473
|
...this.o.stream,
|
|
480
474
|
link: l.link,
|
|
475
|
+
issuer: this.o.issuer,
|
|
481
476
|
fetch: this.f,
|
|
482
477
|
log,
|
|
483
478
|
receive: (ds) => c.receive(ds, stream),
|
package/dist/transport.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// including another 403 or 404 (a router during a deploy, a firewall),
|
|
13
13
|
// reconnects with backoff. A connection that ends at once without a
|
|
14
14
|
// delivery counts as a failure for the backoff.
|
|
15
|
+
import { isIP } from "node:net";
|
|
15
16
|
import { CLIENT_HEADER, clientHeader } from "./version.js";
|
|
16
17
|
/** The longest SSE line, and the largest poll answer, read; a longer line is skipped. */
|
|
17
18
|
export const MAX_LINE = 1 << 20;
|
|
@@ -31,6 +32,59 @@ export function parseLink(link) {
|
|
|
31
32
|
return undefined;
|
|
32
33
|
return { base: `${scheme}//${u.host}`, token };
|
|
33
34
|
}
|
|
35
|
+
/** localhost, or a loopback IP address (127.0.0.0/8, ::1), as Hermes's plugin reads it (ipaddress). */
|
|
36
|
+
function loopback(hostname) {
|
|
37
|
+
const h = hostname.replace(/^\[|\]$/g, "");
|
|
38
|
+
if (h === "localhost")
|
|
39
|
+
return true;
|
|
40
|
+
if (isIP(h) === 4)
|
|
41
|
+
return h.startsWith("127.");
|
|
42
|
+
return isIP(h) === 6 && /^(0{0,4}:){7}0{0,3}1$|^::1$/.test(h);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Undefined if a stream link may be dialed: wss or https on the issuer's
|
|
46
|
+
* host exactly (its port included), or ws or http on it where the issuer
|
|
47
|
+
* is itself http on loopback (development and tests). Otherwise why not,
|
|
48
|
+
* naming only the link's scheme and host (never its token or a user name):
|
|
49
|
+
* another host, a lookalike or subdomain, userinfo, another port, an IP
|
|
50
|
+
* literal for a named issuer, ws or http to an https issuer.
|
|
51
|
+
*/
|
|
52
|
+
export function checkStreamLink(link, issuer) {
|
|
53
|
+
let l;
|
|
54
|
+
try {
|
|
55
|
+
l = new URL(link);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
return "refused a stream link: it isn't a stream link";
|
|
59
|
+
}
|
|
60
|
+
const scheme = l.protocol.replace(/:$/, "");
|
|
61
|
+
if (!l.host)
|
|
62
|
+
return "refused a stream link: it isn't a stream link";
|
|
63
|
+
if (l.username || l.password)
|
|
64
|
+
return `refused a stream link to ${scheme}://…@${l.host}: it carries a user name or password`;
|
|
65
|
+
const where = `${scheme}://${l.host}`;
|
|
66
|
+
const secure = { wss: true, https: true, ws: false, http: false }[scheme];
|
|
67
|
+
if (secure === undefined)
|
|
68
|
+
return `refused a stream link to ${where}: it isn't wss or https`;
|
|
69
|
+
let i;
|
|
70
|
+
try {
|
|
71
|
+
i = new URL(issuer);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return `refused a stream link to ${where}: no Wakeshift is trusted here`;
|
|
75
|
+
}
|
|
76
|
+
if (!i.host || i.username || i.password)
|
|
77
|
+
return `refused a stream link to ${where}: no Wakeshift is trusted here`;
|
|
78
|
+
if (l.host.toLowerCase() === i.host.toLowerCase()) {
|
|
79
|
+
if (i.protocol === "https:" && secure)
|
|
80
|
+
return undefined;
|
|
81
|
+
if (i.protocol === "http:" && !secure && loopback(i.hostname))
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
return `refused a stream link to ${where}: Wakeshift's stream links go only to ${i.protocol}//${i.host}`;
|
|
85
|
+
}
|
|
86
|
+
class Refused extends Error {
|
|
87
|
+
}
|
|
34
88
|
export function sameOrigin(a, b) {
|
|
35
89
|
const x = parseLink(a), y = parseLink(b);
|
|
36
90
|
return !!x && !!y && x.base === y.base;
|
|
@@ -126,6 +180,11 @@ export class Transport {
|
|
|
126
180
|
async ack(ids) {
|
|
127
181
|
if (ids.length === 0 || ids.length > 100)
|
|
128
182
|
throw new Error("ack: 1 to 100 IDs");
|
|
183
|
+
const why = checkStreamLink(this.link, this.o.issuer);
|
|
184
|
+
if (why) {
|
|
185
|
+
this.log(why);
|
|
186
|
+
return 401;
|
|
187
|
+
}
|
|
129
188
|
const p = parseLink(this.link);
|
|
130
189
|
if (!p)
|
|
131
190
|
return 401;
|
|
@@ -187,6 +246,13 @@ export class Transport {
|
|
|
187
246
|
catch (e) {
|
|
188
247
|
if (this.stopped)
|
|
189
248
|
break;
|
|
249
|
+
if (e instanceof Refused) {
|
|
250
|
+
// Never dialed: its token goes nowhere. The link is kept, in case
|
|
251
|
+
// its Wakeshift is pinned again.
|
|
252
|
+
this.log(e.message);
|
|
253
|
+
result = { why: "refused" };
|
|
254
|
+
break;
|
|
255
|
+
}
|
|
190
256
|
if (e instanceof Gone) {
|
|
191
257
|
result = { why: "gone", reason: e.reason };
|
|
192
258
|
break;
|
|
@@ -221,6 +287,9 @@ export class Transport {
|
|
|
221
287
|
this.resolveDone(result);
|
|
222
288
|
}
|
|
223
289
|
parts() {
|
|
290
|
+
const why = checkStreamLink(this.link, this.o.issuer);
|
|
291
|
+
if (why)
|
|
292
|
+
throw new Refused(why);
|
|
224
293
|
const p = parseLink(this.link);
|
|
225
294
|
if (!p)
|
|
226
295
|
throw new Gone("not a stream link");
|
|
@@ -246,7 +315,7 @@ export class Transport {
|
|
|
246
315
|
if (typeof next !== "string" || !next)
|
|
247
316
|
return;
|
|
248
317
|
const p = parseLink(next);
|
|
249
|
-
if (!p || !sameOrigin(next, this.link))
|
|
318
|
+
if (!p || !sameOrigin(next, this.link) || checkStreamLink(next, this.o.issuer))
|
|
250
319
|
return;
|
|
251
320
|
const cur = new URL(this.link);
|
|
252
321
|
const link = `${cur.protocol}//${cur.host}/v1/stream?link=${encodeURIComponent(p.token)}`;
|
package/dist/version.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// ask the user to update. "dev" here; `npm run pack` stamps the build's
|
|
4
4
|
// version into dist/version.js (scripts/stamp-version.mjs, from
|
|
5
5
|
// WAKESHIFT_VERSION).
|
|
6
|
-
export const VERSION = "2026.11.
|
|
6
|
+
export const VERSION = "2026.11.09.2152+03d15b5";
|
|
7
7
|
export const CLIENT_HEADER = "X-Wakeshift-Connector";
|
|
8
8
|
/** X-Wakeshift-Connector's value: wakeshift-openclaw/<version> openclaw. */
|
|
9
9
|
export function clientHeader(version = VERSION) {
|
package/package.json
CHANGED