@nylorun/runtime 0.16.0-beta → 0.19.0-beta
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/CHANGELOG.md +207 -0
- package/README.md +105 -63
- package/dist/adapters/execution/restate.d.ts +1 -3
- package/dist/adapters/execution/restate.js +4 -31
- package/dist/adapters/replication/pgoutput.js +1 -1
- package/dist/adapters/sandbox/local.js +1 -1
- package/dist/adapters/sandbox/virtual.js +1 -1
- package/dist/api/ag-ui/endpoint.js +1 -2
- package/dist/api/ag-ui/routes.d.ts +1 -1
- package/dist/api/ag-ui/routes.js +11 -13
- package/dist/api/ag-ui/translate.d.ts +1 -3
- package/dist/api/ag-ui/translate.js +2 -17
- package/dist/api/components.d.ts +4783 -2588
- package/dist/api/components.js +19 -21
- package/dist/api/gate/routes.d.ts +7 -8
- package/dist/api/gate/routes.js +58 -52
- package/dist/api/http/app.d.ts +6 -0
- package/dist/api/http/app.js +20 -14
- package/dist/api/http/define.d.ts +22 -13
- package/dist/api/http/define.js +43 -39
- package/dist/api/http/routes/access.d.ts +3 -19
- package/dist/api/http/routes/access.js +25 -184
- package/dist/api/http/routes/artifacts.js +3 -7
- package/dist/api/http/routes/files.d.ts +8 -0
- package/dist/api/http/routes/files.js +77 -0
- package/dist/api/http/routes/keys.d.ts +10 -0
- package/dist/api/http/routes/keys.js +64 -0
- package/dist/api/http/routes/me.d.ts +13 -0
- package/dist/api/http/routes/me.js +48 -0
- package/dist/api/http/routes/reads.d.ts +66 -0
- package/dist/api/http/routes/reads.js +127 -0
- package/dist/api/http/routes/sandboxes.d.ts +2 -2
- package/dist/api/http/routes/sandboxes.js +8 -7
- package/dist/api/http/routes/sessions.js +31 -20
- package/dist/api/http/routes/tenant.js +15 -18
- package/dist/api/http/routes/vaults.d.ts +13 -3
- package/dist/api/http/routes/vaults.js +136 -64
- package/dist/api/openapi.d.ts +10 -11
- package/dist/api/openapi.js +292 -68
- package/dist/artifacts/links.js +6 -6
- package/dist/artifacts/service.js +1 -1
- package/dist/core/flow-host.d.ts +44 -23
- package/dist/core/flow-host.js +88 -106
- package/dist/core/provider.d.ts +4 -1
- package/dist/core/provider.js +6 -1
- package/dist/core/sandbox-routes.d.ts +0 -9
- package/dist/core/sandbox-routes.js +2 -21
- package/dist/execution/memory.d.ts +4 -5
- package/dist/execution/memory.js +11 -25
- package/dist/execution/types.d.ts +3 -33
- package/dist/execution/types.js +2 -12
- package/dist/gates/http-tool.d.ts +86 -0
- package/dist/gates/http-tool.js +197 -0
- package/dist/gates/mcp-handler.d.ts +3 -0
- package/dist/gates/mcp-handler.js +6 -4
- package/dist/gates/meter.d.ts +2 -2
- package/dist/gates/meter.js +6 -0
- package/dist/gates/tenant-vaults.d.ts +19 -14
- package/dist/gates/tenant-vaults.js +17 -12
- package/dist/gates/tool-calls.d.ts +7 -15
- package/dist/gates/tool-calls.js +4 -4
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +23 -47
- package/dist/gates/tool-contract.d.ts +27 -39
- package/dist/gates/tool-contract.js +30 -21
- package/dist/gates/tool-gate.d.ts +25 -24
- package/dist/gates/tool-gate.js +11 -7
- package/dist/harness/calls.d.ts +10 -4
- package/dist/harness/calls.js +15 -6
- package/dist/harness/executors.d.ts +1 -1
- package/dist/harness/executors.js +17 -12
- package/dist/harness/main.d.ts +2 -2
- package/dist/harness/main.js +5 -13
- package/dist/harness/service.d.ts +4 -5
- package/dist/harness/service.js +9 -2
- package/dist/harness-api/in-process.js +2 -2
- package/dist/harness-api/record.d.ts +6 -3
- package/dist/harness-api/record.js +65 -72
- package/dist/harness-api/server.d.ts +1 -9
- package/dist/harness-api/server.js +19 -17
- package/dist/harness-api/start.js +1 -5
- package/dist/harness-api/workspace.js +2 -3
- package/dist/harness-api/ws-server.js +1 -1
- package/dist/host/app.d.ts +2 -10
- package/dist/host/app.js +24 -44
- package/dist/host/config-for.d.ts +5 -5
- package/dist/host/config-for.js +2 -3
- package/dist/host/config.d.ts +0 -11
- package/dist/host/create-host.d.ts +3 -40
- package/dist/host/create-host.js +18 -91
- package/dist/host/environment.d.ts +0 -3
- package/dist/host/environment.js +0 -9
- package/dist/host/gates.d.ts +4 -1
- package/dist/host/gates.js +9 -3
- package/dist/host/http.d.ts +1 -2
- package/dist/host/http.js +1 -7
- package/dist/host/index.d.ts +2 -2
- package/dist/host/index.js +2 -2
- package/dist/host/main.js +55 -40
- package/dist/host/operate.d.ts +29 -0
- package/dist/host/operate.js +223 -0
- package/dist/host/stack-config.d.ts +23 -20
- package/dist/host/stack-config.js +28 -44
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -1
- package/dist/infra/workers.d.ts +1 -1
- package/dist/keys/client.js +9 -1
- package/dist/keys/keys.d.ts +12 -2
- package/dist/keys/keys.js +10 -2
- package/dist/management-openapi.json +6491 -0
- package/dist/mcp/connect.d.ts +8 -4
- package/dist/mcp/connect.js +8 -27
- package/dist/mcp/pool.d.ts +6 -9
- package/dist/mcp/pool.js +8 -18
- package/dist/mcp/snapshot.d.ts +8 -2
- package/dist/mcp/snapshot.js +29 -2
- package/dist/media.d.ts +1 -1
- package/dist/media.js +1 -1
- package/dist/model/pi-model.js +3 -0
- package/dist/openapi.json +6228 -15615
- package/dist/reads/access.d.ts +4 -0
- package/dist/reads/access.js +12 -0
- package/dist/reads/cursor.d.ts +11 -0
- package/dist/reads/cursor.js +80 -0
- package/dist/reads/types.d.ts +25 -0
- package/dist/reads/types.js +1 -0
- package/dist/sandbox/manager.d.ts +16 -0
- package/dist/sandbox/manager.js +40 -0
- package/dist/sandbox/session-sandbox.js +13 -3
- package/dist/sandbox/skills.d.ts +13 -0
- package/dist/sandbox/skills.js +46 -0
- package/dist/sandbox/types.d.ts +2 -2
- package/dist/store/ownership.d.ts +9 -0
- package/dist/store/ownership.js +13 -0
- package/dist/store/postgres/connect.d.ts +2 -0
- package/dist/store/postgres/connect.js +29 -0
- package/dist/store/postgres/drizzle/0008_installation_vaults.sql +2 -0
- package/dist/store/postgres/drizzle/0009_oauth_pending.sql +19 -0
- package/dist/store/postgres/drizzle/0010_oss_auth_removals.sql +3 -0
- package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
- package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
- package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
- package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +49 -0
- package/dist/store/postgres/reads.d.ts +3 -0
- package/dist/store/postgres/reads.js +272 -0
- package/dist/store/postgres/schema.d.ts +423 -594
- package/dist/store/postgres/schema.js +84 -101
- package/dist/store/postgres/store.js +96 -248
- package/dist/store/postgres/tenant.d.ts +34 -2
- package/dist/store/postgres/tenant.js +16 -32
- package/dist/store/types.d.ts +44 -119
- package/dist/store/types.js +0 -1
- package/dist/streams/control.d.ts +0 -7
- package/dist/streams/control.js +0 -13
- package/dist/streams/types.d.ts +2 -20
- package/dist/streams/types.js +1 -1
- package/dist/tenant/advance.d.ts +3 -3
- package/dist/tenant/advance.js +10 -7
- package/dist/tenant/auth.d.ts +24 -15
- package/dist/tenant/auth.js +44 -55
- package/dist/tenant/cause.d.ts +3 -3
- package/dist/tenant/cause.js +1 -1
- package/dist/tenant/commands.d.ts +2 -18
- package/dist/tenant/commands.js +38 -179
- package/dist/tenant/context.d.ts +33 -45
- package/dist/tenant/definition-files.d.ts +24 -0
- package/dist/tenant/definition-files.js +103 -0
- package/dist/tenant/effects.d.ts +20 -18
- package/dist/tenant/effects.js +59 -133
- package/dist/tenant/ephemeral.d.ts +21 -8
- package/dist/tenant/ephemeral.js +11 -12
- package/dist/tenant/host-token.js +5 -5
- package/dist/tenant/identity-file.d.ts +75 -0
- package/dist/tenant/identity-file.js +202 -0
- package/dist/tenant/issuers.d.ts +48 -0
- package/dist/tenant/issuers.js +311 -0
- package/dist/tenant/jwt.js +3 -3
- package/dist/tenant/module.d.ts +1 -4
- package/dist/tenant/module.js +3 -12
- package/dist/tenant/operator-keys.d.ts +44 -0
- package/dist/tenant/operator-keys.js +87 -0
- package/dist/tenant/outbound.d.ts +40 -1
- package/dist/tenant/outbound.js +167 -18
- package/dist/tenant/paths.d.ts +1 -1
- package/dist/tenant/paths.js +1 -3
- package/dist/tenant/principals.d.ts +13 -13
- package/dist/tenant/principals.js +35 -36
- package/dist/tenant/run-token.js +6 -6
- package/dist/tenant/runtime.d.ts +5 -3
- package/dist/tenant/runtime.js +29 -15
- package/dist/tenant/sandboxes.d.ts +9 -6
- package/dist/tenant/sandboxes.js +16 -5
- package/dist/tenant/scheduler.d.ts +1 -5
- package/dist/tenant/scheduler.js +2 -5
- package/dist/tenant/session-streams.d.ts +8 -11
- package/dist/tenant/session-streams.js +48 -32
- package/dist/tenant/session.d.ts +8 -41
- package/dist/tenant/session.js +0 -37
- package/dist/tenant/sessions.d.ts +2 -2
- package/dist/tenant/sessions.js +17 -9
- package/dist/tenant/signing-keys.d.ts +3 -2
- package/dist/tenant/signing-keys.js +5 -5
- package/dist/tenant/skill-tool.d.ts +20 -0
- package/dist/tenant/skill-tool.js +72 -0
- package/dist/tenant/slim.d.ts +1 -1
- package/dist/tenant/slim.js +1 -1
- package/dist/tenant/status.js +7 -12
- package/dist/tenant/store-pg.d.ts +15 -0
- package/dist/tenant/store-pg.js +7 -3
- package/dist/tenant/streams.d.ts +3 -11
- package/dist/tenant/streams.js +2 -29
- package/dist/tenant/sweep.js +0 -5
- package/dist/tenant/transcript.d.ts +2 -2
- package/dist/tenant/transcript.js +2 -2
- package/dist/tenant/types.d.ts +33 -19
- package/dist/tenant/worker.d.ts +1 -3
- package/dist/tenant/worker.js +3 -8
- package/dist/vault/crypto.d.ts +7 -0
- package/dist/vault/crypto.js +16 -0
- package/dist/vault/oauth.d.ts +55 -0
- package/dist/vault/oauth.js +186 -0
- package/dist/vault/service.d.ts +54 -7
- package/dist/vault/service.js +261 -17
- package/dist/vault/sources.d.ts +60 -0
- package/dist/vault/sources.js +196 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +5 -4
- package/dist/admin-openapi.json +0 -604
- package/dist/api/http/routes/actions.d.ts +0 -8
- package/dist/api/http/routes/actions.js +0 -76
- package/dist/api/http/routes/endpoints.d.ts +0 -7
- package/dist/api/http/routes/endpoints.js +0 -100
- package/dist/host/admin-api.d.ts +0 -23
- package/dist/host/admin-api.js +0 -74
- package/dist/host/cors.d.ts +0 -27
- package/dist/host/cors.js +0 -64
- package/dist/mcp/stdio.d.ts +0 -17
- package/dist/mcp/stdio.js +0 -82
- package/dist/tenant/access-policy.d.ts +0 -30
- package/dist/tenant/access-policy.js +0 -52
- package/dist/tenant/actions.d.ts +0 -14
- package/dist/tenant/actions.js +0 -29
- package/dist/tenant/browser.d.ts +0 -15
- package/dist/tenant/browser.js +0 -39
- package/dist/tenant/delivery-token.d.ts +0 -41
- package/dist/tenant/delivery-token.js +0 -104
- package/dist/tenant/delivery.d.ts +0 -62
- package/dist/tenant/delivery.js +0 -481
- package/dist/tenant/endpoints.d.ts +0 -67
- package/dist/tenant/endpoints.js +0 -90
- package/dist/tenant/subject-limits.d.ts +0 -16
- package/dist/tenant/subject-limits.js +0 -45
- package/dist/tenant/tokens.d.ts +0 -14
- package/dist/tenant/tokens.js +0 -187
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Tenant's keys by name (F9 I1), managed through the Management API (`/v1/tenant/keys`,
|
|
3
|
+
* application keys only) and `nylorun-operate keys`. A key is a principal (F9-D11): its id is the key's name
|
|
4
|
+
* (`APPLICATION_KEY_ID_PATTERN`), the key is 64 hex characters (`mintBearerToken`), and the
|
|
5
|
+
* Tenant keeps only its SHA-256. Putting a key creates the principal or rotates its key;
|
|
6
|
+
* deleting it removes the row. Either way the old key stops authenticating on its next request,
|
|
7
|
+
* since authentication reads the principal on every request (`auth.ts`).
|
|
8
|
+
*
|
|
9
|
+
* `studio` is derived from the admin key (F9-D9): it is never put or deleted here. `bootstrap` is
|
|
10
|
+
* the management key a Host registers from `NYLORUN_MANAGEMENT_KEY_FILE`: its file is its only
|
|
11
|
+
* source. A key has a role (protocol 8): `application` reaches the Runtime API, `management` the
|
|
12
|
+
* Management API; a key keeps its role when it is rotated.
|
|
13
|
+
*/
|
|
14
|
+
import { APPLICATION_KEY_ID_PATTERN, BOOTSTRAP_KEY_ID, } from "@nylorun/core/compatibility";
|
|
15
|
+
import { hashToken, mintBearerToken } from "../core/bearer.js";
|
|
16
|
+
import { PrincipalRoleConflict } from "../store/ownership.js";
|
|
17
|
+
import { STUDIO_PRINCIPAL_ID } from "./principals.js";
|
|
18
|
+
/**
|
|
19
|
+
* The refusal for `id`, or undefined when it may be `put` or deleted. `bootstrap` is never put
|
|
20
|
+
* here (its file is its only source) but may be deleted on the machine (AP18).
|
|
21
|
+
*/
|
|
22
|
+
export function refuseOperatorKeyId(id, purpose = "put") {
|
|
23
|
+
if (!APPLICATION_KEY_ID_PATTERN.test(id))
|
|
24
|
+
return {
|
|
25
|
+
reason: "invalid",
|
|
26
|
+
message: `A key id must match ${APPLICATION_KEY_ID_PATTERN.source}`,
|
|
27
|
+
};
|
|
28
|
+
if (id === STUDIO_PRINCIPAL_ID)
|
|
29
|
+
return {
|
|
30
|
+
reason: "reserved",
|
|
31
|
+
message: "The studio key is derived from the admin key: it is not put or deleted here",
|
|
32
|
+
};
|
|
33
|
+
if (id === BOOTSTRAP_KEY_ID && purpose === "put")
|
|
34
|
+
return {
|
|
35
|
+
reason: "reserved",
|
|
36
|
+
message: "The bootstrap key comes from NYLORUN_MANAGEMENT_KEY_FILE: change that file to rotate it",
|
|
37
|
+
};
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
export function operatorKeys(store, now = () => new Date().toISOString()) {
|
|
41
|
+
return {
|
|
42
|
+
async list() {
|
|
43
|
+
const rows = await store.tx((t) => t.listPrincipals());
|
|
44
|
+
return rows.map(({ id, role, createdAt }) => ({ id, role, createdAt }));
|
|
45
|
+
},
|
|
46
|
+
async put(id, role = "application") {
|
|
47
|
+
const refused = refuseOperatorKeyId(id);
|
|
48
|
+
if (refused)
|
|
49
|
+
return refused;
|
|
50
|
+
const key = mintBearerToken();
|
|
51
|
+
try {
|
|
52
|
+
const { row, rotated } = await store.tx(async (t) => {
|
|
53
|
+
const existing = await t.principalById(id);
|
|
54
|
+
return {
|
|
55
|
+
row: await t.putPrincipal(id, hashToken(key), now(), role),
|
|
56
|
+
rotated: existing !== undefined,
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
return { id: row.id, role: row.role, createdAt: row.createdAt, key, rotated };
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof PrincipalRoleConflict)
|
|
63
|
+
return {
|
|
64
|
+
reason: "role",
|
|
65
|
+
message: `Key ${id} is not a${role === "application" ? "n application" : " management"} key: delete it first, or choose another name`,
|
|
66
|
+
};
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
async delete(id, role) {
|
|
71
|
+
const refused = refuseOperatorKeyId(id, "delete");
|
|
72
|
+
if (refused)
|
|
73
|
+
return refused;
|
|
74
|
+
return await store.tx(async (t) => {
|
|
75
|
+
if (role !== undefined) {
|
|
76
|
+
const existing = await t.principalById(id);
|
|
77
|
+
if (existing && existing.role !== role)
|
|
78
|
+
return {
|
|
79
|
+
reason: "role",
|
|
80
|
+
message: `Key ${id} is a ${existing.role} key: only the Tenant's machine manages it (nylorun-operate)`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return await t.deletePrincipal(id);
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -36,9 +36,48 @@ export declare const MAX_RESPONSE_BYTES: number;
|
|
|
36
36
|
export declare function isPrivateAddress(address: string): boolean;
|
|
37
37
|
/** Why the Host refuses `url`, or `undefined` when it may be called. */
|
|
38
38
|
export declare function refusal(url: URL, policy: OutboundPolicy): string | undefined;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* POSTs (or, with `method`, PUTs or PATCHes) the JSON `body` to `url` under `policy`. Never
|
|
41
|
+
* throws; `signal` aborts the request.
|
|
42
|
+
*/
|
|
40
43
|
export declare function post(target: string, body: string, headers: Record<string, string>, options: {
|
|
41
44
|
policy: OutboundPolicy;
|
|
42
45
|
signal: AbortSignal;
|
|
43
46
|
maxResponseBytes?: number;
|
|
47
|
+
method?: "POST" | "PUT" | "PATCH";
|
|
44
48
|
}): Promise<OutboundResult>;
|
|
49
|
+
/**
|
|
50
|
+
* A refusal of `guardedFetch`: the Host's policy forbids the URL or every address it resolves
|
|
51
|
+
* to. Not a `TypeError`, so callers that read a `TypeError` as a CORS failure (the MCP SDK's
|
|
52
|
+
* discovery) see the refusal instead of trying again.
|
|
53
|
+
*/
|
|
54
|
+
export declare class OutboundRefused extends Error {
|
|
55
|
+
readonly code = "ENDPOINT_ADDRESS_REFUSED";
|
|
56
|
+
constructor(message: string);
|
|
57
|
+
}
|
|
58
|
+
/** A request `guardedFetch` sent that failed: refused connection, reset, timeout, redirect. */
|
|
59
|
+
export declare class OutboundFailed extends Error {
|
|
60
|
+
readonly code: string;
|
|
61
|
+
constructor(message: string, code: string);
|
|
62
|
+
}
|
|
63
|
+
/** The largest answer `guardedFetch` reads. OAuth metadata and tokens are small. */
|
|
64
|
+
export declare const MAX_FETCH_RESPONSE_BYTES: number;
|
|
65
|
+
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
66
|
+
export declare const FETCH_TIMEOUT_MS = 30000;
|
|
67
|
+
/**
|
|
68
|
+
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
69
|
+
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
70
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), a bounded answer and a 30 s timeout
|
|
71
|
+
* unless the caller passes a signal. The gateway calls OAuth discovery, registration, the code
|
|
72
|
+
* exchange and refresh with it. A refused URL or address rejects with `OutboundRefused`, any
|
|
73
|
+
* other failure with `OutboundFailed`.
|
|
74
|
+
*
|
|
75
|
+
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
76
|
+
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
77
|
+
* stay open (`mcp/connect.ts`).
|
|
78
|
+
*/
|
|
79
|
+
export declare function guardedFetch(policy: OutboundPolicy, options?: {
|
|
80
|
+
maxResponseBytes?: number;
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
stream?: boolean;
|
|
83
|
+
}): typeof fetch;
|
package/dist/tenant/outbound.js
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The Runtime's requests to
|
|
3
|
-
*
|
|
4
|
-
* connected to, so a DNS answer cannot
|
|
2
|
+
* The Runtime's requests to developer URLs: HTTP tools (`gates/http-tool.ts`) and, through
|
|
3
|
+
* `guardedFetch`, remote MCP servers and OAuth: one request, no redirects, a bounded answer, and
|
|
4
|
+
* the Host's address policy checked on the address actually connected to, so a DNS answer cannot
|
|
5
|
+
* steer a request somewhere the Host forbids.
|
|
5
6
|
*
|
|
6
|
-
* The result says whether anything reached the
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* The result says whether anything reached the URL. A request that failed before its body was
|
|
8
|
+
* flushed (refused connection, unknown host, TLS failure, a refused address) was `not_sent`;
|
|
9
|
+
* one that failed after (reset, timeout) is `lost`.
|
|
9
10
|
*/
|
|
10
11
|
import { lookup as dnsLookup } from "node:dns";
|
|
11
12
|
import { request as httpRequest } from "node:http";
|
|
12
13
|
import { request as httpsRequest } from "node:https";
|
|
13
14
|
import { BlockList, isIP } from "node:net";
|
|
15
|
+
import { Readable } from "node:stream";
|
|
14
16
|
/** The largest answer read from an endpoint. */
|
|
15
17
|
export const MAX_RESPONSE_BYTES = 8 * 1024 * 1024;
|
|
16
18
|
const PRIVATE = new BlockList();
|
|
@@ -53,18 +55,14 @@ export function refusal(url, policy) {
|
|
|
53
55
|
return "This Runtime does not call private addresses; expose the endpoint through a public URL (a tunnel, for a local app)";
|
|
54
56
|
return undefined;
|
|
55
57
|
}
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
const { policy, signal } = options;
|
|
59
|
-
const limit = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
60
|
-
const url = new URL(target);
|
|
61
|
-
const refused = refusal(url, policy);
|
|
62
|
-
if (refused)
|
|
63
|
-
return { kind: "not_sent", code: "ENDPOINT_ADDRESS_REFUSED", message: refused };
|
|
64
|
-
const originalHost = url.host;
|
|
58
|
+
/** `localhost` means the machine that runs Docker (`OutboundPolicy.loopback`). */
|
|
59
|
+
function dockerHost(url, policy) {
|
|
65
60
|
if (policy.loopback === "docker-host" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname))
|
|
66
61
|
url.hostname = "host.docker.internal";
|
|
67
|
-
|
|
62
|
+
}
|
|
63
|
+
/** A DNS lookup that drops private answers when `policy` refuses them: checked on what is connected to. */
|
|
64
|
+
function guardedLookup(policy) {
|
|
65
|
+
return (hostname, lookupOptions, callback) => dnsLookup(hostname, { ...lookupOptions, all: true }, (error, addresses) => {
|
|
68
66
|
if (error)
|
|
69
67
|
return callback(error, []);
|
|
70
68
|
const allowed = policy.privateAddresses === "refuse"
|
|
@@ -76,6 +74,21 @@ export async function post(target, body, headers, options) {
|
|
|
76
74
|
return callback(null, allowed);
|
|
77
75
|
return callback(null, allowed[0].address, allowed[0].family);
|
|
78
76
|
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* POSTs (or, with `method`, PUTs or PATCHes) the JSON `body` to `url` under `policy`. Never
|
|
80
|
+
* throws; `signal` aborts the request.
|
|
81
|
+
*/
|
|
82
|
+
export async function post(target, body, headers, options) {
|
|
83
|
+
const { policy, signal } = options;
|
|
84
|
+
const limit = options.maxResponseBytes ?? MAX_RESPONSE_BYTES;
|
|
85
|
+
const url = new URL(target);
|
|
86
|
+
const refused = refusal(url, policy);
|
|
87
|
+
if (refused)
|
|
88
|
+
return { kind: "not_sent", code: "ENDPOINT_ADDRESS_REFUSED", message: refused };
|
|
89
|
+
const originalHost = url.host;
|
|
90
|
+
dockerHost(url, policy);
|
|
91
|
+
const lookup = guardedLookup(policy);
|
|
79
92
|
return new Promise((resolve) => {
|
|
80
93
|
let sent = false;
|
|
81
94
|
let settled = false;
|
|
@@ -88,10 +101,10 @@ export async function post(target, body, headers, options) {
|
|
|
88
101
|
const failed = (error) => finish({
|
|
89
102
|
kind: sent ? "lost" : "not_sent",
|
|
90
103
|
code: signal.aborted ? "ABORTED" : (error.code ?? error.name),
|
|
91
|
-
message: signal.aborted ? "The
|
|
104
|
+
message: signal.aborted ? "The request was aborted" : error.message,
|
|
92
105
|
});
|
|
93
106
|
const request = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, {
|
|
94
|
-
method: "POST",
|
|
107
|
+
method: options.method ?? "POST",
|
|
95
108
|
headers: {
|
|
96
109
|
...headers,
|
|
97
110
|
host: originalHost,
|
|
@@ -125,3 +138,139 @@ export async function post(target, body, headers, options) {
|
|
|
125
138
|
request.end(body);
|
|
126
139
|
});
|
|
127
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* A refusal of `guardedFetch`: the Host's policy forbids the URL or every address it resolves
|
|
143
|
+
* to. Not a `TypeError`, so callers that read a `TypeError` as a CORS failure (the MCP SDK's
|
|
144
|
+
* discovery) see the refusal instead of trying again.
|
|
145
|
+
*/
|
|
146
|
+
export class OutboundRefused extends Error {
|
|
147
|
+
code = "ENDPOINT_ADDRESS_REFUSED";
|
|
148
|
+
constructor(message) {
|
|
149
|
+
super(message);
|
|
150
|
+
this.name = "OutboundRefused";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** A request `guardedFetch` sent that failed: refused connection, reset, timeout, redirect. */
|
|
154
|
+
export class OutboundFailed extends Error {
|
|
155
|
+
code;
|
|
156
|
+
constructor(message, code) {
|
|
157
|
+
super(message);
|
|
158
|
+
this.code = code;
|
|
159
|
+
this.name = "OutboundFailed";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** The largest answer `guardedFetch` reads. OAuth metadata and tokens are small. */
|
|
163
|
+
export const MAX_FETCH_RESPONSE_BYTES = 1024 * 1024;
|
|
164
|
+
/** How long a `guardedFetch` request may take without a signal of its own. */
|
|
165
|
+
export const FETCH_TIMEOUT_MS = 30_000;
|
|
166
|
+
/**
|
|
167
|
+
* A `fetch` under the Host's address policy (F9 C2): the literal-IP and DNS checks of `post`
|
|
168
|
+
* on the address actually connected to, `localhost` rewritten for the local stack, no
|
|
169
|
+
* redirects (`redirect: "error"`; a 3xx answer rejects), a bounded answer and a 30 s timeout
|
|
170
|
+
* unless the caller passes a signal. The gateway calls OAuth discovery, registration, the code
|
|
171
|
+
* exchange and refresh with it. A refused URL or address rejects with `OutboundRefused`, any
|
|
172
|
+
* other failure with `OutboundFailed`.
|
|
173
|
+
*
|
|
174
|
+
* `stream`: the Response resolves once the headers arrive and its body streams, unbounded and
|
|
175
|
+
* with no timeout but the caller's signal: remote MCP servers, whose answers and event streams
|
|
176
|
+
* stay open (`mcp/connect.ts`).
|
|
177
|
+
*/
|
|
178
|
+
export function guardedFetch(policy, options = {}) {
|
|
179
|
+
const limit = options.maxResponseBytes ?? MAX_FETCH_RESPONSE_BYTES;
|
|
180
|
+
const timeoutMs = options.timeoutMs ?? FETCH_TIMEOUT_MS;
|
|
181
|
+
const stream = options.stream === true;
|
|
182
|
+
const lookup = guardedLookup(policy);
|
|
183
|
+
const guarded = async (input, init = {}) => {
|
|
184
|
+
const request = input instanceof Request ? input : undefined;
|
|
185
|
+
const url = new URL(request ? request.url : String(input));
|
|
186
|
+
const refused = refusal(url, policy);
|
|
187
|
+
if (refused)
|
|
188
|
+
throw new OutboundRefused(refused);
|
|
189
|
+
const method = (init.method ?? request?.method ?? "GET").toUpperCase();
|
|
190
|
+
const headers = new Headers(request?.headers);
|
|
191
|
+
for (const [name, value] of new Headers(init.headers))
|
|
192
|
+
headers.set(name, value);
|
|
193
|
+
let body;
|
|
194
|
+
const source = init.body ?? (request && method !== "GET" && method !== "HEAD" ? await request.arrayBuffer() : undefined);
|
|
195
|
+
if (source !== undefined && source !== null) {
|
|
196
|
+
if (typeof source === "string")
|
|
197
|
+
body = Buffer.from(source, "utf8");
|
|
198
|
+
else if (source instanceof URLSearchParams) {
|
|
199
|
+
body = Buffer.from(source.toString(), "utf8");
|
|
200
|
+
if (!headers.has("content-type"))
|
|
201
|
+
headers.set("content-type", "application/x-www-form-urlencoded;charset=UTF-8");
|
|
202
|
+
}
|
|
203
|
+
else if (source instanceof ArrayBuffer)
|
|
204
|
+
body = Buffer.from(source);
|
|
205
|
+
else if (ArrayBuffer.isView(source))
|
|
206
|
+
body = Buffer.from(source.buffer, source.byteOffset, source.byteLength);
|
|
207
|
+
else
|
|
208
|
+
throw new OutboundFailed("guardedFetch sends only string, form or byte bodies", "UNSUPPORTED_BODY");
|
|
209
|
+
}
|
|
210
|
+
const originalHost = url.host;
|
|
211
|
+
dockerHost(url, policy);
|
|
212
|
+
const signal = init.signal ?? request?.signal ?? (stream ? undefined : AbortSignal.timeout(timeoutMs));
|
|
213
|
+
const outgoing = { host: originalHost };
|
|
214
|
+
for (const [name, value] of headers)
|
|
215
|
+
outgoing[name] = value;
|
|
216
|
+
if (body)
|
|
217
|
+
outgoing["content-length"] = String(body.byteLength);
|
|
218
|
+
return await new Promise((resolve, reject) => {
|
|
219
|
+
let settled = false;
|
|
220
|
+
const settle = (fn) => {
|
|
221
|
+
if (settled)
|
|
222
|
+
return;
|
|
223
|
+
settled = true;
|
|
224
|
+
fn();
|
|
225
|
+
};
|
|
226
|
+
const failed = (error) => settle(() => reject(error instanceof RefusedAddress
|
|
227
|
+
? new OutboundRefused(error.message)
|
|
228
|
+
: new OutboundFailed(signal?.aborted ? `The request to ${originalHost} was aborted or timed out` : error.message, signal?.aborted ? "ABORTED" : (error.code ?? error.name))));
|
|
229
|
+
const sent = (url.protocol === "https:" ? httpsRequest : httpRequest)(url, { method, headers: outgoing, lookup: lookup, ...(signal ? { signal } : {}) }, (response) => {
|
|
230
|
+
const status = response.statusCode ?? 0;
|
|
231
|
+
if (status >= 300 && status < 400 && response.headers.location !== undefined) {
|
|
232
|
+
response.resume();
|
|
233
|
+
return settle(() => reject(new OutboundFailed(`${originalHost} answered a redirect, which this Runtime does not follow`, "REDIRECT")));
|
|
234
|
+
}
|
|
235
|
+
const noBody = status === 204 || status === 304 || method === "HEAD";
|
|
236
|
+
const answer = (body) => {
|
|
237
|
+
const headers = new Headers();
|
|
238
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
239
|
+
if (value === undefined)
|
|
240
|
+
continue;
|
|
241
|
+
for (const item of Array.isArray(value) ? value : [value])
|
|
242
|
+
headers.append(name, item);
|
|
243
|
+
}
|
|
244
|
+
return new Response(noBody ? null : body, {
|
|
245
|
+
status,
|
|
246
|
+
statusText: response.statusMessage ?? "",
|
|
247
|
+
headers,
|
|
248
|
+
});
|
|
249
|
+
};
|
|
250
|
+
if (stream) {
|
|
251
|
+
// A failure after this reaches the reader as the body's error.
|
|
252
|
+
if (noBody)
|
|
253
|
+
response.resume();
|
|
254
|
+
return settle(() => resolve(answer(noBody ? null : Readable.toWeb(response))));
|
|
255
|
+
}
|
|
256
|
+
const chunks = [];
|
|
257
|
+
let size = 0;
|
|
258
|
+
response.on("data", (chunk) => {
|
|
259
|
+
size += chunk.length;
|
|
260
|
+
if (size > limit) {
|
|
261
|
+
settle(() => reject(new OutboundFailed(`The answer from ${originalHost} is larger than ${limit} bytes`, "TOO_LARGE")));
|
|
262
|
+
response.destroy();
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
chunks.push(chunk);
|
|
266
|
+
});
|
|
267
|
+
response.on("end", () => settle(() => resolve(answer(Buffer.concat(chunks)))));
|
|
268
|
+
response.on("error", failed);
|
|
269
|
+
response.on("aborted", () => failed(Object.assign(new Error("The answer was cut off"), { code: "ECONNRESET" })));
|
|
270
|
+
});
|
|
271
|
+
sent.on("error", failed);
|
|
272
|
+
sent.end(body);
|
|
273
|
+
});
|
|
274
|
+
};
|
|
275
|
+
return guarded;
|
|
276
|
+
}
|
package/dist/tenant/paths.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare function hostPaths(hostRoot: string): HostPaths;
|
|
|
16
16
|
* root after realpath.
|
|
17
17
|
*
|
|
18
18
|
* The directory holds what stays on the Host (the Tenant's data is its database): `home/`,
|
|
19
|
-
* `
|
|
19
|
+
* `sandboxes/`, `blobs/` (the `fs` BlobStore, when the Host has no
|
|
20
20
|
* Object store), `logs/tenant.log`. The vault key is the one exception:
|
|
21
21
|
* it lives beside the Tenant directory, in `<hostRoot>/keys/vault-kek`, which the local stack
|
|
22
22
|
* mounts only into the gateway (F4.2), never into the runtime container.
|
package/dist/tenant/paths.js
CHANGED
|
@@ -24,7 +24,7 @@ export function hostPaths(hostRoot) {
|
|
|
24
24
|
* root after realpath.
|
|
25
25
|
*
|
|
26
26
|
* The directory holds what stays on the Host (the Tenant's data is its database): `home/`,
|
|
27
|
-
* `
|
|
27
|
+
* `sandboxes/`, `blobs/` (the `fs` BlobStore, when the Host has no
|
|
28
28
|
* Object store), `logs/tenant.log`. The vault key is the one exception:
|
|
29
29
|
* it lives beside the Tenant directory, in `<hostRoot>/keys/vault-kek`, which the local stack
|
|
30
30
|
* mounts only into the gateway (F4.2), never into the runtime container.
|
|
@@ -39,9 +39,7 @@ export function tenantPaths(hostRoot) {
|
|
|
39
39
|
root,
|
|
40
40
|
kek: join(host, "keys", "vault-kek"),
|
|
41
41
|
home: join(root, "home"),
|
|
42
|
-
tmp: join(root, "tmp"),
|
|
43
42
|
sandboxes: join(root, "sandboxes"),
|
|
44
|
-
pluginData: join(root, "plugin-data"),
|
|
45
43
|
blobs: join(root, "blobs"),
|
|
46
44
|
logs: join(root, "logs"),
|
|
47
45
|
log: join(root, "logs", "tenant.log"),
|
|
@@ -2,27 +2,27 @@ import type { InitialPrincipal } from "../store/postgres/tenant.js";
|
|
|
2
2
|
export type { PrincipalRow } from "../store/types.js";
|
|
3
3
|
/** Principal id of the Studio key derived from the admin key. */
|
|
4
4
|
export declare const STUDIO_PRINCIPAL_ID = "studio";
|
|
5
|
-
/** The derived principal Projects use (`PROJECT_PRINCIPAL_ID` in `@nylorun/admin`). */
|
|
6
|
-
export declare const PROJECT_PRINCIPAL_ID = "project";
|
|
7
|
-
/** The derived principals a Host registers when not told otherwise. */
|
|
8
|
-
export declare const DEFAULT_DERIVED_PRINCIPALS: readonly string[];
|
|
9
|
-
/** The Studio key of `tenantId`: HMAC-SHA256 of the admin key. */
|
|
10
|
-
export declare function deriveStudioKey(adminKey: string, tenantId: string): string;
|
|
11
|
-
/** The key of derived principal `principalId` on `tenantId`: HMAC-SHA256 of the admin key. */
|
|
12
|
-
export declare function deriveTenantKey(adminKey: string, tenantId: string, principalId: string): string;
|
|
13
|
-
/** Whether `id` may name a derived principal: the client's name, never `studio`. */
|
|
14
|
-
export declare function isDerivedPrincipalId(id: string): boolean;
|
|
15
5
|
/**
|
|
16
|
-
* The
|
|
17
|
-
*
|
|
6
|
+
* The Studio key: HMAC-SHA256 of the admin key over `nylorun/studio/v2`. It names no Tenant
|
|
7
|
+
* (protocol 8): Studio derives it before it can learn the Tenant's id, and the admin key is
|
|
8
|
+
* already one per installation.
|
|
9
|
+
*/
|
|
10
|
+
export declare function deriveStudioKey(adminKey: string): string;
|
|
11
|
+
/**
|
|
12
|
+
* The principals a Host's Tenant is created with: `studio`, and an application principal when
|
|
13
|
+
* a key is given (the ephemeral Runtime). Keys are hashed here.
|
|
18
14
|
*/
|
|
19
15
|
export declare function hostPrincipals(options: {
|
|
20
16
|
adminKey: string;
|
|
21
|
-
derived?: readonly string[];
|
|
22
17
|
application?: {
|
|
23
18
|
principalId: string;
|
|
24
19
|
key: string;
|
|
25
20
|
};
|
|
26
21
|
/** Registers `studio` with this hash instead of the admin key's derivation. */
|
|
27
22
|
studioCredentialHash?: string;
|
|
23
|
+
/**
|
|
24
|
+
* The management key of `NYLORUN_MANAGEMENT_KEY_FILE`: registered as `bootstrap`, and
|
|
25
|
+
* replaced at the next start when the file changed.
|
|
26
|
+
*/
|
|
27
|
+
bootstrapKey?: string;
|
|
28
28
|
}): (tenantId: string) => InitialPrincipal[];
|
|
@@ -1,44 +1,43 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The principals the Host gives its Tenant when it creates it (tenancy.md §4
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* only their hashes.
|
|
2
|
+
* The principals the Host gives its Tenant when it creates it (tenancy.md §4): the Studio
|
|
3
|
+
* principal, whose key the admin key derives, so Studio (and an app embedding it) stores none,
|
|
4
|
+
* and, for the ephemeral Runtime, one application principal with a key of its own. The Tenant
|
|
5
|
+
* keeps only their hashes. Every other key is put by name (`PUT /v1/tenant/keys/{keyId}`,
|
|
6
|
+
* `nylorun-operate keys`, `operator-keys.ts`); no other key is derived.
|
|
6
7
|
*
|
|
7
|
-
* The
|
|
8
|
-
* (`admin/src/derived-credentials.ts`: `deriveStudioToken
|
|
9
|
-
*
|
|
8
|
+
* The derivation is the one `@nylorun/admin` computes on the client side
|
|
9
|
+
* (`admin/src/derived-credentials.ts`: `deriveStudioToken`); both sides must stay byte for byte
|
|
10
|
+
* the same.
|
|
10
11
|
*/
|
|
11
12
|
import { createHmac } from "node:crypto";
|
|
12
|
-
import {
|
|
13
|
+
import { BOOTSTRAP_KEY_ID } from "@nylorun/core/compatibility";
|
|
13
14
|
import { hashToken } from "../core/bearer.js";
|
|
14
15
|
/** Principal id of the Studio key derived from the admin key. */
|
|
15
16
|
export const STUDIO_PRINCIPAL_ID = "studio";
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export function deriveStudioKey(adminKey
|
|
22
|
-
return hmac(adminKey, ["nylorun/studio/
|
|
23
|
-
}
|
|
24
|
-
/** The key of derived principal `principalId` on `tenantId`: HMAC-SHA256 of the admin key. */
|
|
25
|
-
export function deriveTenantKey(adminKey, tenantId, principalId) {
|
|
26
|
-
return hmac(adminKey, ["nylorun/principal/v1", principalId, tenantId]);
|
|
27
|
-
}
|
|
28
|
-
/** Whether `id` may name a derived principal: the client's name, never `studio`. */
|
|
29
|
-
export function isDerivedPrincipalId(id) {
|
|
30
|
-
return DERIVED_PRINCIPAL_ID_PATTERN.test(id) && id !== STUDIO_PRINCIPAL_ID;
|
|
17
|
+
/**
|
|
18
|
+
* The Studio key: HMAC-SHA256 of the admin key over `nylorun/studio/v2`. It names no Tenant
|
|
19
|
+
* (protocol 8): Studio derives it before it can learn the Tenant's id, and the admin key is
|
|
20
|
+
* already one per installation.
|
|
21
|
+
*/
|
|
22
|
+
export function deriveStudioKey(adminKey) {
|
|
23
|
+
return hmac(adminKey, ["nylorun/studio/v2"]);
|
|
31
24
|
}
|
|
32
25
|
/**
|
|
33
|
-
* The principals a Host's Tenant is created with: `studio`,
|
|
34
|
-
*
|
|
26
|
+
* The principals a Host's Tenant is created with: `studio`, and an application principal when
|
|
27
|
+
* a key is given (the ephemeral Runtime). Keys are hashed here.
|
|
35
28
|
*/
|
|
36
29
|
export function hostPrincipals(options) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
30
|
+
return () => [
|
|
31
|
+
...(options.bootstrapKey
|
|
32
|
+
? [
|
|
33
|
+
{
|
|
34
|
+
id: BOOTSTRAP_KEY_ID,
|
|
35
|
+
role: "management",
|
|
36
|
+
credentialHash: hashToken(options.bootstrapKey),
|
|
37
|
+
replace: true,
|
|
38
|
+
},
|
|
39
|
+
]
|
|
40
|
+
: []),
|
|
42
41
|
...(options.application
|
|
43
42
|
? [
|
|
44
43
|
{
|
|
@@ -49,13 +48,13 @@ export function hostPrincipals(options) {
|
|
|
49
48
|
: []),
|
|
50
49
|
{
|
|
51
50
|
id: STUDIO_PRINCIPAL_ID,
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
// Studio runs sessions and edits the Tenant's settings: both APIs (protocol 8).
|
|
52
|
+
role: "studio",
|
|
53
|
+
credentialHash: options.studioCredentialHash ?? hashToken(deriveStudioKey(options.adminKey)),
|
|
54
|
+
// Its key derives from the admin key: a database holding an older derivation (v1, with
|
|
55
|
+
// the Tenant id) takes this one at the next start.
|
|
56
|
+
replace: true,
|
|
54
57
|
},
|
|
55
|
-
...[...new Set(derived)].map((id) => ({
|
|
56
|
-
id,
|
|
57
|
-
credentialHash: hashToken(deriveTenantKey(options.adminKey, tenantId, id)),
|
|
58
|
-
})),
|
|
59
58
|
];
|
|
60
59
|
}
|
|
61
60
|
/** HMAC-SHA256 (hex) of `parts` joined by NUL bytes, keyed with the admin key. */
|
package/dist/tenant/run-token.js
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* remote MCP calls. The gates take session, turn and agent from it, never from the body, and
|
|
5
5
|
* check on every call that the lease it names is still the session's (`staleRun`).
|
|
6
6
|
*
|
|
7
|
-
* Signed by the keys service with the Tenant's current signing key, like
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Signed by the keys service with the Tenant's current signing key, like capability links,
|
|
8
|
+
* and told apart from them by `typ` and `aud`. Internal: not in `openapi.json`. A token lives
|
|
9
|
+
* `RUN_TOKEN_TTL_SECONDS`, no longer than `TOKEN_TTL_MAX_SECONDS`, so a key rotation never
|
|
10
10
|
* revokes a live one (`signing-keys.ts`).
|
|
11
11
|
*
|
|
12
12
|
* ```text
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
*/
|
|
18
18
|
import { randomUUID } from "node:crypto";
|
|
19
19
|
import { errors, importJWK, jwtVerify } from "jose";
|
|
20
|
-
import {
|
|
20
|
+
import { tenantTokenIssuer } from "@nylorun/core/contracts";
|
|
21
21
|
import { CLOCK_TOLERANCE_SECONDS, tokenKeyId } from "./jwt.js";
|
|
22
22
|
export const RUN_TOKEN_TYP = "nylorun-run+jwt";
|
|
23
23
|
export const RUN_TOKEN_AUD = "nylorun-gates";
|
|
@@ -42,7 +42,7 @@ export async function mintRunToken(signer, lease, session) {
|
|
|
42
42
|
const { token } = await signer.keys.sign({
|
|
43
43
|
typ: RUN_TOKEN_TYP,
|
|
44
44
|
claims: {
|
|
45
|
-
iss:
|
|
45
|
+
iss: tenantTokenIssuer(tenantId),
|
|
46
46
|
aud: RUN_TOKEN_AUD,
|
|
47
47
|
sub: lease.sessionId,
|
|
48
48
|
trn: turnId,
|
|
@@ -89,7 +89,7 @@ export async function verifyRunToken(store, tenantId, raw, cache = new Map()) {
|
|
|
89
89
|
try {
|
|
90
90
|
const verified = await jwtVerify(raw, key, {
|
|
91
91
|
algorithms: ["ES256"],
|
|
92
|
-
issuer:
|
|
92
|
+
issuer: tenantTokenIssuer(tenantId),
|
|
93
93
|
audience: RUN_TOKEN_AUD,
|
|
94
94
|
typ: RUN_TOKEN_TYP,
|
|
95
95
|
clockTolerance: CLOCK_TOLERANCE_SECONDS,
|
package/dist/tenant/runtime.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TenantEnvelope } from "@nylorun/core/contracts";
|
|
1
|
+
import type { HarnessStatus, TenantEnvelope } from "@nylorun/core/contracts";
|
|
2
2
|
import { type ModelProvider } from "../core/provider.js";
|
|
3
3
|
import type { ModelGate } from "../gates/model-gate.js";
|
|
4
4
|
import { type AuthorizeResult } from "../vault/service.js";
|
|
@@ -23,8 +23,8 @@ export type TenantOpenHooks = {
|
|
|
23
23
|
*/
|
|
24
24
|
modelGate?: ModelGate;
|
|
25
25
|
/**
|
|
26
|
-
* Serves the Tenant's remote MCP servers and
|
|
27
|
-
* Without one, the Tenant opens
|
|
26
|
+
* Serves the Tenant's remote MCP servers and HTTP tools (the gates service's client).
|
|
27
|
+
* Without one, the Tenant opens and calls them in this process.
|
|
28
28
|
*/
|
|
29
29
|
toolGate?: ToolGate;
|
|
30
30
|
/**
|
|
@@ -102,6 +102,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
102
102
|
authorize(sessionId: string, request: {
|
|
103
103
|
url: string;
|
|
104
104
|
serverName?: string;
|
|
105
|
+
agentId?: string;
|
|
105
106
|
}): Promise<AuthorizeResult>;
|
|
106
107
|
summary(): Promise<TenantSummary>;
|
|
107
108
|
drain(activeWork: "drain" | "cancel", timeoutMs?: number): Promise<void>;
|
|
@@ -118,6 +119,7 @@ export declare class TenantRuntime implements TenantHandle {
|
|
|
118
119
|
/** Adds a callback to the Tenant sweep. Returns a function that removes it. */
|
|
119
120
|
onSweep(hook: () => Promise<void>): () => void;
|
|
120
121
|
attachHarness(channel: HarnessChannel, peer: HarnessPeer): () => void;
|
|
122
|
+
harnessStatus(): HarnessStatus;
|
|
121
123
|
close(): Promise<void>;
|
|
122
124
|
}
|
|
123
125
|
/**
|