@nylorun/runtime 0.25.0-beta → 0.27.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 +169 -0
- package/README.md +26 -16
- package/dist/adapters/execution/restate.d.ts +15 -18
- package/dist/adapters/execution/restate.js +31 -27
- package/dist/adapters/replication/pgoutput.d.ts +1 -1
- package/dist/adapters/replication/pgoutput.js +3 -2
- package/dist/adapters/sandbox/local.js +8 -5
- package/dist/adapters/sandbox/virtual.js +2 -2
- package/dist/adapters/streams/s2.d.ts +1 -1
- package/dist/adapters/streams/s2.js +18 -14
- package/dist/api/a2a/card.js +1 -1
- package/dist/api/a2a/endpoint.js +3 -4
- package/dist/api/a2a/protocol.js +4 -0
- package/dist/api/a2a/routes.d.ts +2 -6
- package/dist/api/a2a/routes.js +12 -18
- package/dist/api/ag-ui/endpoint.js +2 -3
- package/dist/api/ag-ui/routes.js +4 -4
- package/dist/api/components.d.ts +326 -45
- package/dist/api/components.js +5 -1
- package/dist/api/gate/keys.js +7 -4
- package/dist/api/gate/routes.d.ts +6 -2
- package/dist/api/gate/routes.js +15 -11
- package/dist/api/http/app.d.ts +2 -2
- package/dist/api/http/app.js +2 -2
- package/dist/api/http/body.d.ts +15 -3
- package/dist/api/http/body.js +15 -7
- package/dist/api/http/define.d.ts +1 -1
- package/dist/api/http/respond.js +7 -3
- package/dist/api/http/routes/access.js +5 -4
- package/dist/api/http/routes/artifacts.js +19 -7
- package/dist/api/http/routes/coverage.js +1 -1
- package/dist/api/http/routes/environment-manifests.js +1 -1
- package/dist/api/http/routes/environments.js +6 -6
- package/dist/api/http/routes/files.d.ts +2 -1
- package/dist/api/http/routes/files.js +31 -5
- package/dist/api/http/routes/mcp.js +1 -1
- package/dist/api/http/routes/reads.d.ts +35 -0
- package/dist/api/http/routes/reads.js +50 -22
- package/dist/api/http/routes/sessions.js +12 -11
- package/dist/api/http/routes/tenant.d.ts +2 -2
- package/dist/api/http/routes/tenant.js +69 -16
- package/dist/api/inbound-files.d.ts +3 -9
- package/dist/api/inbound-files.js +18 -24
- package/dist/api/openapi.js +6 -2
- package/dist/artifacts/disk.d.ts +0 -4
- package/dist/artifacts/disk.js +77 -73
- package/dist/artifacts/files.d.ts +13 -4
- package/dist/artifacts/files.js +27 -9
- package/dist/artifacts/folder-upload.d.ts +75 -0
- package/dist/artifacts/folder-upload.js +263 -0
- package/dist/artifacts/folders.d.ts +14 -8
- package/dist/artifacts/folders.js +19 -11
- package/dist/artifacts/links.js +7 -6
- package/dist/artifacts/parts.d.ts +9 -14
- package/dist/artifacts/parts.js +11 -9
- package/dist/artifacts/pipe.d.ts +15 -0
- package/dist/artifacts/pipe.js +71 -0
- package/dist/artifacts/quota.d.ts +36 -0
- package/dist/artifacts/quota.js +44 -0
- package/dist/artifacts/service.d.ts +25 -1
- package/dist/artifacts/service.js +70 -25
- package/dist/blob/fs.js +2 -1
- package/dist/blob/s3.d.ts +6 -2
- package/dist/blob/s3.js +9 -7
- package/dist/blob/types.d.ts +1 -1
- package/dist/blob/types.js +1 -1
- package/dist/core/limits.d.ts +11 -35
- package/dist/core/limits.js +5 -73
- package/dist/core/sandbox-routes.js +0 -1
- package/dist/core/turn-manifest.d.ts +1 -2
- package/dist/core/turn-manifest.js +4 -28
- package/dist/execution/memory.d.ts +8 -5
- package/dist/execution/memory.js +8 -6
- package/dist/execution/types.d.ts +1 -6
- package/dist/execution/types.js +0 -5
- package/dist/gates/contract.d.ts +3 -2
- package/dist/gates/contract.js +1 -1
- package/dist/gates/egress-credentials.js +4 -3
- package/dist/gates/egress.d.ts +9 -9
- package/dist/gates/egress.js +19 -19
- package/dist/gates/fixture-model.js +13 -2
- package/dist/gates/handler.d.ts +1 -3
- package/dist/gates/handler.js +4 -3
- package/dist/gates/http-client.d.ts +4 -3
- package/dist/gates/http-client.js +5 -7
- package/dist/gates/http-tool.d.ts +24 -4
- package/dist/gates/http-tool.js +53 -17
- package/dist/gates/in-process.d.ts +2 -1
- package/dist/gates/in-process.js +17 -9
- package/dist/gates/inflight.d.ts +2 -2
- package/dist/gates/inflight.js +3 -2
- package/dist/gates/journal.js +5 -7
- package/dist/gates/mcp-handler.d.ts +4 -3
- package/dist/gates/mcp-handler.js +15 -5
- package/dist/gates/model-gate.d.ts +11 -2
- package/dist/gates/policy.d.ts +28 -5
- package/dist/gates/policy.js +60 -17
- package/dist/gates/provider-gate.d.ts +22 -0
- package/dist/gates/provider-gate.js +30 -0
- package/dist/gates/tool-calls.d.ts +1 -3
- package/dist/gates/tool-calls.js +2 -3
- package/dist/gates/tool-client.d.ts +1 -1
- package/dist/gates/tool-client.js +5 -6
- package/dist/gates/tool-contract.d.ts +8 -5
- package/dist/gates/tool-contract.js +1 -1
- package/dist/gates/tool-gate.d.ts +18 -7
- package/dist/gates/tool-gate.js +49 -7
- package/dist/harness/calls.d.ts +27 -25
- package/dist/harness/calls.js +40 -46
- package/dist/harness/executors.d.ts +10 -3
- package/dist/harness/executors.js +53 -23
- package/dist/harness/main.js +2 -2
- package/dist/harness/pod.d.ts +0 -2
- package/dist/harness/pod.js +7 -6
- package/dist/harness/run-tokens.d.ts +4 -1
- package/dist/harness/run-tokens.js +6 -2
- package/dist/harness/service.d.ts +17 -14
- package/dist/harness/service.js +21 -35
- package/dist/harness/setup.d.ts +0 -2
- package/dist/harness/setup.js +4 -9
- package/dist/harness/ws-client.d.ts +2 -2
- package/dist/harness/ws-client.js +7 -5
- package/dist/harness/ws-port.d.ts +3 -4
- package/dist/harness/ws-port.js +3 -4
- package/dist/harness-api/artifacts.d.ts +20 -0
- package/dist/harness-api/artifacts.js +478 -0
- package/dist/harness-api/in-process.d.ts +2 -2
- package/dist/harness-api/in-process.js +18 -16
- package/dist/harness-api/record.d.ts +10 -9
- package/dist/harness-api/record.js +102 -157
- package/dist/harness-api/renew.d.ts +1 -1
- package/dist/harness-api/renew.js +2 -2
- package/dist/harness-api/server.d.ts +1 -16
- package/dist/harness-api/server.js +82 -73
- package/dist/harness-api/start.d.ts +20 -2
- package/dist/harness-api/start.js +57 -18
- package/dist/harness-api/tool-journal.d.ts +15 -10
- package/dist/harness-api/tool-journal.js +26 -24
- package/dist/harness-api/workspace.d.ts +0 -4
- package/dist/harness-api/workspace.js +2 -45
- package/dist/harness-api/ws-server.js +10 -10
- package/dist/host/create-host.js +2 -3
- package/dist/host/execution.js +2 -3
- package/dist/host/gates.d.ts +2 -9
- package/dist/host/gates.js +15 -12
- package/dist/host/http.d.ts +0 -2
- package/dist/host/http.js +0 -27
- package/dist/host/main.js +10 -3
- package/dist/host/operate.js +12 -3
- package/dist/host/stack-config.d.ts +11 -0
- package/dist/host/stack-config.js +21 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/index.js +2 -1
- package/dist/infra/readiness.d.ts +0 -2
- package/dist/infra/readiness.js +4 -3
- package/dist/keys/client.d.ts +1 -0
- package/dist/keys/client.js +2 -3
- package/dist/keys/contract.d.ts +1 -3
- package/dist/keys/contract.js +1 -3
- package/dist/keys/egress-ca.d.ts +0 -2
- package/dist/keys/egress-ca.js +2 -3
- package/dist/keys/keys.js +6 -1
- package/dist/keys/x509.d.ts +8 -4
- package/dist/keys/x509.js +14 -5
- package/dist/management-openapi.json +1961 -994
- package/dist/mcp/connect.d.ts +35 -26
- package/dist/mcp/connect.js +112 -141
- package/dist/mcp/pool.d.ts +27 -22
- package/dist/mcp/pool.js +34 -48
- package/dist/mcp/preview.d.ts +13 -6
- package/dist/mcp/preview.js +37 -20
- package/dist/mcp/snapshot.d.ts +35 -86
- package/dist/mcp/snapshot.js +93 -135
- package/dist/model/auth-store.js +5 -3
- package/dist/model/capabilities.d.ts +1 -1
- package/dist/model/catalog.d.ts +9 -36
- package/dist/model/catalog.js +6 -33
- package/dist/model/descriptor.d.ts +51 -0
- package/dist/model/descriptor.js +83 -0
- package/dist/model/models.d.ts +0 -3
- package/dist/model/models.js +3 -5
- package/dist/model/native-blocks.js +1 -1
- package/dist/model/pi-model.d.ts +16 -5
- package/dist/model/pi-model.js +86 -105
- package/dist/openapi.json +866 -1218
- package/dist/reads/cursor.js +3 -1
- package/dist/record/append.d.ts +34 -2
- package/dist/record/append.js +50 -1
- package/dist/record/environment.d.ts +2 -0
- package/dist/record/environment.js +2 -0
- package/dist/record/index.d.ts +1 -1
- package/dist/record/index.js +1 -1
- package/dist/redact.d.ts +1 -1
- package/dist/redact.js +3 -2
- package/dist/sandbox/egress-token.d.ts +3 -3
- package/dist/sandbox/egress-token.js +3 -3
- package/dist/sandbox/join.js +3 -2
- package/dist/sandbox/manager.d.ts +41 -41
- package/dist/sandbox/manager.js +112 -44
- package/dist/sandbox/placement.d.ts +1 -1
- package/dist/sandbox/placement.js +5 -8
- package/dist/sandbox/pods/client.d.ts +1 -10
- package/dist/sandbox/pods/client.js +8 -4
- package/dist/sandbox/pods/lifecycle.d.ts +19 -11
- package/dist/sandbox/pods/lifecycle.js +45 -23
- package/dist/sandbox/pods/network-gate.d.ts +4 -5
- package/dist/sandbox/pods/network-gate.js +7 -7
- package/dist/sandbox/pods/reconcile.js +4 -1
- package/dist/sandbox/pods/spec.js +8 -5
- package/dist/sandbox/policy.d.ts +7 -1
- package/dist/sandbox/policy.js +10 -3
- package/dist/sandbox/resolve.d.ts +8 -3
- package/dist/sandbox/resolve.js +12 -2
- package/dist/sandbox/setup.d.ts +3 -1
- package/dist/sandbox/setup.js +34 -11
- package/dist/sandbox/share.d.ts +5 -14
- package/dist/sandbox/share.js +5 -34
- package/dist/sandbox/skills.d.ts +7 -13
- package/dist/sandbox/skills.js +16 -31
- package/dist/sandbox/tenant-config.d.ts +2 -2
- package/dist/sandbox/tenant-config.js +4 -3
- package/dist/sandbox/tools.d.ts +7 -1
- package/dist/sandbox/tools.js +26 -26
- package/dist/sandbox/types.d.ts +10 -3
- package/dist/sandbox/types.js +9 -0
- package/dist/store/postgres/connect.d.ts +6 -5
- package/dist/store/postgres/connect.js +14 -11
- package/dist/store/postgres/control.js +13 -13
- package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
- package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
- package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
- package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
- package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
- package/dist/store/postgres/migrate.js +1 -1
- package/dist/store/postgres/reads.js +10 -2
- package/dist/store/postgres/schema.d.ts +19 -0
- package/dist/store/postgres/schema.js +7 -0
- package/dist/store/postgres/store.js +55 -26
- package/dist/store/types.d.ts +17 -7
- package/dist/streams/relay/core.d.ts +3 -3
- package/dist/streams/relay/core.js +9 -10
- package/dist/tenant/advance.js +21 -31
- package/dist/tenant/commands.js +9 -16
- package/dist/tenant/context.d.ts +14 -17
- package/dist/tenant/definition-files.d.ts +25 -4
- package/dist/tenant/definition-files.js +85 -47
- package/dist/tenant/effects.js +9 -36
- package/dist/tenant/environment-manifests.js +9 -1
- package/dist/tenant/environments.d.ts +0 -2
- package/dist/tenant/environments.js +54 -30
- package/dist/tenant/ephemeral.d.ts +1 -1
- package/dist/tenant/ephemeral.js +2 -1
- package/dist/tenant/history.d.ts +1 -1
- package/dist/tenant/history.js +1 -1
- package/dist/tenant/host-token.d.ts +3 -3
- package/dist/tenant/host-token.js +12 -11
- package/dist/tenant/http.d.ts +5 -8
- package/dist/tenant/http.js +9 -0
- package/dist/tenant/identity-file.d.ts +24 -1
- package/dist/tenant/identity-file.js +35 -7
- package/dist/tenant/issuers.d.ts +5 -5
- package/dist/tenant/issuers.js +31 -23
- package/dist/tenant/jwt.d.ts +8 -2
- package/dist/tenant/jwt.js +10 -2
- package/dist/tenant/limits.d.ts +90 -0
- package/dist/tenant/limits.js +175 -0
- package/dist/tenant/module.d.ts +4 -1
- package/dist/tenant/module.js +9 -8
- package/dist/tenant/outbound.d.ts +17 -19
- package/dist/tenant/outbound.js +18 -21
- package/dist/tenant/run-grants.js +3 -2
- package/dist/tenant/run-token.d.ts +0 -4
- package/dist/tenant/run-token.js +6 -11
- package/dist/tenant/runtime-limits.d.ts +24 -0
- package/dist/tenant/runtime-limits.js +27 -0
- package/dist/tenant/runtime.js +30 -43
- package/dist/tenant/session-streams.js +7 -6
- package/dist/tenant/session.d.ts +1 -1
- package/dist/tenant/session.js +1 -1
- package/dist/tenant/sessions.js +32 -29
- package/dist/tenant/signing-keys.d.ts +1 -1
- package/dist/tenant/signing-keys.js +5 -6
- package/dist/tenant/status.js +4 -3
- package/dist/tenant/streams.js +18 -20
- package/dist/tenant/sweep.d.ts +1 -21
- package/dist/tenant/sweep.js +39 -48
- package/dist/tenant/transcript.d.ts +70 -23
- package/dist/tenant/transcript.js +28 -22
- package/dist/tenant/types.d.ts +11 -16
- package/dist/tenant/worker.d.ts +2 -10
- package/dist/tenant/worker.js +5 -14
- package/dist/traces/ids.js +5 -1
- package/dist/traces/map.d.ts +9 -6
- package/dist/traces/map.js +9 -11
- package/dist/traces/otlp.d.ts +3 -3
- package/dist/traces/otlp.js +5 -3
- package/dist/traces/projector.d.ts +9 -4
- package/dist/traces/projector.js +13 -7
- package/dist/traces/read.js +6 -1
- package/dist/tuning.d.ts +383 -0
- package/dist/tuning.js +658 -0
- package/dist/vault/headers.js +4 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +3 -3
- package/dist/artifacts/export.d.ts +0 -8
- package/dist/artifacts/export.js +0 -204
- package/dist/artifacts/media-types.d.ts +0 -30
- package/dist/artifacts/media-types.js +0 -137
- package/dist/artifacts/workspace.d.ts +0 -60
- package/dist/artifacts/workspace.js +0 -45
- package/dist/media.d.ts +0 -10
- package/dist/media.js +0 -58
- package/dist/sandbox/runtime-tools.d.ts +0 -9
- package/dist/sandbox/runtime-tools.js +0 -16
- package/dist/sandbox/session-sandbox.d.ts +0 -16
- package/dist/sandbox/session-sandbox.js +0 -77
- package/dist/tenant/artifact-tool.d.ts +0 -13
- package/dist/tenant/artifact-tool.js +0 -243
- package/dist/tenant/skill-tool.d.ts +0 -20
- package/dist/tenant/skill-tool.js +0 -67
- package/dist/tenant/tool-results.d.ts +0 -57
- package/dist/tenant/tool-results.js +0 -298
- package/dist/tenant/tool-search.d.ts +0 -39
- package/dist/tenant/tool-search.js +0 -103
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
*
|
|
17
17
|
* The host token is accepted only by the Harness API listener (a connection serving that one
|
|
18
18
|
* sandbox, and `host.renew`); the egress token only by egress-gate, as the proxy credential
|
|
19
|
-
* (`Proxy-Authorization`). Neither is a bearer anywhere else: the
|
|
19
|
+
* (`Proxy-Authorization`). Neither is a bearer anywhere else: the Runtime API's bearer check
|
|
20
20
|
* accepts trusted issuers' tokens only, and the gates run tokens only.
|
|
21
21
|
*
|
|
22
22
|
* Verification reads the key row on every call (a revoked key refuses at once) and, with
|
|
@@ -26,23 +26,24 @@
|
|
|
26
26
|
import { randomUUID } from "node:crypto";
|
|
27
27
|
import { errors, importJWK, jwtVerify } from "jose";
|
|
28
28
|
import { tenantTokenIssuer } from "@nylorun/core/contracts";
|
|
29
|
-
import {
|
|
29
|
+
import { clockToleranceSeconds, tokenKeyId } from "./jwt.js";
|
|
30
|
+
import { hostTuning } from "../tuning.js";
|
|
30
31
|
export const HOST_TOKEN_TYP = "nylorun-host+jwt";
|
|
31
32
|
export const HOST_TOKEN_AUD = "nylorun-harness-api";
|
|
32
33
|
export const EGRESS_TOKEN_TYP = "nylorun-egress+jwt";
|
|
33
34
|
export const EGRESS_TOKEN_AUD = "nylorun-egress";
|
|
34
|
-
/** How long a host or egress token lives. The engine renews them before. */
|
|
35
|
-
export const
|
|
36
|
-
const LIFETIME_SLACK_SECONDS = 60;
|
|
35
|
+
/** How long a host or egress token lives (`keys.hostTokenTtlSeconds`). The engine renews them before. */
|
|
36
|
+
export const hostTokenTtlSeconds = () => hostTuning().keys.hostTokenTtlSeconds;
|
|
37
37
|
/**
|
|
38
38
|
* Mints an egress token (the only path that does): the proxy credential egress-gate accepts,
|
|
39
39
|
* with egress-gate's claims (no `sub`). `mintHostTokens` mints one with every host token.
|
|
40
40
|
*/
|
|
41
41
|
export async function mintEgressToken(signer, request) {
|
|
42
42
|
const { tenantId, sandboxId, epoch, podUid } = request;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
const most = hostTokenTtlSeconds();
|
|
44
|
+
const ttl = request.ttl ?? most;
|
|
45
|
+
if (!Number.isSafeInteger(ttl) || ttl < 1 || ttl > most)
|
|
46
|
+
throw new Error(`An egress token lives 1 to ${most} s, not ${ttl}`);
|
|
46
47
|
if (!Number.isSafeInteger(epoch) || epoch < 1)
|
|
47
48
|
throw new Error(`Host epoch ${epoch} is not a positive integer`);
|
|
48
49
|
if (sandboxId === "" || podUid === "")
|
|
@@ -68,7 +69,7 @@ export async function mintEgressToken(signer, request) {
|
|
|
68
69
|
export async function mintHostTokens(signer, host) {
|
|
69
70
|
const tenantId = signer.config.tenantId;
|
|
70
71
|
const iat = Math.floor(Date.now() / 1000);
|
|
71
|
-
const exp = iat +
|
|
72
|
+
const exp = iat + hostTokenTtlSeconds();
|
|
72
73
|
const [hostToken, egress] = await Promise.all([
|
|
73
74
|
signer.keys.sign({
|
|
74
75
|
typ: HOST_TOKEN_TYP,
|
|
@@ -129,7 +130,7 @@ export async function verifyHostToken(store, tenantId, raw, kind, cache = new Ma
|
|
|
129
130
|
issuer: tenantTokenIssuer(tenantId),
|
|
130
131
|
audience: aud,
|
|
131
132
|
typ,
|
|
132
|
-
clockTolerance:
|
|
133
|
+
clockTolerance: clockToleranceSeconds(),
|
|
133
134
|
requiredClaims: ["iat", "exp", "jti"],
|
|
134
135
|
});
|
|
135
136
|
payload = verified.payload;
|
|
@@ -148,7 +149,7 @@ export async function verifyHostToken(store, tenantId, raw, kind, cache = new Ma
|
|
|
148
149
|
typeof epc !== "number" ||
|
|
149
150
|
!Number.isSafeInteger(epc) ||
|
|
150
151
|
epc < 1 ||
|
|
151
|
-
exp - iat >
|
|
152
|
+
exp - iat > hostTokenTtlSeconds() + hostTuning().keys.tokenLifetimeSlackSeconds)
|
|
152
153
|
return refused("claims");
|
|
153
154
|
return {
|
|
154
155
|
ok: true,
|
package/dist/tenant/http.d.ts
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The Tenant's typed rejections: what a route or the code it calls throws for the client to
|
|
3
|
-
* see (`api/http/respond.ts` answers it), and the opaque 404 (D5) for what must not be told
|
|
4
|
-
* apart from nothing: a capability link the Runtime did not sign, a Tenant that is not this
|
|
5
|
-
* Host's. Credentials are `401` with a challenge since protocol 9 (`resource-server.ts`).
|
|
6
|
-
*
|
|
7
|
-
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
8
|
-
*/
|
|
9
1
|
/** D5's opaque failure: a 404 that says nothing of why. */
|
|
10
2
|
export declare const OPAQUE_NOT_FOUND: {
|
|
11
3
|
status: "rejected";
|
|
@@ -27,6 +19,11 @@ export declare class HttpError extends Error {
|
|
|
27
19
|
headers?: Readonly<Record<string, string>>);
|
|
28
20
|
}
|
|
29
21
|
export declare const fail: (status: number, message: string, rejection?: Rejection, headers?: Readonly<Record<string, string>>) => never;
|
|
22
|
+
/**
|
|
23
|
+
* A 409 the client may try again: `code`, and when, in `details.retryAfterSeconds` and
|
|
24
|
+
* `Retry-After` (`tenant.retryAfterSeconds`, Host tuning).
|
|
25
|
+
*/
|
|
26
|
+
export declare const failRetryLater: (message: string, code: string) => never;
|
|
30
27
|
export declare class OpaqueAuthError extends Error {
|
|
31
28
|
readonly status = 404;
|
|
32
29
|
readonly body: {
|
package/dist/tenant/http.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Later waves: stable; the streams seam (Wave 2 / Y) keeps using these helpers.
|
|
8
8
|
*/
|
|
9
|
+
import { hostTuning } from "../tuning.js";
|
|
9
10
|
/** D5's opaque failure: a 404 that says nothing of why. */
|
|
10
11
|
export const OPAQUE_NOT_FOUND = {
|
|
11
12
|
status: "rejected",
|
|
@@ -28,6 +29,14 @@ export class HttpError extends Error {
|
|
|
28
29
|
export const fail = (status, message, rejection, headers) => {
|
|
29
30
|
throw new HttpError(status, message, rejection, headers);
|
|
30
31
|
};
|
|
32
|
+
/**
|
|
33
|
+
* A 409 the client may try again: `code`, and when, in `details.retryAfterSeconds` and
|
|
34
|
+
* `Retry-After` (`tenant.retryAfterSeconds`, Host tuning).
|
|
35
|
+
*/
|
|
36
|
+
export const failRetryLater = (message, code) => {
|
|
37
|
+
const seconds = hostTuning().tenant.retryAfterSeconds;
|
|
38
|
+
return fail(409, message, { code, details: { retryAfterSeconds: seconds } }, { "retry-after": String(seconds) });
|
|
39
|
+
};
|
|
31
40
|
export class OpaqueAuthError extends Error {
|
|
32
41
|
status = 404;
|
|
33
42
|
body = OPAQUE_NOT_FOUND;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The identity file (Host feature `trusted-issuers`, F9 I2): the YAML file
|
|
3
|
-
* `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the
|
|
3
|
+
* `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the Runtime API accepts as
|
|
4
4
|
* bearers. Read once at boot (`host/main.ts`); a change takes a restart. A malformed file stops
|
|
5
5
|
* the boot with a message naming the issuer and the field. A key the file does not define is
|
|
6
6
|
* ignored with a warning (`maxLifetime`, gone in protocol 9, among them), so an older file
|
|
@@ -18,6 +18,12 @@
|
|
|
18
18
|
* allowedScopes: [agents:read, sessions:own, environments:write] # add studio to grant it
|
|
19
19
|
* agents: [support] # absent reaches every agent
|
|
20
20
|
* environments: ["{org_id}/*"] # absent reaches no Environment
|
|
21
|
+
* jwksFetch: # how the JWKS is fetched and cached
|
|
22
|
+
* timeoutMs: 5000 # one fetch
|
|
23
|
+
* maxBytes: 262144 # the largest JWKS read
|
|
24
|
+
* maxKeys: 64 # keys read from it, in order
|
|
25
|
+
* refetchMs: 60000 # the least time between two fetches for a new kid
|
|
26
|
+
* maxAgeMs: 600000 # cached keys' age before a background refresh
|
|
21
27
|
* ```
|
|
22
28
|
*/
|
|
23
29
|
import { type KeyObject } from "node:crypto";
|
|
@@ -25,6 +31,21 @@ import { type IssuerScope } from "@nylorun/core/contracts";
|
|
|
25
31
|
/** The JWT algorithms an issuer may sign with. */
|
|
26
32
|
export declare const ISSUER_ALGORITHMS: readonly ["RS256", "ES256", "EdDSA"];
|
|
27
33
|
export type IssuerAlgorithm = (typeof ISSUER_ALGORITHMS)[number];
|
|
34
|
+
/** How an issuer's JWKS is fetched and cached (`jwksFetch`), each setting with its default. */
|
|
35
|
+
export interface JwksFetchSettings {
|
|
36
|
+
/** How long one fetch may take. */
|
|
37
|
+
readonly timeoutMs: number;
|
|
38
|
+
/** The largest JWKS read; a larger one is a failed fetch. */
|
|
39
|
+
readonly maxBytes: number;
|
|
40
|
+
/** The keys read from it, in order; the rest are ignored. */
|
|
41
|
+
readonly maxKeys: number;
|
|
42
|
+
/** The least time between two fetches for a `kid` the cache has not seen. */
|
|
43
|
+
readonly refetchMs: number;
|
|
44
|
+
/** How old cached keys get before a background refresh. */
|
|
45
|
+
readonly maxAgeMs: number;
|
|
46
|
+
}
|
|
47
|
+
/** The `jwksFetch` an entry that sets none fetches with. */
|
|
48
|
+
export declare const JWKS_FETCH_DEFAULTS: JwksFetchSettings;
|
|
28
49
|
/** A public key an issuer signs with, and the one algorithm it verifies. */
|
|
29
50
|
export interface IssuerKey {
|
|
30
51
|
readonly key: KeyObject;
|
|
@@ -57,6 +78,8 @@ export interface TrustedIssuerConfig {
|
|
|
57
78
|
readonly agents?: readonly string[];
|
|
58
79
|
/** Environment grant templates, such as `{org_id}/*`; absent reaches no Environment. */
|
|
59
80
|
readonly environments?: readonly string[];
|
|
81
|
+
/** How its JWKS is fetched; each setting left out is its default (`JWKS_FETCH_DEFAULTS`). */
|
|
82
|
+
readonly jwksFetch?: Partial<JwksFetchSettings>;
|
|
60
83
|
}
|
|
61
84
|
/** A malformed identity file: the message names the issuer and the field. */
|
|
62
85
|
export declare class IdentityFileError extends Error {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The identity file (Host feature `trusted-issuers`, F9 I2): the YAML file
|
|
3
|
-
* `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the
|
|
3
|
+
* `NYLORUN_IDENTITY_FILE` names, listing the issuers whose JWTs the Runtime API accepts as
|
|
4
4
|
* bearers. Read once at boot (`host/main.ts`); a change takes a restart. A malformed file stops
|
|
5
5
|
* the boot with a message naming the issuer and the field. A key the file does not define is
|
|
6
6
|
* ignored with a warning (`maxLifetime`, gone in protocol 9, among them), so an older file
|
|
@@ -18,23 +18,39 @@
|
|
|
18
18
|
* allowedScopes: [agents:read, sessions:own, environments:write] # add studio to grant it
|
|
19
19
|
* agents: [support] # absent reaches every agent
|
|
20
20
|
* environments: ["{org_id}/*"] # absent reaches no Environment
|
|
21
|
+
* jwksFetch: # how the JWKS is fetched and cached
|
|
22
|
+
* timeoutMs: 5000 # one fetch
|
|
23
|
+
* maxBytes: 262144 # the largest JWKS read
|
|
24
|
+
* maxKeys: 64 # keys read from it, in order
|
|
25
|
+
* refetchMs: 60000 # the least time between two fetches for a new kid
|
|
26
|
+
* maxAgeMs: 600000 # cached keys' age before a background refresh
|
|
21
27
|
* ```
|
|
22
28
|
*/
|
|
23
29
|
import { createPublicKey } from "node:crypto";
|
|
24
30
|
import { parse as parseYaml } from "yaml";
|
|
25
31
|
import { z } from "zod";
|
|
26
|
-
import { ISSUER_SCOPES, isEnvironmentGrant,
|
|
32
|
+
import { ISSUER_SCOPES, isEnvironmentGrant, TIMER_MAX_MS, TOKEN_SCOPES, } from "@nylorun/core/contracts";
|
|
27
33
|
/** The JWT algorithms an issuer may sign with. */
|
|
28
34
|
export const ISSUER_ALGORITHMS = ["RS256", "ES256", "EdDSA"];
|
|
29
35
|
/** An entry's defaults: the `sub` claim, the RFC 9068 `scope` claim, every token scope but `studio`. */
|
|
30
36
|
const DEFAULT_SUBJECT = "{sub}";
|
|
31
37
|
const DEFAULT_SCOPES = { claim: "scope" };
|
|
32
38
|
const DEFAULT_ALLOWED_SCOPES = TOKEN_SCOPES;
|
|
33
|
-
/**
|
|
34
|
-
|
|
39
|
+
/**
|
|
40
|
+
* An issuer's name, 1–32 characters as an application key's id: an identifier's grammar is the
|
|
41
|
+
* protocol's (it names the issuer in tokens and principals), so its length is a constant.
|
|
42
|
+
*/
|
|
35
43
|
const ISSUER_NAME = /^[a-z][a-z0-9-]{0,31}$/;
|
|
36
44
|
/** A claim reference in a template: `{claim}`, a top-level claim name without braces. */
|
|
37
45
|
const CLAIM_REFERENCE = /\{([^{}]+)\}/g;
|
|
46
|
+
/** The `jwksFetch` an entry that sets none fetches with. */
|
|
47
|
+
export const JWKS_FETCH_DEFAULTS = Object.freeze({
|
|
48
|
+
timeoutMs: 5_000,
|
|
49
|
+
maxBytes: 256 * 1024,
|
|
50
|
+
maxKeys: 64,
|
|
51
|
+
refetchMs: 60_000,
|
|
52
|
+
maxAgeMs: 10 * 60_000,
|
|
53
|
+
});
|
|
38
54
|
/** A malformed identity file: the message names the issuer and the field. */
|
|
39
55
|
export class IdentityFileError extends Error {
|
|
40
56
|
constructor(message) {
|
|
@@ -79,10 +95,10 @@ export function issuerKey(key, kid) {
|
|
|
79
95
|
throw new Error(`a ${key.asymmetricKeyType ?? "secret"} key is not accepted: use RSA (RS256), P-256 (ES256) or Ed25519 (EdDSA)`);
|
|
80
96
|
}
|
|
81
97
|
}
|
|
98
|
+
// The identity file's author writes every template; nothing a caller sends adds to one (B15).
|
|
82
99
|
const template = (what) => z
|
|
83
100
|
.string()
|
|
84
101
|
.min(1)
|
|
85
|
-
.max(200)
|
|
86
102
|
.refine(bracesBalanced, `${what} may use braces only around a claim name, as {sub}`);
|
|
87
103
|
const IssuerScopeSchema = z.enum(ISSUER_SCOPES, {
|
|
88
104
|
error: (issue) => `${String(issue.input)} is not a scope an issuer may grant (one of ${ISSUER_SCOPES.join(", ")})`,
|
|
@@ -107,7 +123,8 @@ const IssuerEntrySchema = z
|
|
|
107
123
|
}
|
|
108
124
|
}, "must be an http(s) URL without credentials")
|
|
109
125
|
.optional(),
|
|
110
|
-
|
|
126
|
+
// The file's author writes every key: no count of them is a limit (B15).
|
|
127
|
+
keys: z.array(z.string().min(1)).min(1).optional(),
|
|
111
128
|
subject: template("subject")
|
|
112
129
|
.refine((value) => claimReferences(value).length > 0, "must reference a claim, as {sub}: without one every token is the same person")
|
|
113
130
|
.default(DEFAULT_SUBJECT),
|
|
@@ -123,7 +140,17 @@ const IssuerEntrySchema = z
|
|
|
123
140
|
.array(template("an Environment grant").refine(
|
|
124
141
|
// A rendered grant must be a grant: checked here with a sample value per claim.
|
|
125
142
|
(value) => isEnvironmentGrant(renderTemplate(value, () => "x")), "must render to an Environment id or a prefix ending in /*, as {org_id}/*"))
|
|
126
|
-
.
|
|
143
|
+
.optional(),
|
|
144
|
+
jwksFetch: z
|
|
145
|
+
.object({
|
|
146
|
+
// A fetch is timed with a JavaScript timer.
|
|
147
|
+
timeoutMs: z.number().int().positive().max(TIMER_MAX_MS).optional(),
|
|
148
|
+
maxBytes: z.number().int().positive().optional(),
|
|
149
|
+
maxKeys: z.number().int().positive().optional(),
|
|
150
|
+
refetchMs: z.number().int().nonnegative().optional(),
|
|
151
|
+
maxAgeMs: z.number().int().nonnegative().optional(),
|
|
152
|
+
})
|
|
153
|
+
.strict()
|
|
127
154
|
.optional(),
|
|
128
155
|
});
|
|
129
156
|
const IdentityFileSchema = z.object({ issuers: z.array(z.unknown()) });
|
|
@@ -211,6 +238,7 @@ export function parseIdentityFile(text, source = "identity file", options = {})
|
|
|
211
238
|
issuer: value.issuer,
|
|
212
239
|
audience: value.audience,
|
|
213
240
|
...(value.jwks ? { jwks: value.jwks } : {}),
|
|
241
|
+
...(value.jwksFetch ? { jwksFetch: value.jwksFetch } : {}),
|
|
214
242
|
...(keys ? { keys } : {}),
|
|
215
243
|
subject: value.subject,
|
|
216
244
|
scopes: value.scopes,
|
package/dist/tenant/issuers.d.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { type JWTPayload } from "jose";
|
|
2
2
|
import type { AuthScope, TenantContext } from "./context.js";
|
|
3
3
|
import { type TrustedIssuerConfig } from "./identity-file.js";
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* The longest issuer token read: a bearer arrives in a request's headers, which Node refuses past
|
|
6
|
+
* `http.maxHeaderSize` (16 KiB unless `--max-http-header-size` says otherwise). A dependency's
|
|
7
|
+
* constant, not a limit (B15).
|
|
8
|
+
*/
|
|
5
9
|
export declare const MAX_ISSUER_TOKEN_BYTES: number;
|
|
6
10
|
export interface TrustedIssuersOptions {
|
|
7
11
|
/** Fetches JWKS URLs. Default: the global fetch. */
|
|
8
12
|
fetch?: typeof fetch;
|
|
9
|
-
/** The least time between two JWKS fetches of one issuer for an unknown `kid`. Default 60 s. */
|
|
10
|
-
refetchMs?: number;
|
|
11
|
-
/** How old cached JWKS keys get before a background refresh. Default 10 minutes. */
|
|
12
|
-
maxAgeMs?: number;
|
|
13
13
|
/** Milliseconds since the epoch. Default `Date.now`. */
|
|
14
14
|
now?: () => number;
|
|
15
15
|
}
|
package/dist/tenant/issuers.js
CHANGED
|
@@ -4,11 +4,12 @@
|
|
|
4
4
|
* A token is one of theirs when its unverified `iss` names a configured issuer; it is then
|
|
5
5
|
* verified like this, and nothing else is tried:
|
|
6
6
|
*
|
|
7
|
-
* -
|
|
8
|
-
*
|
|
7
|
+
* - no longer than a request's headers may be (`http.maxHeaderSize`), RS256, ES256 or EdDSA, and
|
|
8
|
+
* no header that could name another key (`jku`, `jwk`, `x5u`, `x5c`, `crit`);
|
|
9
9
|
* - signed by a key of the issuer: its static `keys`, or its JWKS, fetched only from the
|
|
10
10
|
* configured URL (`redirect: "error"`), cached by `kid`, refetched at most once a minute for
|
|
11
|
-
* a `kid` it has not seen, and refreshed in the background once the cache is 10 minutes old
|
|
11
|
+
* a `kid` it has not seen, and refreshed in the background once the cache is 10 minutes old
|
|
12
|
+
* (the entry's `jwksFetch` settings, these by default).
|
|
12
13
|
* When the JWKS cannot be fetched, cached keys keep working and a new `kid` is
|
|
13
14
|
* `401 issuer_unavailable`;
|
|
14
15
|
* - `iss` and `aud` as configured, and `exp` present, with the 30 s clock tolerance of every
|
|
@@ -21,22 +22,27 @@
|
|
|
21
22
|
* (`resource-server.ts`), its reason logged.
|
|
22
23
|
*/
|
|
23
24
|
import { createHash, createPublicKey } from "node:crypto";
|
|
25
|
+
import { maxHeaderSize } from "node:http";
|
|
24
26
|
import { decodeJwt, decodeProtectedHeader, errors, jwtVerify } from "jose";
|
|
25
27
|
import { isSubject } from "@nylorun/core/contracts";
|
|
26
28
|
import { HttpError } from "./http.js";
|
|
27
|
-
import {
|
|
29
|
+
import { clockToleranceSeconds, looksLikeToken } from "./jwt.js";
|
|
28
30
|
import { bearerChallenge, failCredential } from "./resource-server.js";
|
|
29
|
-
import { ISSUER_ALGORITHMS, issuerKey, renderTemplate, } from "./identity-file.js";
|
|
30
|
-
/**
|
|
31
|
-
|
|
31
|
+
import { ISSUER_ALGORITHMS, issuerKey, JWKS_FETCH_DEFAULTS, renderTemplate, } from "./identity-file.js";
|
|
32
|
+
/**
|
|
33
|
+
* The longest issuer token read: a bearer arrives in a request's headers, which Node refuses past
|
|
34
|
+
* `http.maxHeaderSize` (16 KiB unless `--max-http-header-size` says otherwise). A dependency's
|
|
35
|
+
* constant, not a limit (B15).
|
|
36
|
+
*/
|
|
37
|
+
export const MAX_ISSUER_TOKEN_BYTES = maxHeaderSize;
|
|
32
38
|
/** Subjects the Runtime keeps for itself: never an issuer token's. */
|
|
39
|
+
/**
|
|
40
|
+
* The longest token id kept as it is, 200 characters as the Runtime's other ids: an identifier's
|
|
41
|
+
* grammar is the protocol's, so a constant.
|
|
42
|
+
*/
|
|
43
|
+
const TOKEN_ID_MAX = 200;
|
|
33
44
|
const RESERVED = new Set(["host", "installation"]);
|
|
34
45
|
const FORBIDDEN_HEADERS = ["jku", "jwk", "x5u", "x5c", "crit"];
|
|
35
|
-
const JWKS_TIMEOUT_MS = 5000;
|
|
36
|
-
const JWKS_MAX_BYTES = 256 * 1024;
|
|
37
|
-
const JWKS_MAX_KEYS = 64;
|
|
38
|
-
const DEFAULT_REFETCH_MS = 60_000;
|
|
39
|
-
const DEFAULT_MAX_AGE_MS = 10 * 60_000;
|
|
40
46
|
/** A grant's claim value: one Environment id segment, so a claim never widens the template. */
|
|
41
47
|
const GRANT_SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
42
48
|
/** The issuer's public keys: static, or its JWKS's as last fetched. */
|
|
@@ -49,11 +55,13 @@ class IssuerKeys {
|
|
|
49
55
|
okAt = Number.NEGATIVE_INFINITY;
|
|
50
56
|
lastOk;
|
|
51
57
|
inflight;
|
|
58
|
+
settings;
|
|
52
59
|
constructor(config, options) {
|
|
53
60
|
this.config = config;
|
|
54
61
|
this.options = options;
|
|
55
62
|
for (const key of config.keys ?? [])
|
|
56
63
|
this.unnamed.push(key);
|
|
64
|
+
this.settings = { ...JWKS_FETCH_DEFAULTS, ...config.jwksFetch };
|
|
57
65
|
}
|
|
58
66
|
/** The keys that may have signed a token with `kid` and `alg`, or `unavailable`. */
|
|
59
67
|
async candidates(kid, alg) {
|
|
@@ -75,13 +83,13 @@ class IssuerKeys {
|
|
|
75
83
|
if (found.length === 0) {
|
|
76
84
|
if (this.inflight)
|
|
77
85
|
await this.inflight;
|
|
78
|
-
else if (now - this.fetchedAt >= this.
|
|
86
|
+
else if (now - this.fetchedAt >= this.settings.refetchMs)
|
|
79
87
|
await this.refresh();
|
|
80
88
|
found = pick();
|
|
81
89
|
if (found.length === 0 && this.lastOk === false)
|
|
82
90
|
return "unavailable";
|
|
83
91
|
}
|
|
84
|
-
else if (now - this.okAt >= this.
|
|
92
|
+
else if (now - this.okAt >= this.settings.maxAgeMs && now - this.fetchedAt >= this.settings.refetchMs) {
|
|
85
93
|
// Old keys keep working while the refresh runs, and when it fails.
|
|
86
94
|
void this.refresh();
|
|
87
95
|
}
|
|
@@ -101,19 +109,19 @@ class IssuerKeys {
|
|
|
101
109
|
const response = await this.options.fetch(this.config.jwks, {
|
|
102
110
|
redirect: "error",
|
|
103
111
|
headers: { accept: "application/jwk-set+json, application/json" },
|
|
104
|
-
signal: AbortSignal.timeout(
|
|
112
|
+
signal: AbortSignal.timeout(this.settings.timeoutMs),
|
|
105
113
|
});
|
|
106
114
|
if (!response.ok)
|
|
107
115
|
throw new Error(`JWKS answered ${response.status}`);
|
|
108
116
|
const text = await response.text();
|
|
109
|
-
if (Buffer.byteLength(text) >
|
|
117
|
+
if (Buffer.byteLength(text) > this.settings.maxBytes)
|
|
110
118
|
throw new Error("JWKS is too large");
|
|
111
119
|
const body = JSON.parse(text);
|
|
112
120
|
if (!Array.isArray(body.keys))
|
|
113
121
|
throw new Error("JWKS has no keys array");
|
|
114
122
|
const byKid = new Map();
|
|
115
123
|
const unnamed = [];
|
|
116
|
-
for (const jwk of body.keys.slice(0,
|
|
124
|
+
for (const jwk of body.keys.slice(0, this.settings.maxKeys)) {
|
|
117
125
|
const key = jwkKey(jwk);
|
|
118
126
|
if (!key)
|
|
119
127
|
continue;
|
|
@@ -143,7 +151,8 @@ function jwkKey(jwk) {
|
|
|
143
151
|
return undefined; // a private key: never trusted as published
|
|
144
152
|
if (entry.alg !== undefined && !ISSUER_ALGORITHMS.includes(entry.alg))
|
|
145
153
|
return undefined;
|
|
146
|
-
|
|
154
|
+
// A `kid` longer than a token may be names no token's key.
|
|
155
|
+
const kid = typeof entry.kid === "string" && entry.kid.length <= MAX_ISSUER_TOKEN_BYTES ? entry.kid : undefined;
|
|
147
156
|
try {
|
|
148
157
|
const key = issuerKey(createPublicKey({ key: jwk, format: "jwk" }), kid);
|
|
149
158
|
return entry.alg === undefined || entry.alg === key.alg ? key : undefined;
|
|
@@ -186,12 +195,12 @@ function trustedIssuer(config, options) {
|
|
|
186
195
|
algorithms: [candidate.alg],
|
|
187
196
|
issuer: config.issuer,
|
|
188
197
|
audience: config.audience,
|
|
189
|
-
clockTolerance:
|
|
198
|
+
clockTolerance: clockToleranceSeconds(),
|
|
190
199
|
requiredClaims: ["exp"],
|
|
191
200
|
currentDate: new Date(options.now()),
|
|
192
201
|
});
|
|
193
202
|
const now = Math.floor(options.now() / 1000);
|
|
194
|
-
if (payload.iat !== undefined && payload.iat > now +
|
|
203
|
+
if (payload.iat !== undefined && payload.iat > now + clockToleranceSeconds())
|
|
195
204
|
return refuse("invalid", "issuer_token_future");
|
|
196
205
|
return { ok: true, payload, ...(kid !== undefined ? { kid } : {}) };
|
|
197
206
|
}
|
|
@@ -212,8 +221,6 @@ function trustedIssuer(config, options) {
|
|
|
212
221
|
export function createTrustedIssuers(configs, options = {}) {
|
|
213
222
|
const resolved = {
|
|
214
223
|
fetch: options.fetch ?? ((input, init) => globalThis.fetch(input, init)),
|
|
215
|
-
refetchMs: options.refetchMs ?? DEFAULT_REFETCH_MS,
|
|
216
|
-
maxAgeMs: options.maxAgeMs ?? DEFAULT_MAX_AGE_MS,
|
|
217
224
|
now: options.now ?? Date.now,
|
|
218
225
|
};
|
|
219
226
|
const issuers = configs.map((config) => trustedIssuer(config, resolved));
|
|
@@ -300,7 +307,8 @@ export async function verifyIssuerToken(ctx, issuer, raw, resourceMetadata) {
|
|
|
300
307
|
// A grant whose claim is missing or not one id segment reaches nothing.
|
|
301
308
|
return rendered === undefined ? [] : [rendered];
|
|
302
309
|
});
|
|
303
|
-
|
|
310
|
+
// A `jti` longer than an id the Runtime keeps (`TOKEN_ID_MAX`) is replaced by the token's hash.
|
|
311
|
+
const jti = typeof payload.jti === "string" && payload.jti.length > 0 && payload.jti.length <= TOKEN_ID_MAX
|
|
304
312
|
? payload.jti
|
|
305
313
|
: createHash("sha256").update(raw).digest("base64url");
|
|
306
314
|
return {
|
package/dist/tenant/jwt.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/**
|
|
2
|
+
* The longest token read: a token arrives in a request's headers (a bearer) or its URL (a
|
|
3
|
+
* capability link), which Node refuses past `http.maxHeaderSize` (16 KiB unless
|
|
4
|
+
* `--max-http-header-size` says otherwise). A dependency's constant, not a limit (B15).
|
|
5
|
+
*/
|
|
6
|
+
export declare const MAX_TOKEN_BYTES: number;
|
|
7
|
+
/** The clock skew accepted when a token's times are checked (`keys.clockToleranceSeconds`). */
|
|
8
|
+
export declare const clockToleranceSeconds: () => number;
|
|
3
9
|
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
4
10
|
export declare function looksLikeToken(bearer: string): boolean;
|
|
5
11
|
/** The `typ` of a JWT-shaped bearer, or `undefined` when its header cannot be read. */
|
package/dist/tenant/jwt.js
CHANGED
|
@@ -3,10 +3,18 @@
|
|
|
3
3
|
* the Tenant's ES256 JWTs, recognised by shape, told apart by their `typ`, and refused before
|
|
4
4
|
* any key is read when the header is not one the Runtime issues.
|
|
5
5
|
*/
|
|
6
|
+
import { maxHeaderSize } from "node:http";
|
|
6
7
|
import { decodeProtectedHeader } from "jose";
|
|
7
8
|
import { SIGNING_KEY_ID_PATTERN } from "@nylorun/core/compatibility";
|
|
8
|
-
|
|
9
|
-
|
|
9
|
+
import { hostTuning } from "../tuning.js";
|
|
10
|
+
/**
|
|
11
|
+
* The longest token read: a token arrives in a request's headers (a bearer) or its URL (a
|
|
12
|
+
* capability link), which Node refuses past `http.maxHeaderSize` (16 KiB unless
|
|
13
|
+
* `--max-http-header-size` says otherwise). A dependency's constant, not a limit (B15).
|
|
14
|
+
*/
|
|
15
|
+
export const MAX_TOKEN_BYTES = maxHeaderSize;
|
|
16
|
+
/** The clock skew accepted when a token's times are checked (`keys.clockToleranceSeconds`). */
|
|
17
|
+
export const clockToleranceSeconds = () => hostTuning().keys.clockToleranceSeconds;
|
|
10
18
|
const JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
11
19
|
const FORBIDDEN_HEADERS = ["jku", "jwk", "x5u", "x5c", "x5t", "crit"];
|
|
12
20
|
/** True when `bearer` has the shape of a JWT; application keys never do. */
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What artifacts, skills and disk versions may hold: the Tenant's limits (`artifacts.config`,
|
|
3
|
+
* `skills.config` here, and the Runtime limits `diskVersion*` of `limits.config`), and an
|
|
4
|
+
* Environment's, which its manifest sets (`limits`). A limit the manifest leaves out is the
|
|
5
|
+
* Tenant's as it is when the limit is checked, and none is ever above the Tenant's: a manifest
|
|
6
|
+
* asking for more is refused when the Environment is made, and a Tenant that lowers a limit
|
|
7
|
+
* later lowers it for every Environment.
|
|
8
|
+
*
|
|
9
|
+
* Also the shape a manifest and an Environment's labels must fit, which the Tenant's Runtime
|
|
10
|
+
* limits set (`manifestShapeIssues`, `labelIssues`), checked when they are put rather than by
|
|
11
|
+
* their schemas (B15).
|
|
12
|
+
*/
|
|
13
|
+
import { type EnvironmentFile, type EnvironmentLimits, type EnvironmentSecretSlots, type EnvironmentSetupCommand, type RuntimeLimits, type TenantSkillsConfig, type ToolRule } from "@nylorun/core/contracts";
|
|
14
|
+
import { type ArtifactLimits } from "../artifacts/config.js";
|
|
15
|
+
export declare const SKILLS_CONFIG_SETTING = "skills.config";
|
|
16
|
+
export interface SkillLimits {
|
|
17
|
+
readonly totalBytes: number;
|
|
18
|
+
}
|
|
19
|
+
/** What one version of an Environment's disk may hold. */
|
|
20
|
+
export interface DiskLimits {
|
|
21
|
+
readonly versionBytes: number;
|
|
22
|
+
readonly versionEntries: number;
|
|
23
|
+
}
|
|
24
|
+
/** The limits an Environment's artifacts, skills and disk versions are held to. */
|
|
25
|
+
export interface StorageLimits {
|
|
26
|
+
readonly artifacts: ArtifactLimits;
|
|
27
|
+
readonly skills: SkillLimits;
|
|
28
|
+
readonly disk: DiskLimits;
|
|
29
|
+
}
|
|
30
|
+
interface SettingsReader {
|
|
31
|
+
getSetting(key: string): Promise<string | undefined>;
|
|
32
|
+
}
|
|
33
|
+
interface SettingsWriter extends SettingsReader {
|
|
34
|
+
putSetting(key: string, value: string): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
/** The skill limits in force: the Tenant's, with the default for what it leaves unset. */
|
|
37
|
+
export declare function effectiveSkillLimits(config: TenantSkillsConfig): SkillLimits;
|
|
38
|
+
export declare function readSkillsConfig(t: SettingsReader): Promise<TenantSkillsConfig>;
|
|
39
|
+
export declare function readSkillLimits(t: SettingsReader): Promise<SkillLimits>;
|
|
40
|
+
export declare function writeSkillsConfig(t: SettingsWriter, config: TenantSkillsConfig): Promise<void>;
|
|
41
|
+
/** The Tenant's artifact, skill and disk version limits. */
|
|
42
|
+
export declare function readTenantLimits(t: SettingsReader): Promise<StorageLimits>;
|
|
43
|
+
/** The limits in force for an Environment whose manifest sets `set`: each within the Tenant's. */
|
|
44
|
+
export declare function limitsInForce(tenant: StorageLimits, set: EnvironmentLimits | undefined): StorageLimits;
|
|
45
|
+
/** Why a manifest's `limits` cannot be made: each limit above the Tenant's. */
|
|
46
|
+
export declare function limitIssues(set: EnvironmentLimits | undefined, tenant: StorageLimits): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Why `labels` cannot be set: more than the Tenant's Runtime limit `count` names
|
|
49
|
+
* (`environmentLabels` or `artifactLabels`), or a key or a value longer than `labelKeyChars` or
|
|
50
|
+
* `labelValueChars`.
|
|
51
|
+
*/
|
|
52
|
+
export declare function labelIssues(labels: Readonly<Record<string, string>> | undefined, limits: RuntimeLimits, count?: "environmentLabels" | "artifactLabels"): string[];
|
|
53
|
+
/** The parts of a manifest whose shape the Tenant's Runtime limits set. */
|
|
54
|
+
export interface ManifestShape {
|
|
55
|
+
readonly description?: string | undefined;
|
|
56
|
+
readonly setup?: readonly EnvironmentSetupCommand[] | undefined;
|
|
57
|
+
readonly files?: readonly EnvironmentFile[] | undefined;
|
|
58
|
+
readonly secrets?: EnvironmentSecretSlots | undefined;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Why a secret write does not fit the Tenant's Runtime limits: an `environment_secret`'s or an
|
|
62
|
+
* `environment_variable`'s value past `secretValueChars`, or more hosts than `secretHosts`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function secretRequestIssues(request: {
|
|
65
|
+
readonly type: string;
|
|
66
|
+
readonly value?: unknown;
|
|
67
|
+
readonly allowedHosts?: readonly string[];
|
|
68
|
+
}, limits: RuntimeLimits): string[];
|
|
69
|
+
/**
|
|
70
|
+
* Why a tool policy does not fit the Tenant's Runtime limits: more rules than `toolRules`, a
|
|
71
|
+
* selector or a `when` path past `toolRuleFieldChars`, a `reason` or a `matches` past
|
|
72
|
+
* `toolRuleTextChars`.
|
|
73
|
+
*/
|
|
74
|
+
export declare function toolPolicyIssues(rules: readonly ToolRule[], limits: RuntimeLimits): string[];
|
|
75
|
+
/** Why a set of model budgets does not fit: more than the Tenant's Runtime limit `modelBudgets`. */
|
|
76
|
+
export declare function budgetIssues(budgets: readonly unknown[], limits: RuntimeLimits): string[];
|
|
77
|
+
/**
|
|
78
|
+
* Why an agent's `requires` does not fit: more secrets than an Environment's manifest may declare
|
|
79
|
+
* slots for (the Runtime limit `environmentSecretSlots`).
|
|
80
|
+
*/
|
|
81
|
+
export declare function requiresIssues(requires: {
|
|
82
|
+
readonly secrets?: readonly string[];
|
|
83
|
+
} | undefined, limits: RuntimeLimits): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Why a manifest (inline, or saved) does not fit the Tenant's Runtime limits: its description,
|
|
86
|
+
* its setup commands, their lengths and timeouts, its input file entries and the files of each,
|
|
87
|
+
* and its secret slots, their descriptions and hosts.
|
|
88
|
+
*/
|
|
89
|
+
export declare function manifestShapeIssues(manifest: ManifestShape, limits: RuntimeLimits): string[];
|
|
90
|
+
export {};
|