@nylorun/runtime 0.20.0-beta → 0.21.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.
Files changed (95) hide show
  1. package/CHANGELOG.md +64 -0
  2. package/README.md +15 -10
  3. package/dist/api/components.d.ts +155 -36
  4. package/dist/api/components.js +3 -3
  5. package/dist/api/http/app.js +2 -0
  6. package/dist/api/http/routes/mcp.d.ts +9 -0
  7. package/dist/api/http/routes/mcp.js +32 -0
  8. package/dist/api/http/routes/vaults.d.ts +1 -9
  9. package/dist/api/http/routes/vaults.js +3 -103
  10. package/dist/api/openapi.js +6 -3
  11. package/dist/artifacts/media-types.d.ts +2 -0
  12. package/dist/artifacts/media-types.js +5 -0
  13. package/dist/gates/http-tool.d.ts +7 -0
  14. package/dist/gates/http-tool.js +34 -3
  15. package/dist/gates/mcp-handler.d.ts +4 -1
  16. package/dist/gates/mcp-handler.js +64 -8
  17. package/dist/gates/tenant-vaults.d.ts +9 -15
  18. package/dist/gates/tenant-vaults.js +9 -17
  19. package/dist/gates/tool-client.js +36 -17
  20. package/dist/gates/tool-contract.d.ts +19 -3
  21. package/dist/gates/tool-contract.js +9 -3
  22. package/dist/gates/tool-gate.d.ts +2 -2
  23. package/dist/harness/calls.d.ts +2 -0
  24. package/dist/harness/calls.js +13 -3
  25. package/dist/harness-api/record.d.ts +7 -6
  26. package/dist/harness-api/record.js +99 -17
  27. package/dist/harness-api/server.js +35 -27
  28. package/dist/harness-api/start.js +2 -1
  29. package/dist/host/config-for.d.ts +0 -2
  30. package/dist/host/config-for.js +0 -1
  31. package/dist/host/gates.js +0 -1
  32. package/dist/host/main.js +13 -3
  33. package/dist/host/stack-config.d.ts +5 -8
  34. package/dist/host/stack-config.js +8 -19
  35. package/dist/index.d.ts +0 -1
  36. package/dist/keys/client.js +1 -9
  37. package/dist/keys/keys.d.ts +11 -13
  38. package/dist/keys/keys.js +5 -7
  39. package/dist/management-openapi.json +315 -345
  40. package/dist/mcp/connect.d.ts +159 -13
  41. package/dist/mcp/connect.js +538 -55
  42. package/dist/mcp/pool.d.ts +11 -2
  43. package/dist/mcp/pool.js +74 -27
  44. package/dist/mcp/preview.d.ts +35 -0
  45. package/dist/mcp/preview.js +215 -0
  46. package/dist/mcp/snapshot.d.ts +79 -5
  47. package/dist/mcp/snapshot.js +145 -27
  48. package/dist/model/catalog.d.ts +14 -0
  49. package/dist/model/catalog.js +14 -1
  50. package/dist/model/pi-model.js +36 -2
  51. package/dist/openapi.json +362 -123
  52. package/dist/redact.d.ts +19 -1
  53. package/dist/redact.js +42 -13
  54. package/dist/sandbox/session-sandbox.d.ts +7 -0
  55. package/dist/sandbox/session-sandbox.js +89 -28
  56. package/dist/store/postgres/drizzle/0016_mcp_oauth_removed.sql +5 -0
  57. package/dist/store/postgres/drizzle/meta/_journal.json +7 -0
  58. package/dist/store/postgres/schema.d.ts +2 -261
  59. package/dist/store/postgres/schema.js +2 -36
  60. package/dist/store/postgres/store.js +1 -18
  61. package/dist/store/postgres/tenant.d.ts +14 -0
  62. package/dist/store/postgres/tenant.js +22 -2
  63. package/dist/store/types.d.ts +2 -8
  64. package/dist/tenant/artifact-tool.d.ts +9 -1
  65. package/dist/tenant/artifact-tool.js +113 -9
  66. package/dist/tenant/context.d.ts +5 -6
  67. package/dist/tenant/effects.d.ts +2 -2
  68. package/dist/tenant/effects.js +20 -5
  69. package/dist/tenant/ephemeral.d.ts +0 -6
  70. package/dist/tenant/ephemeral.js +0 -1
  71. package/dist/tenant/outbound.d.ts +14 -5
  72. package/dist/tenant/outbound.js +21 -9
  73. package/dist/tenant/runtime.js +9 -14
  74. package/dist/tenant/sessions.js +20 -3
  75. package/dist/tenant/store-pg.js +9 -0
  76. package/dist/tenant/tool-results.d.ts +57 -0
  77. package/dist/tenant/tool-results.js +298 -0
  78. package/dist/tenant/tool-search.d.ts +43 -0
  79. package/dist/tenant/tool-search.js +100 -0
  80. package/dist/tenant/transcript.d.ts +108 -0
  81. package/dist/tenant/transcript.js +13 -0
  82. package/dist/tenant/types.d.ts +0 -14
  83. package/dist/vault/crypto.d.ts +0 -7
  84. package/dist/vault/crypto.js +0 -16
  85. package/dist/vault/headers.d.ts +17 -0
  86. package/dist/vault/headers.js +61 -0
  87. package/dist/vault/service.d.ts +11 -68
  88. package/dist/vault/service.js +83 -438
  89. package/dist/vault/sources.d.ts +20 -48
  90. package/dist/vault/sources.js +25 -189
  91. package/dist/version.d.ts +1 -1
  92. package/dist/version.js +1 -1
  93. package/package.json +3 -3
  94. package/dist/vault/oauth.d.ts +0 -55
  95. package/dist/vault/oauth.js +0 -186
@@ -102,14 +102,27 @@ export function toolCompleted(request, value) {
102
102
  error: {
103
103
  code: String(outcome.code ?? "tool.failed"),
104
104
  message: String(outcome.message ?? ""),
105
+ // `credential_rejected` (R2b C1): which server, and which vault's credential.
106
+ ...(typeof outcome.server === "string" ? { server: outcome.server } : {}),
107
+ ...(outcome.vault === "installation" || outcome.vault === "user"
108
+ ? { vault: outcome.vault }
109
+ : {}),
110
+ ...(typeof outcome.retryable === "boolean" ? { retryable: outcome.retryable } : {}),
105
111
  },
106
112
  }
107
113
  : { output: outcome?.kind === "completed" ? outcome.output : value };
114
+ // Credential values the server echoed, replaced before the outcome was recorded (R2b C8).
115
+ const redacted = (outcome?.kind === "completed" || outcome?.kind === "failed") &&
116
+ typeof outcome.redacted === "number" &&
117
+ outcome.redacted > 0
118
+ ? { redacted: outcome.redacted }
119
+ : {};
108
120
  return {
109
121
  ...ids,
110
122
  capabilityId: request.capabilityId,
111
123
  toolName: request.toolName,
112
124
  ...result,
125
+ ...redacted,
113
126
  ...agentOf(request),
114
127
  };
115
128
  }
@@ -8,7 +8,6 @@ import type { SessionStore } from "../store/types.js";
8
8
  import type { SandboxBackend } from "../sandbox/types.js";
9
9
  import type { TenantCause } from "./cause.js";
10
10
  import type { OutboundPolicy } from "./outbound.js";
11
- import type { ResolverConfig } from "../vault/sources.js";
12
11
  import type { TenantWorker } from "./worker.js";
13
12
  export type TenantMode = "shared" | "ephemeral" | "test";
14
13
  /**
@@ -75,24 +74,11 @@ export interface TenantConfig {
75
74
  * never reads ambient process environment for flow limits.
76
75
  */
77
76
  flowEnv?: Readonly<Record<string, string | undefined>>;
78
- vaultFetch?: typeof fetch;
79
77
  /**
80
78
  * The trusted issuers whose JWTs the Tenant API accepts (Host feature `trusted-issuers`), from
81
79
  * the identity file (`NYLORUN_IDENTITY_FILE`). Absent: none.
82
80
  */
83
81
  issuers?: import("./issuers.js").TrustedIssuers;
84
- /**
85
- * The operator's credential resolver (F9 C1), asked for a person's MCP credential when the
86
- * session's vaults hold none. Used where this process authorizes MCP calls itself (embedding,
87
- * the ephemeral Runtime, tests); the gateway takes `NYLORUN_RESOLVER_*` instead.
88
- */
89
- resolver?: ResolverConfig;
90
- /**
91
- * The URL browsers reach this Tenant API at (`NYLORUN_PUBLIC_URL`, no trailing slash): the
92
- * base of the MCP OAuth callback, `<publicUrl>/v1/oauth/callback` (F9 C2). Absent (embedding,
93
- * tests): the origin the start request was sent to.
94
- */
95
- publicUrl?: string;
96
82
  logger: Logger;
97
83
  }
98
84
  export interface Logger {
@@ -10,10 +10,3 @@ export declare function encryptSecret(kek: Buffer, aad: Buffer, plaintext: Buffe
10
10
  kekId: string;
11
11
  };
12
12
  export declare function decryptSecret(kek: Buffer, aad: Buffer, nonce: Buffer, ciphertext: Buffer, wrappedDek: Buffer, expectedKekId: string): Buffer;
13
- /**
14
- * `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
15
- * seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
16
- */
17
- export declare function sealBytes(kek: Buffer, aad: Buffer, plaintext: Buffer): Buffer;
18
- /** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
19
- export declare function openBytes(kek: Buffer, aad: Buffer, sealed: Buffer, expectedKekId: string): Buffer;
@@ -78,19 +78,3 @@ function unwrapKey(kek, wrapped) {
78
78
  throw new VaultCryptoError();
79
79
  }
80
80
  }
81
- /** The wrapped DEK's length: its nonce, the 32-byte key and the tag. */
82
- const WRAPPED_DEK_BYTES = IV_BYTES + 32 + TAG_BYTES;
83
- /**
84
- * `encryptSecret` in one column: nonce, wrapped DEK, then ciphertext and tag. For rows that
85
- * seal several short secrets under one `kekId` (`oauth_pending`, F9 C2).
86
- */
87
- export function sealBytes(kek, aad, plaintext) {
88
- const sealed = encryptSecret(kek, aad, plaintext);
89
- return Buffer.concat([sealed.nonce, sealed.wrappedDek, sealed.ciphertext]);
90
- }
91
- /** Opens what `sealBytes` sealed; `VaultCryptoError` when it cannot. */
92
- export function openBytes(kek, aad, sealed, expectedKekId) {
93
- if (sealed.length < IV_BYTES + WRAPPED_DEK_BYTES + TAG_BYTES)
94
- throw new VaultCryptoError();
95
- return decryptSecret(kek, aad, sealed.subarray(0, IV_BYTES), sealed.subarray(IV_BYTES + WRAPPED_DEK_BYTES), sealed.subarray(IV_BYTES, IV_BYTES + WRAPPED_DEK_BYTES), expectedKekId);
96
- }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
3
+ * value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
4
+ * the session owner and is no secret, so it is left out.
5
+ */
6
+ export declare function credentialSecrets(headers: Readonly<Record<string, string>>, identity?: {
7
+ readonly header: string;
8
+ }): string[];
9
+ /** True when a credential may not send `name`: a transport header or `Nylorun-*`. */
10
+ export declare function isReservedHeader(name: string): boolean;
11
+ /**
12
+ * The header names a credential sends, lower-cased: a `headers` map's, `authorization` for a
13
+ * `bearer`. Refuses a reserved name, and an identity header that repeats one of them.
14
+ */
15
+ export declare function checkCredentialHeaders(names: readonly string[], identity: {
16
+ readonly header: string;
17
+ } | undefined): string[];
@@ -0,0 +1,61 @@
1
+ /**
2
+ * The headers a vault credential may send (R2b C2): a `headers` credential's names and an
3
+ * identity header. The core schema checks their syntax; this module refuses the names the
4
+ * Runtime sets itself, so a credential can never replace them.
5
+ */
6
+ import { VaultError } from "./error.js";
7
+ /** Headers the MCP transport and the HTTP tool call own. */
8
+ const RESERVED_HEADERS = new Set([
9
+ "host",
10
+ "content-length",
11
+ "content-type",
12
+ "transfer-encoding",
13
+ "connection",
14
+ "accept",
15
+ "mcp-session-id",
16
+ "mcp-protocol-version",
17
+ "last-event-id",
18
+ "idempotency-key",
19
+ ]);
20
+ /**
21
+ * The secret values of a credential's `headers`, as a server could echo them (R2b C8, Q16): each
22
+ * value of at least 8 characters, and the token of a `Bearer …` value. The identity header names
23
+ * the session owner and is no secret, so it is left out.
24
+ */
25
+ export function credentialSecrets(headers, identity) {
26
+ const skip = identity?.header.toLowerCase();
27
+ const secrets = new Set();
28
+ for (const [name, value] of Object.entries(headers)) {
29
+ if (name.toLowerCase() === skip)
30
+ continue;
31
+ if (value.length >= 8)
32
+ secrets.add(value);
33
+ const bearer = /^bearer\s+(\S+)\s*$/iu.exec(value)?.[1];
34
+ if (bearer !== undefined && bearer.length >= 8)
35
+ secrets.add(bearer);
36
+ }
37
+ return [...secrets];
38
+ }
39
+ /** True when a credential may not send `name`: a transport header or `Nylorun-*`. */
40
+ export function isReservedHeader(name) {
41
+ const lower = name.toLowerCase();
42
+ return RESERVED_HEADERS.has(lower) || lower.startsWith("nylorun-");
43
+ }
44
+ /**
45
+ * The header names a credential sends, lower-cased: a `headers` map's, `authorization` for a
46
+ * `bearer`. Refuses a reserved name, and an identity header that repeats one of them.
47
+ */
48
+ export function checkCredentialHeaders(names, identity) {
49
+ const lower = names.map((name) => name.toLowerCase());
50
+ for (const name of lower)
51
+ if (isReservedHeader(name))
52
+ throw new VaultError(400, `A credential may not set the header ${name}`);
53
+ if (identity) {
54
+ const header = identity.header.toLowerCase();
55
+ if (isReservedHeader(header))
56
+ throw new VaultError(400, `The identity header may not be ${header}`);
57
+ if (lower.includes(header))
58
+ throw new VaultError(400, `The identity header ${header} is already one of the credential's headers`);
59
+ }
60
+ return lower;
61
+ }
@@ -1,7 +1,5 @@
1
- import type { CreateCredentialRequest, CreateVaultRequest, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
1
+ import type { CreateCredentialRequest, CreateVaultRequest, CredentialIdentity, CredentialInfo, CredentialSelection, HostModelProviderInfo, HostModelView, PutHostModelRequest, CustomModelSettings, RotateCredentialRequest, SelectHostModelRequest, VaultInfo } from "@nylorun/core/contracts";
2
2
  import type { SessionStore, Tx, VaultCredentialRow } from "../store/types.js";
3
- /** How long an MCP OAuth connect may take between its start and its callback (F9 C2). */
4
- export declare const OAUTH_PENDING_MS: number;
5
3
  export declare const HOST_VAULT_ID = "host";
6
4
  export type HostModelSecret = {
7
5
  provider: string;
@@ -27,50 +25,28 @@ export type AuthorizeResult = {
27
25
  } | {
28
26
  status: "authorized";
29
27
  url: string;
30
- /** A vault credential's `authorization`, or the headers the credential resolver gave. */
28
+ /** The vault credential's headers: `authorization` for a `bearer`, a `headers` map's. */
31
29
  headers: Record<string, string>;
30
+ /** Where the request goes instead of `url` (a gateway), when the credential has one. */
31
+ via?: string;
32
+ /** The header that names the session owner; `sessionCredentials` adds it. */
33
+ identity?: CredentialIdentity;
34
+ /** The scope of the vault the credential is in. */
35
+ vault: "installation" | "user";
32
36
  } | {
33
37
  status: "refused";
34
38
  url: string;
35
39
  credentialIds: string[];
36
40
  reason: string;
37
41
  };
38
- /** `startOAuth`'s input: the route's body, the installation vault and the callback URL. */
39
- export interface StartOAuthInput {
40
- vaultId: string;
41
- /** The MCP server's name, the credential's name. */
42
- server: string;
43
- url: string;
44
- clientId?: string;
45
- /** `<public URL>/v1/oauth/callback`. */
46
- redirectUri: string;
47
- }
48
- /** The callback's query: `code`, or the authorization server's `error`. */
49
- export interface FinishOAuthInput {
50
- state: string;
51
- code?: string;
52
- error?: string;
53
- }
54
42
  export interface VaultServiceOptions {
55
43
  store: SessionStore;
56
44
  /** The Tenant key-encryption key. May read or create the key file. */
57
45
  kek: () => Buffer;
58
- /**
59
- * OAuth refresh and MCP OAuth connect (discovery, registration, the code exchange), always
60
- * outside a transaction: the Host's `guardedFetch` (`tenant/outbound.ts`) unless a test or
61
- * an embedder injects one.
62
- */
63
- fetch: typeof fetch;
64
- /** How long a token endpoint may take to answer a refresh. Default 30 s. */
65
- refreshTimeoutMs?: number;
66
46
  }
67
47
  export declare class VaultService {
68
48
  private readonly store;
69
49
  private readonly kek;
70
- private readonly fetchImpl;
71
- private readonly refreshTimeoutMs;
72
- /** Refreshes in progress by credential id: concurrent uses share one. */
73
- private readonly refreshing;
74
50
  constructor(options: VaultServiceOptions);
75
51
  createVault(body: CreateVaultRequest): Promise<VaultInfo>;
76
52
  /**
@@ -101,54 +77,21 @@ export declare class VaultService {
101
77
  /** Writes the `attach` audit row. Run in the session PUT transaction. */
102
78
  recordAttachment(t: Tx, sessionId: string, vaultIds: readonly string[]): Promise<void>;
103
79
  authorize(input: {
104
- sessionId: string;
80
+ /** The session the request is made for; absent for a tool preview (R2b C12). */
81
+ sessionId?: string;
105
82
  vaultIds: readonly string[];
106
83
  credentialSelections: readonly CredentialSelection[];
107
84
  url: string;
108
85
  serverName?: string;
109
86
  }): Promise<AuthorizeResult>;
110
- /**
111
- * Starts signing the installation in to the MCP server at `url`: discovery, a client (the
112
- * given id, or one registered now), an S256 PKCE verifier and a `state`, kept for ten minutes
113
- * in a pending row (verifier and secret sealed, state hashed). Returns where to send the
114
- * browser. Installation vaults only.
115
- */
116
- startOAuth(input: StartOAuthInput): Promise<{
117
- authorizeUrl: string;
118
- expiresAt: string;
119
- }>;
120
- /**
121
- * Finishes a connect from its callback: takes the pending row of `state` (deleted whatever
122
- * happens next), exchanges the code, and seals an `oauth` credential bound to the server's
123
- * URL in the installation vault, rotating the vault's OAuth credential for that URL when it
124
- * has one.
125
- */
126
- finishOAuth(input: FinishOAuthInput): Promise<{
127
- vaultId: string;
128
- credentialId: string;
129
- }>;
130
- /** An installation vault, else the 404 of a missing one (a user vault is a 400). */
131
- private installationVault;
132
87
  getHostModel(): Promise<HostModelView>;
133
88
  listHostProviders(): Promise<{
134
89
  providers: HostModelProviderInfo[];
135
90
  }>;
136
91
  putHostModel(body: PutHostModelRequest): Promise<HostModelView>;
137
92
  selectHostModel(body: SelectHostModelRequest): Promise<HostModelView>;
138
- /** Calls the token endpoint outside any transaction, then stores the result. */
139
- /**
140
- * Refreshes an OAuth grant once for every concurrent use in this process. A rotating refresh
141
- * token is valid once, so two refreshes with it would fail the second, or make the provider
142
- * revoke the grant.
143
- */
144
- private refreshShared;
145
- /** Uses a token another process refreshed instead of refreshing again, before and after. */
146
- private refreshLatest;
147
- /** The credential's payload when it was rotated after `row` was read and is not due. */
148
- private rotatedSince;
149
- private refresh;
150
93
  private replay;
151
- /** Re-seals a user credential's payload and returns the updated row. */
94
+ /** Re-seals a user credential's payload with its new binding and returns the updated row. */
152
95
  private writePayload;
153
96
  private vaultInfo;
154
97
  private ensureHostVault;