@synoikia/core 0.1.0

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 (209) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app.d.ts +79 -0
  3. package/dist/app.js +155 -0
  4. package/dist/app.js.map +1 -0
  5. package/dist/approvals/links.d.ts +23 -0
  6. package/dist/approvals/links.js +49 -0
  7. package/dist/approvals/links.js.map +1 -0
  8. package/dist/approvals/service.d.ts +110 -0
  9. package/dist/approvals/service.js +182 -0
  10. package/dist/approvals/service.js.map +1 -0
  11. package/dist/audit-query.d.ts +52 -0
  12. package/dist/audit-query.js +116 -0
  13. package/dist/audit-query.js.map +1 -0
  14. package/dist/audit.d.ts +5 -0
  15. package/dist/audit.js +8 -0
  16. package/dist/audit.js.map +1 -0
  17. package/dist/auth/mcp-auth.d.ts +50 -0
  18. package/dist/auth/mcp-auth.js +160 -0
  19. package/dist/auth/mcp-auth.js.map +1 -0
  20. package/dist/auth/mcp-tokens.d.ts +57 -0
  21. package/dist/auth/mcp-tokens.js +143 -0
  22. package/dist/auth/mcp-tokens.js.map +1 -0
  23. package/dist/auth/oauth.d.ts +188 -0
  24. package/dist/auth/oauth.js +470 -0
  25. package/dist/auth/oauth.js.map +1 -0
  26. package/dist/auth/oidc.d.ts +110 -0
  27. package/dist/auth/oidc.js +221 -0
  28. package/dist/auth/oidc.js.map +1 -0
  29. package/dist/auth/sessions.d.ts +34 -0
  30. package/dist/auth/sessions.js +83 -0
  31. package/dist/auth/sessions.js.map +1 -0
  32. package/dist/auth/throttle.d.ts +31 -0
  33. package/dist/auth/throttle.js +52 -0
  34. package/dist/auth/throttle.js.map +1 -0
  35. package/dist/auth/tokens.d.ts +7 -0
  36. package/dist/auth/tokens.js +40 -0
  37. package/dist/auth/tokens.js.map +1 -0
  38. package/dist/auth/totp.d.ts +14 -0
  39. package/dist/auth/totp.js +81 -0
  40. package/dist/auth/totp.js.map +1 -0
  41. package/dist/auth/users.d.ts +70 -0
  42. package/dist/auth/users.js +296 -0
  43. package/dist/auth/users.js.map +1 -0
  44. package/dist/catalog/groups.d.ts +102 -0
  45. package/dist/catalog/groups.js +363 -0
  46. package/dist/catalog/groups.js.map +1 -0
  47. package/dist/catalog/registry.d.ts +25 -0
  48. package/dist/catalog/registry.js +80 -0
  49. package/dist/catalog/registry.js.map +1 -0
  50. package/dist/catalog/rules.d.ts +128 -0
  51. package/dist/catalog/rules.js +178 -0
  52. package/dist/catalog/rules.js.map +1 -0
  53. package/dist/catalog/sync.d.ts +29 -0
  54. package/dist/catalog/sync.js +222 -0
  55. package/dist/catalog/sync.js.map +1 -0
  56. package/dist/cli.d.ts +3 -0
  57. package/dist/cli.js +83 -0
  58. package/dist/cli.js.map +1 -0
  59. package/dist/config/env.d.ts +40 -0
  60. package/dist/config/env.js +71 -0
  61. package/dist/config/env.js.map +1 -0
  62. package/dist/crypto/index.d.ts +28 -0
  63. package/dist/crypto/index.js +88 -0
  64. package/dist/crypto/index.js.map +1 -0
  65. package/dist/crypto/rotate.d.ts +15 -0
  66. package/dist/crypto/rotate.js +61 -0
  67. package/dist/crypto/rotate.js.map +1 -0
  68. package/dist/db/index.d.ts +17 -0
  69. package/dist/db/index.js +29 -0
  70. package/dist/db/index.js.map +1 -0
  71. package/dist/db/schema.d.ts +4389 -0
  72. package/dist/db/schema.js +372 -0
  73. package/dist/db/schema.js.map +1 -0
  74. package/dist/endpoints/slug.d.ts +3 -0
  75. package/dist/endpoints/slug.js +7 -0
  76. package/dist/endpoints/slug.js.map +1 -0
  77. package/dist/errors.d.ts +16 -0
  78. package/dist/errors.js +29 -0
  79. package/dist/errors.js.map +1 -0
  80. package/dist/events.d.ts +43 -0
  81. package/dist/events.js +16 -0
  82. package/dist/events.js.map +1 -0
  83. package/dist/gate/access.d.ts +47 -0
  84. package/dist/gate/access.js +48 -0
  85. package/dist/gate/access.js.map +1 -0
  86. package/dist/gate/attestation.d.ts +23 -0
  87. package/dist/gate/attestation.js +33 -0
  88. package/dist/gate/attestation.js.map +1 -0
  89. package/dist/gate/canonical.d.ts +3 -0
  90. package/dist/gate/canonical.js +11 -0
  91. package/dist/gate/canonical.js.map +1 -0
  92. package/dist/gate/match.d.ts +67 -0
  93. package/dist/gate/match.js +133 -0
  94. package/dist/gate/match.js.map +1 -0
  95. package/dist/gate/pipeline.d.ts +61 -0
  96. package/dist/gate/pipeline.js +284 -0
  97. package/dist/gate/pipeline.js.map +1 -0
  98. package/dist/gate/preapproval.d.ts +24 -0
  99. package/dist/gate/preapproval.js +48 -0
  100. package/dist/gate/preapproval.js.map +1 -0
  101. package/dist/gate/rate-limit.d.ts +35 -0
  102. package/dist/gate/rate-limit.js +82 -0
  103. package/dist/gate/rate-limit.js.map +1 -0
  104. package/dist/gate/redact.d.ts +23 -0
  105. package/dist/gate/redact.js +87 -0
  106. package/dist/gate/redact.js.map +1 -0
  107. package/dist/http/admin/auth.d.ts +33 -0
  108. package/dist/http/admin/auth.js +338 -0
  109. package/dist/http/admin/auth.js.map +1 -0
  110. package/dist/http/admin/instances.d.ts +5 -0
  111. package/dist/http/admin/instances.js +193 -0
  112. package/dist/http/admin/instances.js.map +1 -0
  113. package/dist/http/admin/system.d.ts +5 -0
  114. package/dist/http/admin/system.js +225 -0
  115. package/dist/http/admin/system.js.map +1 -0
  116. package/dist/http/admin-app.d.ts +9 -0
  117. package/dist/http/admin-app.js +51 -0
  118. package/dist/http/admin-app.js.map +1 -0
  119. package/dist/http/common.d.ts +32 -0
  120. package/dist/http/common.js +80 -0
  121. package/dist/http/common.js.map +1 -0
  122. package/dist/http/mcp/approval-routes.d.ts +11 -0
  123. package/dist/http/mcp/approval-routes.js +117 -0
  124. package/dist/http/mcp/approval-routes.js.map +1 -0
  125. package/dist/http/mcp/endpoint.d.ts +32 -0
  126. package/dist/http/mcp/endpoint.js +293 -0
  127. package/dist/http/mcp/endpoint.js.map +1 -0
  128. package/dist/http/mcp/oauth-routes.d.ts +29 -0
  129. package/dist/http/mcp/oauth-routes.js +465 -0
  130. package/dist/http/mcp/oauth-routes.js.map +1 -0
  131. package/dist/http/mcp/pages.d.ts +63 -0
  132. package/dist/http/mcp/pages.js +200 -0
  133. package/dist/http/mcp/pages.js.map +1 -0
  134. package/dist/http/mcp-app.d.ts +7 -0
  135. package/dist/http/mcp-app.js +30 -0
  136. package/dist/http/mcp-app.js.map +1 -0
  137. package/dist/instances/connection.d.ts +37 -0
  138. package/dist/instances/connection.js +109 -0
  139. package/dist/instances/connection.js.map +1 -0
  140. package/dist/instances/manager.d.ts +313 -0
  141. package/dist/instances/manager.js +600 -0
  142. package/dist/instances/manager.js.map +1 -0
  143. package/dist/instances/settings.d.ts +42 -0
  144. package/dist/instances/settings.js +57 -0
  145. package/dist/instances/settings.js.map +1 -0
  146. package/dist/lenient.d.ts +16 -0
  147. package/dist/lenient.js +51 -0
  148. package/dist/lenient.js.map +1 -0
  149. package/dist/lock.d.ts +28 -0
  150. package/dist/lock.js +77 -0
  151. package/dist/lock.js.map +1 -0
  152. package/dist/main.d.ts +1 -0
  153. package/dist/main.js +38 -0
  154. package/dist/main.js.map +1 -0
  155. package/dist/maintenance.d.ts +10 -0
  156. package/dist/maintenance.js +48 -0
  157. package/dist/maintenance.js.map +1 -0
  158. package/dist/notify/service.d.ts +149 -0
  159. package/dist/notify/service.js +358 -0
  160. package/dist/notify/service.js.map +1 -0
  161. package/dist/plugins/discovery.d.ts +39 -0
  162. package/dist/plugins/discovery.js +157 -0
  163. package/dist/plugins/discovery.js.map +1 -0
  164. package/dist/plugins/minisign.d.ts +25 -0
  165. package/dist/plugins/minisign.js +49 -0
  166. package/dist/plugins/minisign.js.map +1 -0
  167. package/dist/plugins/process.d.ts +55 -0
  168. package/dist/plugins/process.js +162 -0
  169. package/dist/plugins/process.js.map +1 -0
  170. package/dist/plugins/repos.d.ts +198 -0
  171. package/dist/plugins/repos.js +612 -0
  172. package/dist/plugins/repos.js.map +1 -0
  173. package/dist/plugins/supervisor.d.ts +50 -0
  174. package/dist/plugins/supervisor.js +125 -0
  175. package/dist/plugins/supervisor.js.map +1 -0
  176. package/dist/runtime/index.d.ts +28 -0
  177. package/dist/runtime/index.js +248 -0
  178. package/dist/runtime/index.js.map +1 -0
  179. package/dist/sandbox/index.d.ts +53 -0
  180. package/dist/sandbox/index.js +197 -0
  181. package/dist/sandbox/index.js.map +1 -0
  182. package/dist/server.d.ts +16 -0
  183. package/dist/server.js +36 -0
  184. package/dist/server.js.map +1 -0
  185. package/dist/settings.d.ts +43 -0
  186. package/dist/settings.js +99 -0
  187. package/dist/settings.js.map +1 -0
  188. package/dist/testing/index.d.ts +73 -0
  189. package/dist/testing/index.js +174 -0
  190. package/dist/testing/index.js.map +1 -0
  191. package/dist/version.d.ts +2 -0
  192. package/dist/version.js +4 -0
  193. package/dist/version.js.map +1 -0
  194. package/drizzle/0000_init.sql +334 -0
  195. package/drizzle/0001_totp_last_step.sql +1 -0
  196. package/drizzle/0002_access_levels.sql +16 -0
  197. package/drizzle/0003_drop_exclusion_flags.sql +2 -0
  198. package/drizzle/0004_grant_instance_ids.sql +1 -0
  199. package/drizzle/0005_rule_strict_miss.sql +1 -0
  200. package/drizzle/0006_attestation_waived.sql +1 -0
  201. package/drizzle/meta/0000_snapshot.json +2318 -0
  202. package/drizzle/meta/0001_snapshot.json +2325 -0
  203. package/drizzle/meta/0002_snapshot.json +2348 -0
  204. package/drizzle/meta/0003_snapshot.json +2332 -0
  205. package/drizzle/meta/0004_snapshot.json +2339 -0
  206. package/drizzle/meta/0005_snapshot.json +2346 -0
  207. package/drizzle/meta/0006_snapshot.json +2354 -0
  208. package/drizzle/meta/_journal.json +55 -0
  209. package/package.json +58 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nir
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/app.d.ts ADDED
@@ -0,0 +1,79 @@
1
+ import { ApprovalLinkService } from './approvals/links.js';
2
+ import { ApprovalService } from './approvals/service.js';
3
+ import { McpTokenService } from './auth/mcp-tokens.js';
4
+ import { OAuthService } from './auth/oauth.js';
5
+ import { OidcService } from './auth/oidc.js';
6
+ import { SessionService } from './auth/sessions.js';
7
+ import { LoginThrottle } from './auth/throttle.js';
8
+ import { UserService } from './auth/users.js';
9
+ import type { Config } from './config/env.js';
10
+ import { SecretBox } from './crypto/index.js';
11
+ import type { Db } from './db/index.js';
12
+ import { CoreEvents } from './events.js';
13
+ import { SlidingWindowLimiter } from './gate/rate-limit.js';
14
+ import type { GateDeps } from './gate/pipeline.js';
15
+ import { InstanceManager } from './instances/manager.js';
16
+ import type { ManagerOptions } from './instances/manager.js';
17
+ import { NotifierService } from './notify/service.js';
18
+ import type { FetchLike } from './notify/service.js';
19
+ import { syncPluginRegistry } from './plugins/discovery.js';
20
+ import { PluginRepoService } from './plugins/repos.js';
21
+ import type { FetchBytes } from './plugins/repos.js';
22
+ /**
23
+ * Wires every core service together once (design §2). Both listeners, the scheduler and tests use
24
+ * the same context, so there is exactly one database handle, one approval service, one event bus.
25
+ */
26
+ export interface AppContext {
27
+ config: Config;
28
+ db: Db;
29
+ secrets: SecretBox;
30
+ events: CoreEvents;
31
+ users: UserService;
32
+ sessions: SessionService;
33
+ throttle: LoginThrottle;
34
+ oidc: OidcService;
35
+ tokens: McpTokenService;
36
+ oauth: OAuthService;
37
+ instances: InstanceManager;
38
+ approvals: ApprovalService;
39
+ limiter: SlidingWindowLimiter;
40
+ links: ApprovalLinkService;
41
+ notifier: NotifierService;
42
+ repos: PluginRepoService;
43
+ keys: {
44
+ attestation: Buffer;
45
+ state: Buffer;
46
+ };
47
+ warnings: string[];
48
+ now: () => Date;
49
+ gateDeps(): GateDeps;
50
+ /** Re-scan plugin directories and update the registry (startup, after installs). */
51
+ discoverPlugins(): ReturnType<typeof syncPluginRegistry>;
52
+ start(): Promise<void>;
53
+ /** Registers cleanup to run when draining (e.g. closing open MCP sessions). */
54
+ onStop(fn: () => unknown): void;
55
+ /** Aborted when shutdown begins; long-lived responses (SSE) end on it. */
56
+ shutdownSignal: AbortSignal;
57
+ /**
58
+ * First step of shutdown, while the listeners still run: stops timers, cancels open approvals,
59
+ * runs the stop hooks and ends long-lived streams, so closing the listeners doesn't wait on them.
60
+ */
61
+ drain(): Promise<void>;
62
+ /** Drains (if not yet), stops plugin children and closes the database. */
63
+ stop(): Promise<void>;
64
+ }
65
+ export interface AppOptions {
66
+ now?: () => Date;
67
+ supervisor?: ManagerOptions['supervisor'];
68
+ /** Allow http:// OIDC issuers (tests, lab IdPs). */
69
+ oidcAllowInsecure?: boolean;
70
+ /** Use an in-memory database (tests). */
71
+ memoryDb?: boolean;
72
+ /** Outbound HTTP for notifications (tests). */
73
+ notifyFetch?: FetchLike;
74
+ notifyRetryDelaysMs?: number[];
75
+ /** Outbound HTTP for plugin repositories (tests). */
76
+ repoFetch?: FetchBytes;
77
+ repoAllowHttp?: boolean;
78
+ }
79
+ export declare function createAppContext(config: Config, opts?: AppOptions): Promise<AppContext>;
package/dist/app.js ADDED
@@ -0,0 +1,155 @@
1
+ import path from 'node:path';
2
+ import { ApprovalLinkService } from './approvals/links.js';
3
+ import { ApprovalService } from './approvals/service.js';
4
+ import { McpTokenService } from './auth/mcp-tokens.js';
5
+ import { OAuthService } from './auth/oauth.js';
6
+ import { OidcService } from './auth/oidc.js';
7
+ import { SessionService } from './auth/sessions.js';
8
+ import { LoginThrottle } from './auth/throttle.js';
9
+ import { UserService } from './auth/users.js';
10
+ import { loadMasterKey, SecretBox } from './crypto/index.js';
11
+ import { openDatabase } from './db/index.js';
12
+ import { CoreEvents } from './events.js';
13
+ import { SlidingWindowLimiter } from './gate/rate-limit.js';
14
+ import { InstanceManager } from './instances/manager.js';
15
+ import { runHousekeeping } from './maintenance.js';
16
+ import { NotifierService } from './notify/service.js';
17
+ import { discoverPlugins, syncPluginRegistry } from './plugins/discovery.js';
18
+ import { PluginRepoService } from './plugins/repos.js';
19
+ import { sandboxesRunning } from './runtime/index.js';
20
+ import { normalizeStoredSettings } from './settings.js';
21
+ export async function createAppContext(config, opts = {}) {
22
+ const now = opts.now ?? (() => new Date());
23
+ const warnings = [];
24
+ const master = loadMasterKey({ envKey: config.MASTER_KEY, dataDir: config.DATA_DIR });
25
+ if (master.warning)
26
+ warnings.push(master.warning);
27
+ const secrets = SecretBox.fromKey(master.key);
28
+ const db = opts.memoryDb ? openDatabase(':memory:') : openDatabase({ dataDir: config.DATA_DIR });
29
+ const orphans = ApprovalService.denyOrphans(db);
30
+ if (orphans > 0)
31
+ warnings.push(`Denied ${orphans} approval(s) left pending by the previous run`);
32
+ const events = new CoreEvents();
33
+ const users = new UserService(db, secrets);
34
+ const boot = await users.bootstrap(config.ADMIN_BOOTSTRAP_USERNAME, config.ADMIN_BOOTSTRAP_PASSWORD);
35
+ if (boot === 'created')
36
+ warnings.push(`Created admin user "${config.ADMIN_BOOTSTRAP_USERNAME}" from ADMIN_BOOTSTRAP_*`);
37
+ if (boot === 'ignored')
38
+ warnings.push('ADMIN_BOOTSTRAP_* is set but users already exist; it is ignored — remove it');
39
+ const instances = new InstanceManager({ db, secrets, events, now, supervisor: opts.supervisor });
40
+ // Stored JSON from an earlier release is brought to the current schemas once, before anything reads it.
41
+ normalizeStoredSettings(db);
42
+ instances.normalizeStoredSettings();
43
+ if (!config.PUBLIC_MCP_URL)
44
+ warnings.push('PUBLIC_MCP_URL is not set: OAuth is off (bearer tokens still work). Set it to the public address of the MCP port.');
45
+ const links = new ApprovalLinkService(db, now);
46
+ const approvals = new ApprovalService(db, links, now);
47
+ // An approval must not outlive the plugin process and configuration it was asked for.
48
+ events.on('instance.status', ({ instanceId, status }) => {
49
+ if (status === 'stopped')
50
+ approvals.cancelForInstance(instanceId, 'endpoint_stopped');
51
+ });
52
+ const keys = {
53
+ attestation: secrets.deriveKey('attestation'),
54
+ state: secrets.deriveKey('signed-state'),
55
+ };
56
+ const limiter = new SlidingWindowLimiter();
57
+ const notifier = new NotifierService(db, secrets, {
58
+ fetch: opts.notifyFetch,
59
+ retryDelaysMs: opts.notifyRetryDelaysMs,
60
+ now,
61
+ });
62
+ const unsubscribeNotifier = notifier.subscribe(events);
63
+ const discover = () => syncPluginRegistry(db, discoverPlugins([
64
+ { dir: config.CORE_PLUGINS_DIR, source: 'core' },
65
+ { dir: path.join(config.DATA_DIR, 'plugins'), source: 'repo' },
66
+ ]), { autoEnableCore: config.CORE_PLUGINS_AUTOENABLE });
67
+ const registered = discover();
68
+ const repos = new PluginRepoService({
69
+ db,
70
+ dataDir: config.DATA_DIR,
71
+ fetch: opts.repoFetch,
72
+ allowHttp: opts.repoAllowHttp,
73
+ now,
74
+ discover,
75
+ stopPlugin: (id) => instances.stopPlugin(id),
76
+ startPlugin: (id) => instances.startPlugin(id),
77
+ });
78
+ for (const id of registered.rejected)
79
+ warnings.push(`Plugin ${id} was ignored: its id is already taken`);
80
+ const timers = [];
81
+ const stopHooks = [];
82
+ const draining = new AbortController();
83
+ const drain = async () => {
84
+ if (draining.signal.aborted)
85
+ return;
86
+ draining.abort();
87
+ for (const t of timers)
88
+ clearInterval(t);
89
+ // Cancelled approvals end their tool calls first, so closing sessions doesn't cut them off mid-reply.
90
+ approvals.cancelAll('shutdown');
91
+ // Give the scripts those calls belonged to a moment to return their result to the client.
92
+ if (sandboxesRunning() > 0) {
93
+ for (const until = Date.now() + 2000; sandboxesRunning() > 0 && Date.now() < until;)
94
+ await new Promise((r) => setTimeout(r, 20));
95
+ await new Promise((r) => setTimeout(r, 50)); // the reply is written after the script returns
96
+ }
97
+ for (const fn of stopHooks.splice(0).reverse())
98
+ await fn();
99
+ };
100
+ const ctx = {
101
+ config,
102
+ db,
103
+ secrets,
104
+ events,
105
+ users,
106
+ sessions: new SessionService(db, secrets.deriveKey('session-pepper'), now),
107
+ throttle: new LoginThrottle(),
108
+ oidc: new OidcService(db, secrets, keys.state, opts.oidcAllowInsecure ?? false),
109
+ tokens: new McpTokenService(db, now),
110
+ oauth: new OAuthService(db, now),
111
+ instances,
112
+ approvals,
113
+ limiter,
114
+ links,
115
+ notifier,
116
+ repos,
117
+ keys,
118
+ warnings,
119
+ now,
120
+ gateDeps: () => ({ db, approvals, limiter, attestationKey: keys.attestation, now }),
121
+ discoverPlugins: discover,
122
+ async start() {
123
+ await instances.startAll();
124
+ // Hourly: daily backstop sync per instance and per plugin repo (design §10), staggered by their
125
+ // own last-run times, plus housekeeping.
126
+ const hourly = () => {
127
+ void instances.syncStale();
128
+ void repos.refreshStale();
129
+ try {
130
+ runHousekeeping(ctx);
131
+ }
132
+ catch (err) {
133
+ console.error('housekeeping failed', err);
134
+ }
135
+ };
136
+ hourly();
137
+ timers.push(setInterval(hourly, 60 * 60_000).unref());
138
+ },
139
+ onStop(fn) {
140
+ stopHooks.push(fn);
141
+ },
142
+ shutdownSignal: draining.signal,
143
+ drain,
144
+ async stop() {
145
+ await drain();
146
+ unsubscribeNotifier();
147
+ await instances.stopAll();
148
+ db.$client.close();
149
+ },
150
+ };
151
+ // Grants from before instance binding (design §6.2) get the ids their slugs name today.
152
+ ctx.oauth.backfillInstanceIds(instances.list());
153
+ return ctx;
154
+ }
155
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Db } from '../db/index.js';
2
+ /**
3
+ * Approval page tokens (design §5.3): single-use, stored hashed, bound to one approval, expiring with
4
+ * it. The MCP client receives the page URL in a URL-mode elicitation; the token only opens the page,
5
+ * and deciding still needs a signed-in human with TOTP, so the client holding it gains nothing.
6
+ */
7
+ export declare class ApprovalLinkService {
8
+ private readonly db;
9
+ private readonly now;
10
+ constructor(db: Db, now?: () => Date);
11
+ create(approvalId: string, expiresAt: Date): string;
12
+ /** The live link row for a token, or null if unknown, used, or expired. */
13
+ resolve(token: string): {
14
+ tokenHash: string;
15
+ approvalId: string;
16
+ action: "approve" | "deny" | "view";
17
+ expiresAt: Date;
18
+ usedAt: Date | null;
19
+ } | null;
20
+ /** Burns every link of an approval once it has been decided. */
21
+ consumeAll(approvalId: string): void;
22
+ purgeExpired(): number;
23
+ }
@@ -0,0 +1,49 @@
1
+ import { and, eq, gt, isNull, lt } from 'drizzle-orm';
2
+ import { approvalLinks } from '../db/schema.js';
3
+ import { randomToken, sha256 } from '../auth/tokens.js';
4
+ /**
5
+ * Approval page tokens (design §5.3): single-use, stored hashed, bound to one approval, expiring with
6
+ * it. The MCP client receives the page URL in a URL-mode elicitation; the token only opens the page,
7
+ * and deciding still needs a signed-in human with TOTP, so the client holding it gains nothing.
8
+ */
9
+ export class ApprovalLinkService {
10
+ db;
11
+ now;
12
+ constructor(db, now = () => new Date()) {
13
+ this.db = db;
14
+ this.now = now;
15
+ }
16
+ create(approvalId, expiresAt) {
17
+ const token = randomToken(32);
18
+ this.db
19
+ .insert(approvalLinks)
20
+ .values({ tokenHash: sha256(token), approvalId, action: 'view', expiresAt })
21
+ .run();
22
+ return token;
23
+ }
24
+ /** The live link row for a token, or null if unknown, used, or expired. */
25
+ resolve(token) {
26
+ if (!/^[A-Za-z0-9_-]{20,100}$/.test(token))
27
+ return null;
28
+ return (this.db
29
+ .select()
30
+ .from(approvalLinks)
31
+ .where(and(eq(approvalLinks.tokenHash, sha256(token)), isNull(approvalLinks.usedAt), gt(approvalLinks.expiresAt, this.now())))
32
+ .get() ?? null);
33
+ }
34
+ /** Burns every link of an approval once it has been decided. */
35
+ consumeAll(approvalId) {
36
+ this.db
37
+ .update(approvalLinks)
38
+ .set({ usedAt: this.now() })
39
+ .where(and(eq(approvalLinks.approvalId, approvalId), isNull(approvalLinks.usedAt)))
40
+ .run();
41
+ }
42
+ purgeExpired() {
43
+ return this.db
44
+ .delete(approvalLinks)
45
+ .where(lt(approvalLinks.expiresAt, new Date(this.now().getTime() - 24 * 3600_000)))
46
+ .run().changes;
47
+ }
48
+ }
49
+ //# sourceMappingURL=links.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"links.js","sourceRoot":"","sources":["../../src/approvals/links.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAEtD,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;GAIG;AACH,MAAM,OAAO,mBAAmB;IAEX;IACA;IAFnB,YACmB,EAAM,EACN,MAAkB,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE;QADlC,OAAE,GAAF,EAAE,CAAI;QACN,QAAG,GAAH,GAAG,CAA+B;IAClD,CAAC;IAEJ,MAAM,CAAC,UAAkB,EAAE,SAAe;QACxC,MAAM,KAAK,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC,EAAE;aACJ,MAAM,CAAC,aAAa,CAAC;aACrB,MAAM,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;aAC3E,GAAG,EAAE,CAAC;QACT,OAAO,KAAK,CAAC;IACf,CAAC;IAED,2EAA2E;IAC3E,OAAO,CAAC,KAAa;QACnB,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,IAAI,CAAC;QACxD,OAAO,CACL,IAAI,CAAC,EAAE;aACJ,MAAM,EAAE;aACR,IAAI,CAAC,aAAa,CAAC;aACnB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAC1C,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,EAC5B,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,CAAC,CACxC,CACF;aACA,GAAG,EAAE,IAAI,IAAI,CACjB,CAAC;IACJ,CAAC;IAED,gEAAgE;IAChE,UAAU,CAAC,UAAkB;QAC3B,IAAI,CAAC,EAAE;aACJ,MAAM,CAAC,aAAa,CAAC;aACrB,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;aAC3B,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,aAAa,CAAC,UAAU,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;aAClF,GAAG,EAAE,CAAC;IACX,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,EAAE;aACX,MAAM,CAAC,aAAa,CAAC;aACrB,KAAK,CAAC,EAAE,CAAC,aAAa,CAAC,SAAS,EAAE,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,CAAC;aAClF,GAAG,EAAE,CAAC,OAAO,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,110 @@
1
+ import type { Db } from '../db/index.js';
2
+ import type { ApprovalLinkService } from './links.js';
3
+ /**
4
+ * Human approval (design §5.3). Approvals happen only through the MCP client's own prompts, and an
5
+ * answer the client relays is never taken as a human's approval of a risky call:
6
+ *
7
+ * - **URL mode** (the approval path): the client is asked to open our approval page. A signed-in
8
+ * human with TOTP decides there; the client never sees the decision form. Its own reply only says
9
+ * whether the user opened the page (`accept`) or refused (`decline`/`cancel` → denied).
10
+ * - **Form mode**, only where the endpoint opted in (`formElicitationApprovals`) and the operation is
11
+ * a plain write (not locked, no typed confirmation): the client's answer decides.
12
+ * - Anything else is denied at once. Unanswered requests are denied at the timeout; nothing is ever
13
+ * approved by default.
14
+ */
15
+ export type DecisionOutcome = 'approved' | 'denied' | 'timed_out' | 'cancelled';
16
+ export type DecisionChannel = 'elicitation' | 'url';
17
+ export interface Decision {
18
+ outcome: DecisionOutcome;
19
+ via?: DecisionChannel;
20
+ decidedBy?: string;
21
+ reason?: string;
22
+ }
23
+ export interface UrlPromptRequest {
24
+ approvalId: string;
25
+ /** Path of the approval page on the MCP listener; the MCP layer makes it absolute. */
26
+ path: string;
27
+ message: string;
28
+ }
29
+ export interface UrlPromptResponse {
30
+ action: 'accept' | 'decline' | 'cancel';
31
+ }
32
+ export interface FormPromptRequest {
33
+ approvalId: string;
34
+ message: string;
35
+ requestedSchema: {
36
+ type: 'object';
37
+ properties: Record<string, {
38
+ type: 'boolean';
39
+ title: string;
40
+ description?: string;
41
+ }>;
42
+ required: string[];
43
+ };
44
+ }
45
+ export interface FormPromptResponse {
46
+ action: 'accept' | 'decline' | 'cancel';
47
+ content?: {
48
+ approve?: unknown;
49
+ };
50
+ }
51
+ /** What the connected MCP client can show; present only for a live session that advertised it. */
52
+ export interface ClientPrompts {
53
+ url?: (req: UrlPromptRequest) => Promise<UrlPromptResponse>;
54
+ /** Tells the client a URL prompt was decided, so it can close it. */
55
+ urlComplete?: (approvalId: string) => void;
56
+ form?: (req: FormPromptRequest) => Promise<FormPromptResponse>;
57
+ }
58
+ export interface ApprovalRequestInput {
59
+ instanceId: string;
60
+ operationId: string;
61
+ operationKey: string;
62
+ classification: string;
63
+ paramsDisplay: unknown;
64
+ paramsHash: string;
65
+ resolvedTargets: unknown;
66
+ summary: string;
67
+ confirmLiteral?: string;
68
+ diff?: unknown;
69
+ expectedHash?: string;
70
+ client: {
71
+ kind: string;
72
+ id?: string;
73
+ };
74
+ mcpSessionId?: string;
75
+ timeoutMs: number;
76
+ prompts?: ClientPrompts;
77
+ /** The endpoint lets form prompts approve, and this is a plain write (not locked, no typed confirmation). */
78
+ formApprovals: boolean;
79
+ }
80
+ export declare class ApprovalService {
81
+ private readonly db;
82
+ private readonly links;
83
+ private readonly now;
84
+ private readonly live;
85
+ constructor(db: Db, links: ApprovalLinkService, now?: () => Date);
86
+ /**
87
+ * Called once at startup: pending rows from a previous process can never be approved, because the
88
+ * exact params they cover only ever lived in memory (design §5.5). They are denied and audited.
89
+ */
90
+ static denyOrphans(db: Db, now?: Date): number;
91
+ request(input: ApprovalRequestInput): {
92
+ id: string;
93
+ decision: Promise<Decision>;
94
+ };
95
+ private message;
96
+ private offerUrl;
97
+ private offerForm;
98
+ /** The approval page's decision. A wrong typed confirmation is rejected so the approver can retry. */
99
+ decide(id: string, input: {
100
+ approve: boolean;
101
+ confirm?: string;
102
+ decidedBy: string;
103
+ }): Decision;
104
+ /** Cancels one open request (its execution ended). Returns false if it was already decided. */
105
+ cancel(id: string, reason: string): boolean;
106
+ /** Cancels the open requests of one endpoint (it was stopped: disabled, deleted or reconfigured). */
107
+ cancelForInstance(instanceId: string, reason: string): number;
108
+ /** Cancels every open request (shutdown). Cancelled calls are denied to the sandbox. */
109
+ cancelAll(reason?: string): void;
110
+ }
@@ -0,0 +1,182 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { and, eq } from 'drizzle-orm';
3
+ import { writeAudit } from '../audit.js';
4
+ import { pendingApprovals } from '../db/schema.js';
5
+ import { ConflictError, NotFoundError, ValidationError } from '../errors.js';
6
+ export class ApprovalService {
7
+ db;
8
+ links;
9
+ now;
10
+ live = new Map();
11
+ constructor(db, links, now = () => new Date()) {
12
+ this.db = db;
13
+ this.links = links;
14
+ this.now = now;
15
+ }
16
+ /**
17
+ * Called once at startup: pending rows from a previous process can never be approved, because the
18
+ * exact params they cover only ever lived in memory (design §5.5). They are denied and audited.
19
+ */
20
+ static denyOrphans(db, now = new Date()) {
21
+ return db.transaction((tx) => {
22
+ const orphans = tx.select().from(pendingApprovals).where(eq(pendingApprovals.status, 'pending')).all();
23
+ for (const o of orphans) {
24
+ tx.update(pendingApprovals)
25
+ .set({ status: 'denied', decidedAt: now })
26
+ .where(eq(pendingApprovals.id, o.id))
27
+ .run();
28
+ writeAudit(tx, {
29
+ kind: 'call',
30
+ instanceId: o.instanceId,
31
+ decision: 'denied',
32
+ actorKind: 'system',
33
+ detail: { approvalId: o.id, reason: 'server_restart' },
34
+ }, now);
35
+ }
36
+ return orphans.length;
37
+ });
38
+ }
39
+ request(input) {
40
+ const now = this.now();
41
+ const id = randomUUID();
42
+ const row = {
43
+ id,
44
+ instanceId: input.instanceId,
45
+ operationId: input.operationId,
46
+ paramsDisplay: input.paramsDisplay ?? null,
47
+ paramsHash: input.paramsHash,
48
+ resolvedTargets: input.resolvedTargets ?? null,
49
+ summary: input.summary,
50
+ confirmLiteral: input.confirmLiteral ?? null,
51
+ diff: input.diff ?? null,
52
+ expectedHash: input.expectedHash ?? null,
53
+ clientKind: input.client.kind,
54
+ clientId: input.client.id ?? null,
55
+ mcpSessionId: input.mcpSessionId ?? null,
56
+ requestedAt: now,
57
+ expiresAt: new Date(now.getTime() + input.timeoutMs),
58
+ status: 'pending',
59
+ decidedBy: null,
60
+ decidedVia: null,
61
+ decidedAt: null,
62
+ };
63
+ const prompts = input.prompts ?? {};
64
+ const channel = prompts.url ? 'url' : prompts.form && input.formApprovals ? 'form' : null;
65
+ if (!channel) {
66
+ // A form-only client can't carry a human's approval for this call; no prompts at all is worse.
67
+ const reason = prompts.form ? 'client_cannot_approve' : 'no_approval_path';
68
+ this.db
69
+ .insert(pendingApprovals)
70
+ .values({ ...row, status: 'denied', decidedAt: now })
71
+ .run();
72
+ return { id, decision: Promise.resolve({ outcome: 'denied', reason }) };
73
+ }
74
+ this.db.insert(pendingApprovals).values(row).run();
75
+ const decision = new Promise((resolve) => {
76
+ const timer = setTimeout(() => settle({ outcome: 'timed_out' }), input.timeoutMs);
77
+ const settle = (d) => {
78
+ if (!this.live.delete(id))
79
+ return; // first decision wins
80
+ clearTimeout(timer);
81
+ const status = d.outcome === 'timed_out' ? 'timed_out' : d.outcome;
82
+ this.db
83
+ .update(pendingApprovals)
84
+ .set({ status, decidedBy: d.decidedBy ?? null, decidedVia: d.via ?? null, decidedAt: this.now() })
85
+ .where(and(eq(pendingApprovals.id, id), eq(pendingApprovals.status, 'pending')))
86
+ .run();
87
+ this.links.consumeAll(id);
88
+ if (channel === 'url')
89
+ prompts.urlComplete?.(id);
90
+ resolve(d);
91
+ };
92
+ this.live.set(id, { row, settle });
93
+ });
94
+ if (channel === 'url')
95
+ this.offerUrl(id, row.expiresAt, input);
96
+ else
97
+ this.offerForm(id, input);
98
+ return { id, decision };
99
+ }
100
+ message(input, trailer) {
101
+ return [input.summary, '', `Operation: ${input.operationKey} (${input.classification})`, '', trailer].join('\n');
102
+ }
103
+ offerUrl(id, expiresAt, input) {
104
+ const token = this.links.create(id, expiresAt);
105
+ const clientDenied = (reason) => ({
106
+ outcome: 'denied',
107
+ via: 'elicitation',
108
+ decidedBy: input.client.id,
109
+ reason,
110
+ });
111
+ input.prompts.url({
112
+ approvalId: id,
113
+ path: `/a/${token}`,
114
+ message: this.message(input, 'Open the approval page to review the details and approve or deny.'),
115
+ }).then((res) => {
116
+ // `accept` only means the page was opened; the decision is made there.
117
+ if (res.action !== 'accept')
118
+ this.live.get(id)?.settle(clientDenied('declined'));
119
+ },
120
+ // The client could not show the prompt: nobody can reach the page, so don't wait for the timeout.
121
+ () => this.live.get(id)?.settle(clientDenied('prompt_failed')));
122
+ }
123
+ offerForm(id, input) {
124
+ const message = this.message(input, `Parameters: ${JSON.stringify(input.paramsDisplay)}${input.diff ? `\nChanges: ${JSON.stringify(input.diff)}` : ''}`);
125
+ input.prompts.form({
126
+ approvalId: id,
127
+ message,
128
+ requestedSchema: {
129
+ type: 'object',
130
+ properties: { approve: { type: 'boolean', title: 'Approve this call?' } },
131
+ required: ['approve'],
132
+ },
133
+ }).then((res) => {
134
+ const live = this.live.get(id);
135
+ if (!live)
136
+ return;
137
+ const approved = res.action === 'accept' && res.content?.approve === true;
138
+ live.settle({ outcome: approved ? 'approved' : 'denied', via: 'elicitation', decidedBy: input.client.id });
139
+ }, () => this.live.get(id)?.settle({ outcome: 'denied', reason: 'prompt_failed' }));
140
+ }
141
+ /** The approval page's decision. A wrong typed confirmation is rejected so the approver can retry. */
142
+ decide(id, input) {
143
+ const live = this.live.get(id);
144
+ if (!live) {
145
+ const row = this.db.select().from(pendingApprovals).where(eq(pendingApprovals.id, id)).get();
146
+ if (!row)
147
+ throw new NotFoundError('approval_not_found', 'No such approval');
148
+ throw new ConflictError('approval_closed', `This approval is already ${row.status}`);
149
+ }
150
+ if (input.approve && live.row.confirmLiteral && input.confirm !== live.row.confirmLiteral) {
151
+ throw new ValidationError('confirmation_mismatch', `Type "${live.row.confirmLiteral}" exactly to approve`);
152
+ }
153
+ const decision = {
154
+ outcome: input.approve ? 'approved' : 'denied',
155
+ via: 'url',
156
+ decidedBy: input.decidedBy,
157
+ };
158
+ live.settle(decision);
159
+ return decision;
160
+ }
161
+ /** Cancels one open request (its execution ended). Returns false if it was already decided. */
162
+ cancel(id, reason) {
163
+ const live = this.live.get(id);
164
+ if (!live)
165
+ return false;
166
+ live.settle({ outcome: 'cancelled', reason });
167
+ return true;
168
+ }
169
+ /** Cancels the open requests of one endpoint (it was stopped: disabled, deleted or reconfigured). */
170
+ cancelForInstance(instanceId, reason) {
171
+ const mine = [...this.live.values()].filter((l) => l.row.instanceId === instanceId);
172
+ for (const live of mine)
173
+ live.settle({ outcome: 'cancelled', reason });
174
+ return mine.length;
175
+ }
176
+ /** Cancels every open request (shutdown). Cancelled calls are denied to the sandbox. */
177
+ cancelAll(reason = 'shutdown') {
178
+ for (const live of [...this.live.values()])
179
+ live.settle({ outcome: 'cancelled', reason });
180
+ }
181
+ }
182
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/approvals/service.ts"],"names":[],"mappings":"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"}