@north-light/crouter 0.3.283 → 0.3.285
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/api/client.d.ts +3 -1
- package/dist/daemon/api/server.js +72 -7
- package/dist/shared/env.d.ts +5 -0
- package/dist/shared/env.js +8 -0
- package/package.json +6 -1
- package/runtime.lock.json +35 -2
package/dist/api/client.d.ts
CHANGED
|
@@ -28,7 +28,9 @@ export interface CrtrClientOptions {
|
|
|
28
28
|
socketPath?: string;
|
|
29
29
|
/** `http(s)://host:port` for TCP/remote transport. */
|
|
30
30
|
baseUrl?: string;
|
|
31
|
-
/** Extra headers
|
|
31
|
+
/** Extra headers, e.g. `{ authorization: 'Bearer <token>' }` when the target
|
|
32
|
+
* crtrd's TCP listener has `CRTRD_TOKEN` set (unix-socket transport is
|
|
33
|
+
* never checked, and a TCP daemon with no token set ignores this too). */
|
|
32
34
|
headers?: Record<string, string>;
|
|
33
35
|
/** Autostart on a cold socket (default true for socketPath, false for baseUrl). */
|
|
34
36
|
autostart?: boolean;
|
|
@@ -3,10 +3,14 @@
|
|
|
3
3
|
// Two `http.Server` listeners share one logical API (a Node server can bind one
|
|
4
4
|
// handle only):
|
|
5
5
|
// - the unix socket at `apiSocketPath()` — ALWAYS bound, owner-only (mode
|
|
6
|
-
// 0600 = the fs-perms-are-auth model);
|
|
6
|
+
// 0600 = the fs-perms-are-auth model); NEVER token-checked, token set or
|
|
7
|
+
// not;
|
|
7
8
|
// - an opt-in TCP listener, bound only when `--tcp <host:port>` / `CRTRD_TCP`
|
|
8
|
-
// is set
|
|
9
|
-
//
|
|
9
|
+
// is set. When `CRTRD_TOKEN` is ALSO set, every TCP request and WS upgrade
|
|
10
|
+
// must carry `Authorization: Bearer <token>` or the daemon answers 401
|
|
11
|
+
// with the standard `ErrorBody` (errors.ts) — otherwise (the default) TCP
|
|
12
|
+
// gets no auth check at all, unchanged from before this guard existed (a
|
|
13
|
+
// fronting edge owns auth there instead).
|
|
10
14
|
//
|
|
11
15
|
// Non-upgrade requests flow through the hand-rolled `Router` (A-6) loaded with
|
|
12
16
|
// every handler route table (A-7). `GET /v1/nodes/:id/attach` upgrades are
|
|
@@ -17,7 +21,7 @@ import { createServer } from 'node:http';
|
|
|
17
21
|
import { chmodSync, existsSync, unlinkSync } from 'node:fs';
|
|
18
22
|
import { apiSocketPath } from '../../core/canvas/paths.js';
|
|
19
23
|
import { emitEvent } from '../../core/events/emit.js';
|
|
20
|
-
import { envCrtrdTcp } from '../../shared/env.js';
|
|
24
|
+
import { envCrtrdTcp, envCrtrdToken } from '../../shared/env.js';
|
|
21
25
|
import { Router } from './router.js';
|
|
22
26
|
import { handleAttachUpgrade } from './bridge.js';
|
|
23
27
|
import { attachRoutes } from './handlers/attach.js';
|
|
@@ -114,6 +118,46 @@ function parseTcp(spec) {
|
|
|
114
118
|
return null;
|
|
115
119
|
return { host: host === '' ? '0.0.0.0' : host, port };
|
|
116
120
|
}
|
|
121
|
+
/** The standard `ErrorBody` (errors.ts) for a TCP request/upgrade missing or
|
|
122
|
+
* carrying a wrong `Authorization: Bearer <token>` header. Only ever written
|
|
123
|
+
* when `CRTRD_TOKEN` is set — an unset token never reaches this path. */
|
|
124
|
+
const UNAUTHORIZED_BODY = {
|
|
125
|
+
error: { code: 'unauthorized', message: 'missing or invalid bearer token' },
|
|
126
|
+
};
|
|
127
|
+
/** Does `req` carry the exact `Authorization: Bearer <token>` this TCP
|
|
128
|
+
* listener requires? */
|
|
129
|
+
function isAuthorized(req, token) {
|
|
130
|
+
return req.headers['authorization'] === `Bearer ${token}`;
|
|
131
|
+
}
|
|
132
|
+
/** Reject an unauthorized non-upgrade TCP request with the standard 401 body. */
|
|
133
|
+
function writeUnauthorized(res) {
|
|
134
|
+
const payload = JSON.stringify(UNAUTHORIZED_BODY);
|
|
135
|
+
res.writeHead(401, { 'content-type': 'application/json; charset=utf-8' });
|
|
136
|
+
res.end(payload);
|
|
137
|
+
}
|
|
138
|
+
/** Reject an unauthorized TCP WS-upgrade attempt: this is a raw pre-upgrade
|
|
139
|
+
* socket (no `ServerResponse`), so the 401 is written as a literal HTTP
|
|
140
|
+
* response — mirroring `bridge.ts`'s `refuseUpgrade` (`end()`, not
|
|
141
|
+
* `write()`+`destroy()`, so a slow peer can't truncate the body) — before the
|
|
142
|
+
* socket closes. */
|
|
143
|
+
function destroyUpgradeUnauthorized(socket) {
|
|
144
|
+
const payload = JSON.stringify(UNAUTHORIZED_BODY);
|
|
145
|
+
const response = `HTTP/1.1 401 Unauthorized\r\n` +
|
|
146
|
+
`content-type: application/json; charset=utf-8\r\n` +
|
|
147
|
+
`content-length: ${Buffer.byteLength(payload)}\r\n` +
|
|
148
|
+
`connection: close\r\n\r\n${payload}`;
|
|
149
|
+
try {
|
|
150
|
+
socket.end(response);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
try {
|
|
154
|
+
socket.destroy();
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
/* already gone */
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
117
161
|
/** Close one http.Server, dropping idle keep-alives so the callback fires. */
|
|
118
162
|
function closeServer(server) {
|
|
119
163
|
return new Promise((resolve) => {
|
|
@@ -244,7 +288,9 @@ export function createApiServer(opts = {}) {
|
|
|
244
288
|
});
|
|
245
289
|
}
|
|
246
290
|
});
|
|
247
|
-
// TCP — opt-in only. A SECOND http.Server sharing the same handlers
|
|
291
|
+
// TCP — opt-in only. A SECOND http.Server sharing the same handlers, gated
|
|
292
|
+
// by CRTRD_TOKEN when set (never the unix socket, which shares onRequest/
|
|
293
|
+
// onUpgrade directly above with no wrapping at all).
|
|
248
294
|
let tcpServer;
|
|
249
295
|
const tcpSpec = opts.tcp ?? envCrtrdTcp();
|
|
250
296
|
if (tcpSpec !== undefined && tcpSpec !== '') {
|
|
@@ -257,8 +303,27 @@ export function createApiServer(opts = {}) {
|
|
|
257
303
|
});
|
|
258
304
|
}
|
|
259
305
|
else {
|
|
260
|
-
|
|
261
|
-
|
|
306
|
+
const token = envCrtrdToken();
|
|
307
|
+
const tcpOnRequest = token === undefined
|
|
308
|
+
? onRequest
|
|
309
|
+
: (req, res) => {
|
|
310
|
+
if (!isAuthorized(req, token)) {
|
|
311
|
+
writeUnauthorized(res);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
onRequest(req, res);
|
|
315
|
+
};
|
|
316
|
+
const tcpOnUpgrade = token === undefined
|
|
317
|
+
? onUpgrade
|
|
318
|
+
: (req, socket, head) => {
|
|
319
|
+
if (!isAuthorized(req, token)) {
|
|
320
|
+
destroyUpgradeUnauthorized(socket);
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
onUpgrade(req, socket, head);
|
|
324
|
+
};
|
|
325
|
+
tcpServer = createServer(tcpOnRequest);
|
|
326
|
+
tcpServer.on('upgrade', tcpOnUpgrade);
|
|
262
327
|
bindErrorHandler(tcpServer);
|
|
263
328
|
tcpServer.listen(parsed.port, parsed.host);
|
|
264
329
|
}
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -56,6 +56,11 @@ export declare function envNoDaemonAutostart(): boolean;
|
|
|
56
56
|
/** The `--tcp` fallback for crtrd's opt-in TCP listener (`CRTRD_TCP`), raw
|
|
57
57
|
* (`host:port` or undefined — absent means unix socket only). */
|
|
58
58
|
export declare function envCrtrdTcp(): string | undefined;
|
|
59
|
+
/** The opt-in bearer token guarding crtrd's TCP listener (`CRTRD_TOKEN`).
|
|
60
|
+
* Unset (or blank) → undefined, and the TCP listener performs NO auth check
|
|
61
|
+
* at all — exactly today's behavior. The unix socket never checks this,
|
|
62
|
+
* set or not. */
|
|
63
|
+
export declare function envCrtrdToken(): string | undefined;
|
|
59
64
|
/** Kill switch for the host-exports writer/pruner (`CRTR_NO_EXPORTS=1`). */
|
|
60
65
|
export declare function envNoExports(): boolean;
|
|
61
66
|
/** Verbose-diagnostics gate for otherwise-silent best-effort catches
|
package/dist/shared/env.js
CHANGED
|
@@ -137,6 +137,14 @@ export function envNoDaemonAutostart() {
|
|
|
137
137
|
export function envCrtrdTcp() {
|
|
138
138
|
return process.env['CRTRD_TCP'];
|
|
139
139
|
}
|
|
140
|
+
/** The opt-in bearer token guarding crtrd's TCP listener (`CRTRD_TOKEN`).
|
|
141
|
+
* Unset (or blank) → undefined, and the TCP listener performs NO auth check
|
|
142
|
+
* at all — exactly today's behavior. The unix socket never checks this,
|
|
143
|
+
* set or not. */
|
|
144
|
+
export function envCrtrdToken() {
|
|
145
|
+
const raw = process.env['CRTRD_TOKEN'];
|
|
146
|
+
return raw !== undefined && raw !== '' ? raw : undefined;
|
|
147
|
+
}
|
|
140
148
|
/** Kill switch for the host-exports writer/pruner (`CRTR_NO_EXPORTS=1`). */
|
|
141
149
|
export function envNoExports() {
|
|
142
150
|
return process.env['CRTR_NO_EXPORTS'] === '1';
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.285",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"workspaces": [
|
|
9
|
+
"packages/*"
|
|
10
|
+
],
|
|
8
11
|
"bin": {
|
|
9
12
|
"crtr": "bin/crtr",
|
|
10
13
|
"crouter": "bin/crouter",
|
|
@@ -73,6 +76,8 @@
|
|
|
73
76
|
"activate": "npm run build && npm run install-runtime && crtr sys daemon restart",
|
|
74
77
|
"activate:pull": "git pull && npm install && npm run activate",
|
|
75
78
|
"build:api": "tsc -p packages/crouter-api/tsconfig.json",
|
|
79
|
+
"build:env-docker": "tsc -p packages/crouter-env-docker/tsconfig.json",
|
|
80
|
+
"build:sdk": "tsc -p packages/crouter-sdk/tsconfig.json",
|
|
76
81
|
"typecheck:pi-packages": "npx tsc -p src/builtin-pi-packages/pi-crtr-extensions/tsconfig.json",
|
|
77
82
|
"test:build-output": "node scripts/build-concurrency-test.mjs",
|
|
78
83
|
"check:cli-no-canvas": "node scripts/check-cli-no-canvas.mjs",
|
package/runtime.lock.json
CHANGED
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/crouter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.285",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/crouter",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.285",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "MIT",
|
|
12
|
+
"workspaces": [
|
|
13
|
+
"packages/*"
|
|
14
|
+
],
|
|
12
15
|
"dependencies": {
|
|
13
16
|
"@earendil-works/pi-agent-core": "0.84.4",
|
|
14
17
|
"@earendil-works/pi-ai": "0.84.4",
|
|
@@ -2771,6 +2774,18 @@
|
|
|
2771
2774
|
"node": ">=18.0.0"
|
|
2772
2775
|
}
|
|
2773
2776
|
},
|
|
2777
|
+
"node_modules/@north-light/crouter-api": {
|
|
2778
|
+
"resolved": "packages/crouter-api",
|
|
2779
|
+
"link": true
|
|
2780
|
+
},
|
|
2781
|
+
"node_modules/@north-light/crouter-env-docker": {
|
|
2782
|
+
"resolved": "packages/crouter-env-docker",
|
|
2783
|
+
"link": true
|
|
2784
|
+
},
|
|
2785
|
+
"node_modules/@north-light/crouter-sdk": {
|
|
2786
|
+
"resolved": "packages/crouter-sdk",
|
|
2787
|
+
"link": true
|
|
2788
|
+
},
|
|
2774
2789
|
"node_modules/@protobufjs/aspromise": {
|
|
2775
2790
|
"version": "1.1.2",
|
|
2776
2791
|
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
|
@@ -4226,6 +4241,24 @@
|
|
|
4226
4241
|
"funding": {
|
|
4227
4242
|
"url": "https://github.com/sponsors/colinhacks"
|
|
4228
4243
|
}
|
|
4244
|
+
},
|
|
4245
|
+
"packages/crouter-api": {
|
|
4246
|
+
"name": "@north-light/crouter-api",
|
|
4247
|
+
"version": "0.3.285",
|
|
4248
|
+
"license": "UNLICENSED"
|
|
4249
|
+
},
|
|
4250
|
+
"packages/crouter-env-docker": {
|
|
4251
|
+
"name": "@north-light/crouter-env-docker",
|
|
4252
|
+
"version": "0.3.285",
|
|
4253
|
+
"license": "UNLICENSED"
|
|
4254
|
+
},
|
|
4255
|
+
"packages/crouter-sdk": {
|
|
4256
|
+
"name": "@north-light/crouter-sdk",
|
|
4257
|
+
"version": "0.3.285",
|
|
4258
|
+
"license": "UNLICENSED",
|
|
4259
|
+
"dependencies": {
|
|
4260
|
+
"@north-light/crouter-api": "*"
|
|
4261
|
+
}
|
|
4229
4262
|
}
|
|
4230
4263
|
}
|
|
4231
4264
|
}
|