@veertu/anka-mcp 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 (102) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +324 -0
  3. package/dist/anka.d.ts +41 -0
  4. package/dist/anka.js +65 -0
  5. package/dist/anka.js.map +1 -0
  6. package/dist/auth.d.ts +10 -0
  7. package/dist/auth.js +43 -0
  8. package/dist/auth.js.map +1 -0
  9. package/dist/config.d.ts +69 -0
  10. package/dist/config.js +98 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/controller.d.ts +73 -0
  13. package/dist/controller.js +125 -0
  14. package/dist/controller.js.map +1 -0
  15. package/dist/index.d.ts +2 -0
  16. package/dist/index.js +7 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/log.d.ts +107 -0
  19. package/dist/log.js +254 -0
  20. package/dist/log.js.map +1 -0
  21. package/dist/security/host.d.ts +2 -0
  22. package/dist/security/host.js +9 -0
  23. package/dist/security/host.js.map +1 -0
  24. package/dist/security/rate-limit.d.ts +18 -0
  25. package/dist/security/rate-limit.js +71 -0
  26. package/dist/security/rate-limit.js.map +1 -0
  27. package/dist/security/sanitize.d.ts +6 -0
  28. package/dist/security/sanitize.js +33 -0
  29. package/dist/security/sanitize.js.map +1 -0
  30. package/dist/security/schemas.d.ts +9 -0
  31. package/dist/security/schemas.js +20 -0
  32. package/dist/security/schemas.js.map +1 -0
  33. package/dist/server.d.ts +3 -0
  34. package/dist/server.js +13 -0
  35. package/dist/server.js.map +1 -0
  36. package/dist/ssh-key.d.ts +23 -0
  37. package/dist/ssh-key.js +73 -0
  38. package/dist/ssh-key.js.map +1 -0
  39. package/dist/tokens/cleanup.d.ts +10 -0
  40. package/dist/tokens/cleanup.js +28 -0
  41. package/dist/tokens/cleanup.js.map +1 -0
  42. package/dist/tokens/ownership.d.ts +6 -0
  43. package/dist/tokens/ownership.js +31 -0
  44. package/dist/tokens/ownership.js.map +1 -0
  45. package/dist/tokens/schema.d.ts +3 -0
  46. package/dist/tokens/schema.js +35 -0
  47. package/dist/tokens/schema.js.map +1 -0
  48. package/dist/tokens/store.d.ts +45 -0
  49. package/dist/tokens/store.js +145 -0
  50. package/dist/tokens/store.js.map +1 -0
  51. package/dist/tools/controller/get-vm.d.ts +3 -0
  52. package/dist/tools/controller/get-vm.js +34 -0
  53. package/dist/tools/controller/get-vm.js.map +1 -0
  54. package/dist/tools/controller/index.d.ts +3 -0
  55. package/dist/tools/controller/index.js +12 -0
  56. package/dist/tools/controller/index.js.map +1 -0
  57. package/dist/tools/controller/list-templates.d.ts +1 -0
  58. package/dist/tools/controller/list-templates.js +21 -0
  59. package/dist/tools/controller/list-templates.js.map +1 -0
  60. package/dist/tools/controller/request-vm.d.ts +8 -0
  61. package/dist/tools/controller/request-vm.js +101 -0
  62. package/dist/tools/controller/request-vm.js.map +1 -0
  63. package/dist/tools/controller/results.d.ts +5 -0
  64. package/dist/tools/controller/results.js +23 -0
  65. package/dist/tools/controller/results.js.map +1 -0
  66. package/dist/tools/controller/terminate-vm.d.ts +3 -0
  67. package/dist/tools/controller/terminate-vm.js +32 -0
  68. package/dist/tools/controller/terminate-vm.js.map +1 -0
  69. package/dist/tools/define-tool.d.ts +34 -0
  70. package/dist/tools/define-tool.js +51 -0
  71. package/dist/tools/define-tool.js.map +1 -0
  72. package/dist/tools/index.d.ts +8 -0
  73. package/dist/tools/index.js +20 -0
  74. package/dist/tools/index.js.map +1 -0
  75. package/dist/tools/local/delete-vm.d.ts +3 -0
  76. package/dist/tools/local/delete-vm.js +20 -0
  77. package/dist/tools/local/delete-vm.js.map +1 -0
  78. package/dist/tools/local/index.d.ts +3 -0
  79. package/dist/tools/local/index.js +14 -0
  80. package/dist/tools/local/index.js.map +1 -0
  81. package/dist/tools/local/list-templates.d.ts +1 -0
  82. package/dist/tools/local/list-templates.js +17 -0
  83. package/dist/tools/local/list-templates.js.map +1 -0
  84. package/dist/tools/local/show-vm.d.ts +3 -0
  85. package/dist/tools/local/show-vm.js +23 -0
  86. package/dist/tools/local/show-vm.js.map +1 -0
  87. package/dist/tools/local/ssh-access.d.ts +3 -0
  88. package/dist/tools/local/ssh-access.js +68 -0
  89. package/dist/tools/local/ssh-access.js.map +1 -0
  90. package/dist/tools/local/start-vm.d.ts +7 -0
  91. package/dist/tools/local/start-vm.js +52 -0
  92. package/dist/tools/local/start-vm.js.map +1 -0
  93. package/dist/tools/local/vms.d.ts +62 -0
  94. package/dist/tools/local/vms.js +91 -0
  95. package/dist/tools/local/vms.js.map +1 -0
  96. package/dist/transports/admin.d.ts +3 -0
  97. package/dist/transports/admin.js +74 -0
  98. package/dist/transports/admin.js.map +1 -0
  99. package/dist/transports/http.d.ts +14 -0
  100. package/dist/transports/http.js +283 -0
  101. package/dist/transports/http.js.map +1 -0
  102. package/package.json +46 -0
package/dist/log.js ADDED
@@ -0,0 +1,254 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { appendFileSync } from "node:fs";
3
+ import { config } from "./config.js";
4
+ const requestContext = new AsyncLocalStorage();
5
+ const SENSITIVE_KEYS = new Set(["password", "private_key", "authorization", "token"]);
6
+ function timestamp() {
7
+ return new Date().toISOString();
8
+ }
9
+ function write(line) {
10
+ process.stderr.write(`anka-mcp: ${line}\n`);
11
+ if (config.auditLogPath) {
12
+ appendFileSync(config.auditLogPath, `anka-mcp: ${line}\n`, { encoding: "utf8" });
13
+ }
14
+ }
15
+ /** Log an auth failure (always written when logging is enabled). */
16
+ export function logAuthFailure(source, route, reason = "invalid token") {
17
+ if (!config.logEnabled)
18
+ return;
19
+ write(`${timestamp()} [${source}] auth failure ${route} (${reason})`);
20
+ }
21
+ function formatActor(actor) {
22
+ const parts = [];
23
+ if (actor.source)
24
+ parts.push(`source=${actor.source}`);
25
+ else if (actor.ip)
26
+ parts.push(`ip=${actor.ip}`);
27
+ if (actor.credentialId)
28
+ parts.push(`credential_id=${actor.credentialId}`);
29
+ if (actor.credentialLabel)
30
+ parts.push(`credential_label=${actor.credentialLabel}`);
31
+ return parts.length > 0 ? parts.join(" ") : "unknown";
32
+ }
33
+ /** Build actor fields from an Express request (includes credential when auth ran). */
34
+ export function limitActorFromRequest(req) {
35
+ return {
36
+ source: clientSourceFromRequest(req),
37
+ ip: req.ip || req.socket.remoteAddress || "unknown",
38
+ credentialId: req.mcpCredential?.credentialId,
39
+ credentialLabel: req.mcpCredential?.credentialLabel
40
+ };
41
+ }
42
+ /** Build actor fields from the current async request context. */
43
+ export function limitActorFromContext() {
44
+ const ctx = getRequestContext();
45
+ if (!ctx)
46
+ return {};
47
+ return {
48
+ source: ctx.source,
49
+ ip: ctx.ip,
50
+ credentialId: ctx.credentialId,
51
+ credentialLabel: ctx.credentialLabel
52
+ };
53
+ }
54
+ /**
55
+ * Log that a configured limit was hit. Format:
56
+ * `LIMIT REACHED MCP_RATE_LIMIT_RPM=120 by source=… credential_id=… route=/mcp …`
57
+ */
58
+ export function logLimitReached(opts) {
59
+ if (!config.logEnabled)
60
+ return;
61
+ const route = opts.route ? ` route=${opts.route}` : "";
62
+ const detail = opts.detail ? ` ${opts.detail}` : "";
63
+ write(`${timestamp()} LIMIT REACHED ${opts.limit}=${opts.configured} by ${formatActor(opts.actor ?? {})}${route}${detail}`);
64
+ }
65
+ /** Log MCP session lifecycle events. */
66
+ export function logSessionEvent(event, sessionId, actor) {
67
+ if (!config.logEnabled)
68
+ return;
69
+ const who = actor ? ` by ${formatActor(actor)}` : ` [${getRequestSource()}]`;
70
+ write(`${timestamp()} session ${event} session_id=${sessionId}${who}`);
71
+ }
72
+ /** Log admin token lifecycle events (never logs plaintext secrets). */
73
+ export function logAdminEvent(event, detail) {
74
+ if (!config.logEnabled)
75
+ return;
76
+ const label = detail.label ? ` label=${detail.label}` : "";
77
+ write(`${timestamp()} admin ${event} id=${detail.id}${label}`);
78
+ }
79
+ /** Run `fn` with request-scoped logging context (client source). */
80
+ export function runWithRequestContext(context, fn) {
81
+ return requestContext.run(context, fn);
82
+ }
83
+ /** Run async `fn` with request-scoped logging context. */
84
+ export async function runWithRequestContextAsync(context, fn) {
85
+ return requestContext.run(context, fn);
86
+ }
87
+ export function getRequestSource() {
88
+ return requestContext.getStore()?.source ?? "unknown";
89
+ }
90
+ export function getRequestContext() {
91
+ return requestContext.getStore();
92
+ }
93
+ const MAX_CONTROLLER_EXTERNAL_ID_LENGTH = 512;
94
+ function sanitizeExternalIdPart(value) {
95
+ return value.replace(/[\r\n\t\0]/g, " ").replace(/\s+/g, " ").trim();
96
+ }
97
+ /**
98
+ * Build a controller `external_id` that identifies the MCP caller for admins.
99
+ * Always includes client/IP/session context; an optional caller `ref` is appended.
100
+ */
101
+ export function buildControllerExternalId(callerExternalId) {
102
+ const ctx = requestContext.getStore();
103
+ const parts = ["anka-mcp"];
104
+ if (ctx?.mcpClientName) {
105
+ const client = ctx.mcpClientVersion
106
+ ? `${ctx.mcpClientName}/${ctx.mcpClientVersion}`
107
+ : ctx.mcpClientName;
108
+ parts.push(`client=${sanitizeExternalIdPart(client)}`);
109
+ }
110
+ if (ctx?.ip) {
111
+ parts.push(`ip=${sanitizeExternalIdPart(ctx.ip.replace(/^::ffff:/, ""))}`);
112
+ }
113
+ if (ctx?.userAgent) {
114
+ parts.push(`ua=${sanitizeExternalIdPart(ctx.userAgent)}`);
115
+ }
116
+ if (ctx?.sessionId) {
117
+ parts.push(`session=${ctx.sessionId}`);
118
+ }
119
+ if (ctx?.credentialId) {
120
+ parts.push(`credential_id=${sanitizeExternalIdPart(ctx.credentialId)}`);
121
+ }
122
+ if (callerExternalId?.trim()) {
123
+ parts.push(`ref=${sanitizeExternalIdPart(callerExternalId.trim())}`);
124
+ }
125
+ const externalId = parts.join(" ");
126
+ if (externalId.length <= MAX_CONTROLLER_EXTERNAL_ID_LENGTH)
127
+ return externalId;
128
+ return `${externalId.slice(0, MAX_CONTROLLER_EXTERNAL_ID_LENGTH - 1)}…`;
129
+ }
130
+ /** Build request-scoped context from an HTTP request and optional MCP client info. */
131
+ export function buildRequestContext(req, clientInfo, sessionId) {
132
+ const ip = req.ip || req.socket.remoteAddress || "unknown";
133
+ const userAgent = typeof req.headers["user-agent"] === "string" ? req.headers["user-agent"] : undefined;
134
+ const resolvedSessionId = sessionId ??
135
+ (typeof req.headers["mcp-session-id"] === "string" ? req.headers["mcp-session-id"] : undefined);
136
+ return {
137
+ source: clientSourceFromRequest(req),
138
+ ip,
139
+ userAgent,
140
+ sessionId: resolvedSessionId,
141
+ mcpClientName: clientInfo?.name,
142
+ mcpClientVersion: clientInfo?.version,
143
+ credentialId: req.mcpCredential?.credentialId,
144
+ credentialLabel: req.mcpCredential?.credentialLabel
145
+ };
146
+ }
147
+ /** Extract MCP clientInfo from an initialize JSON-RPC body. */
148
+ export function mcpClientInfoFromBody(body) {
149
+ const messages = Array.isArray(body) ? body : [body];
150
+ for (const message of messages) {
151
+ if (!message || typeof message !== "object" || message.method !== "initialize") {
152
+ continue;
153
+ }
154
+ const clientInfo = message.params?.clientInfo;
155
+ if (!clientInfo || typeof clientInfo !== "object")
156
+ continue;
157
+ return {
158
+ name: typeof clientInfo.name === "string"
159
+ ? clientInfo.name
160
+ : undefined,
161
+ version: typeof clientInfo.version === "string"
162
+ ? clientInfo.version
163
+ : undefined
164
+ };
165
+ }
166
+ return undefined;
167
+ }
168
+ /** Build a compact client label from an Express request. */
169
+ export function clientSourceFromRequest(req) {
170
+ const ip = req.ip || req.socket.remoteAddress || "unknown";
171
+ const userAgent = req.headers["user-agent"];
172
+ if (typeof userAgent === "string" && userAgent.length > 0) {
173
+ return `${ip} (${userAgent})`;
174
+ }
175
+ return ip;
176
+ }
177
+ /** Redact known secret fields before logging structured data. */
178
+ export function sanitizeForLog(value) {
179
+ if (value === null || value === undefined)
180
+ return value;
181
+ if (Array.isArray(value))
182
+ return value.map(sanitizeForLog);
183
+ if (typeof value === "object") {
184
+ const out = {};
185
+ for (const [key, nested] of Object.entries(value)) {
186
+ out[key] = SENSITIVE_KEYS.has(key.toLowerCase()) ? "[redacted]" : sanitizeForLog(nested);
187
+ }
188
+ return out;
189
+ }
190
+ return value;
191
+ }
192
+ function formatPayload(value, maxLength = 800) {
193
+ const text = typeof value === "string" ? value : JSON.stringify(sanitizeForLog(value), null, 0) ?? "undefined";
194
+ if (text.length <= maxLength)
195
+ return text;
196
+ return `${text.slice(0, maxLength)}…`;
197
+ }
198
+ function parseToolResultPayload(result) {
199
+ const block = result.content?.[0];
200
+ if (!block || block.type !== "text")
201
+ return { isError: result.isError };
202
+ try {
203
+ return JSON.parse(block.text);
204
+ }
205
+ catch {
206
+ return { isError: result.isError, text: block.text };
207
+ }
208
+ }
209
+ /** Extract JSON-RPC method name(s) from an MCP POST body. */
210
+ export function mcpMethodsFromBody(body) {
211
+ if (!body)
212
+ return [];
213
+ const messages = Array.isArray(body) ? body : [body];
214
+ return messages
215
+ .map((message) => message && typeof message === "object" && "method" in message
216
+ ? String(message.method)
217
+ : undefined)
218
+ .filter((method) => Boolean(method));
219
+ }
220
+ export function logMcpRequest(method, detail) {
221
+ if (!config.logEnabled)
222
+ return;
223
+ const suffix = detail ? ` ${formatPayload(detail)}` : "";
224
+ write(`${timestamp()} [${getRequestSource()}] mcp ${method}${suffix}`);
225
+ }
226
+ export function logToolCall(name, args, result) {
227
+ if (!config.logEnabled)
228
+ return;
229
+ const payload = parseToolResultPayload(result);
230
+ write(`${timestamp()} [${getRequestSource()}] tool ${name} args=${formatPayload(args)} -> ${formatPayload(payload)}`);
231
+ }
232
+ export function logToolError(name, args, error) {
233
+ if (!config.logEnabled)
234
+ return;
235
+ write(`${timestamp()} [${getRequestSource()}] tool ${name} args=${formatPayload(args)} -> error ${String(error)}`);
236
+ }
237
+ export function logAnkaCommand(args, outcome) {
238
+ if (!config.logEnabled)
239
+ return;
240
+ const cmd = [config.ankaBin, ...args].join(" ");
241
+ const status = outcome.ok ? "ok" : `failed${outcome.message ? `: ${outcome.message}` : ""}`;
242
+ write(`${timestamp()} [${getRequestSource()}] anka ${cmd} -> ${status}`);
243
+ }
244
+ export function logControllerRequest(method, path, outcome) {
245
+ if (!config.logEnabled)
246
+ return;
247
+ const status = outcome.ok ? "ok" : `failed${outcome.detail ? `: ${outcome.detail}` : ""}`;
248
+ write(`${timestamp()} [${getRequestSource()}] controller ${method} ${path} -> ${status}`);
249
+ }
250
+ /** Log the tool names registered at startup (always written, like the listen banner). */
251
+ export function logStartupTools(toolNames) {
252
+ process.stderr.write(`anka-mcp: tools: ${toolNames.join(", ") || "(none)"}\n`);
253
+ }
254
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../src/log.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAmBrC,MAAM,cAAc,GAAG,IAAI,iBAAiB,EAAkB,CAAC;AAE/D,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,aAAa,EAAE,eAAe,EAAE,OAAO,CAAC,CAAC,CAAC;AAEtF,SAAS,SAAS;IAChB,OAAO,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAClC,CAAC;AAED,SAAS,KAAK,CAAC,IAAY;IACzB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,aAAa,IAAI,IAAI,CAAC,CAAC;IAC5C,IAAI,MAAM,CAAC,YAAY,EAAE,CAAC;QACxB,cAAc,CAAC,MAAM,CAAC,YAAY,EAAE,aAAa,IAAI,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC,CAAC;IACnF,CAAC;AACH,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,cAAc,CAAC,MAAc,EAAE,KAAa,EAAE,MAAM,GAAG,eAAe;IACpF,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,KAAK,CAAC,GAAG,SAAS,EAAE,KAAK,MAAM,kBAAkB,KAAK,KAAK,MAAM,GAAG,CAAC,CAAC;AACxE,CAAC;AAUD,SAAS,WAAW,CAAC,KAAiB;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,KAAK,CAAC,MAAM;QAAE,KAAK,CAAC,IAAI,CAAC,UAAU,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;SAClD,IAAI,KAAK,CAAC,EAAE;QAAE,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC;IAChD,IAAI,KAAK,CAAC,YAAY;QAAE,KAAK,CAAC,IAAI,CAAC,iBAAiB,KAAK,CAAC,YAAY,EAAE,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,eAAe;QAAE,KAAK,CAAC,IAAI,CAAC,oBAAoB,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;IACnF,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,qBAAqB,CAAC,GAKrC;IACC,OAAO;QACL,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC;QACpC,EAAE,EAAE,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS;QACnD,YAAY,EAAE,GAAG,CAAC,aAAa,EAAE,YAAY;QAC7C,eAAe,EAAE,GAAG,CAAC,aAAa,EAAE,eAAe;KACpD,CAAC;AACJ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,qBAAqB;IACnC,MAAM,GAAG,GAAG,iBAAiB,EAAE,CAAC;IAChC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO;QACL,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,YAAY,EAAE,GAAG,CAAC,YAAY;QAC9B,eAAe,EAAE,GAAG,CAAC,eAAe;KACrC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,IAM/B;IACC,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACvD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACpD,KAAK,CACH,GAAG,SAAS,EAAE,kBAAkB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,UAAU,OAAO,WAAW,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,GAAG,KAAK,GAAG,MAAM,EAAE,CACrH,CAAC;AACJ,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,eAAe,CAC7B,KAA2B,EAC3B,SAAiB,EACjB,KAAkB;IAElB,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,OAAO,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,gBAAgB,EAAE,GAAG,CAAC;IAC7E,KAAK,CAAC,GAAG,SAAS,EAAE,YAAY,KAAK,eAAe,SAAS,GAAG,GAAG,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,aAAa,CAC3B,KAAwC,EACxC,MAAsC;IAEtC,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,UAAU,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3D,KAAK,CAAC,GAAG,SAAS,EAAE,UAAU,KAAK,OAAO,MAAM,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;AACjE,CAAC;AAED,oEAAoE;AACpE,MAAM,UAAU,qBAAqB,CAAI,OAAuB,EAAE,EAAW;IAC3E,OAAO,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,OAAuB,EACvB,EAAoB;IAEpB,OAAO,cAAc,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,cAAc,CAAC,QAAQ,EAAE,EAAE,MAAM,IAAI,SAAS,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,CAAC,QAAQ,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,iCAAiC,GAAG,GAAG,CAAC;AAE9C,SAAS,sBAAsB,CAAC,KAAa;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;AACvE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CAAC,gBAAyB;IACjE,MAAM,GAAG,GAAG,cAAc,CAAC,QAAQ,EAAE,CAAC;IACtC,MAAM,KAAK,GAAG,CAAC,UAAU,CAAC,CAAC;IAE3B,IAAI,GAAG,EAAE,aAAa,EAAE,CAAC;QACvB,MAAM,MAAM,GAAG,GAAG,CAAC,gBAAgB;YACjC,CAAC,CAAC,GAAG,GAAG,CAAC,aAAa,IAAI,GAAG,CAAC,gBAAgB,EAAE;YAChD,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,UAAU,sBAAsB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACzD,CAAC;IACD,IAAI,GAAG,EAAE,EAAE,EAAE,CAAC;QACZ,KAAK,CAAC,IAAI,CAAC,MAAM,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,IAAI,GAAG,EAAE,SAAS,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,MAAM,sBAAsB,CAAC,GAAG,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IACD,IAAI,GAAG,EAAE,SAAS,EAAE,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,WAAW,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC;IACzC,CAAC;IACD,IAAI,GAAG,EAAE,YAAY,EAAE,CAAC;QACtB,KAAK,CAAC,IAAI,CAAC,iBAAiB,sBAAsB,CAAC,GAAG,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,IAAI,gBAAgB,EAAE,IAAI,EAAE,EAAE,CAAC;QAC7B,KAAK,CAAC,IAAI,CAAC,OAAO,sBAAsB,CAAC,gBAAgB,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,MAAM,IAAI,iCAAiC;QAAE,OAAO,UAAU,CAAC;IAC9E,OAAO,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,iCAAiC,GAAG,CAAC,CAAC,GAAG,CAAC;AAC1E,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,mBAAmB,CACjC,GAKC,EACD,UAA0B,EAC1B,SAAkB;IAElB,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IAC3D,MAAM,SAAS,GACb,OAAO,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACxF,MAAM,iBAAiB,GACrB,SAAS;QACT,CAAC,OAAO,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAElG,OAAO;QACL,MAAM,EAAE,uBAAuB,CAAC,GAAG,CAAC;QACpC,EAAE;QACF,SAAS;QACT,SAAS,EAAE,iBAAiB;QAC5B,aAAa,EAAE,UAAU,EAAE,IAAI;QAC/B,gBAAgB,EAAE,UAAU,EAAE,OAAO;QACrC,YAAY,EAAE,GAAG,CAAC,aAAa,EAAE,YAAY;QAC7C,eAAe,EAAE,GAAG,CAAC,aAAa,EAAE,eAAe;KACpD,CAAC;AACJ,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,qBAAqB,CAAC,IAAa;IACjD,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC/B,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAK,OAA+B,CAAC,MAAM,KAAK,YAAY,EAAE,CAAC;YACxG,SAAS;QACX,CAAC;QACD,MAAM,UAAU,GAAI,OAAiD,CAAC,MAAM,EAAE,UAAU,CAAC;QACzF,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;YAAE,SAAS;QAC5D,OAAO;YACL,IAAI,EAAE,OAAQ,UAAiC,CAAC,IAAI,KAAK,QAAQ;gBAC/D,CAAC,CAAE,UAA+B,CAAC,IAAI;gBACvC,CAAC,CAAC,SAAS;YACb,OAAO,EAAE,OAAQ,UAAoC,CAAC,OAAO,KAAK,QAAQ;gBACxE,CAAC,CAAE,UAAkC,CAAC,OAAO;gBAC7C,CAAC,CAAC,SAAS;SACd,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,uBAAuB,CAAC,GAIvC;IACC,MAAM,EAAE,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC;IAC3D,MAAM,SAAS,GAAG,GAAG,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAC5C,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1D,OAAO,GAAG,EAAE,KAAK,SAAS,GAAG,CAAC;IAChC,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC3D,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,GAAG,GAA4B,EAAE,CAAC;QACxC,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YAC7E,GAAG,CAAC,GAAG,CAAC,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO,GAAG,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,aAAa,CAAC,KAAc,EAAE,SAAS,GAAG,GAAG;IACpD,MAAM,IAAI,GACR,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,WAAW,CAAC;IACpG,IAAI,IAAI,CAAC,MAAM,IAAI,SAAS;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,GAAG,CAAC;AACxC,CAAC;AAED,SAAS,sBAAsB,CAAC,MAAsB;IACpD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC;IAClC,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IACxE,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC;IACvD,CAAC;AACH,CAAC;AAED,6DAA6D;AAC7D,MAAM,UAAU,kBAAkB,CAAC,IAAa;IAC9C,IAAI,CAAC,IAAI;QAAE,OAAO,EAAE,CAAC;IACrB,MAAM,QAAQ,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IACrD,OAAO,QAAQ;SACZ,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CACf,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,QAAQ,IAAI,OAAO;QAC3D,CAAC,CAAC,MAAM,CAAE,OAA+B,CAAC,MAAM,CAAC;QACjD,CAAC,CAAC,SAAS,CACd;SACA,MAAM,CAAC,CAAC,MAAM,EAAoB,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,MAAgC;IAC5E,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;IACzD,KAAK,CAAC,GAAG,SAAS,EAAE,KAAK,gBAAgB,EAAE,SAAS,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,IAAa,EAAE,MAAsB;IAC7E,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,OAAO,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAAC;IAC/C,KAAK,CACH,GAAG,SAAS,EAAE,KAAK,gBAAgB,EAAE,UAAU,IAAI,SAAS,aAAa,CAAC,IAAI,CAAC,OAAO,aAAa,CAAC,OAAO,CAAC,EAAE,CAC/G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,IAAa,EAAE,KAAc;IACtE,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,KAAK,CACH,GAAG,SAAS,EAAE,KAAK,gBAAgB,EAAE,UAAU,IAAI,SAAS,aAAa,CAAC,IAAI,CAAC,aAAa,MAAM,CAAC,KAAK,CAAC,EAAE,CAC5G,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAc,EAAE,OAA0C;IACvF,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC5F,KAAK,CAAC,GAAG,SAAS,EAAE,KAAK,gBAAgB,EAAE,UAAU,GAAG,OAAO,MAAM,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,IAAY,EACZ,OAAyC;IAEzC,IAAI,CAAC,MAAM,CAAC,UAAU;QAAE,OAAO;IAC/B,MAAM,MAAM,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC1F,KAAK,CAAC,GAAG,SAAS,EAAE,KAAK,gBAAgB,EAAE,gBAAgB,MAAM,IAAI,IAAI,OAAO,MAAM,EAAE,CAAC,CAAC;AAC5F,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,eAAe,CAAC,SAAmB;IACjD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,IAAI,CAAC,CAAC;AACjF,CAAC"}
@@ -0,0 +1,2 @@
1
+ /** True when the bind address is loopback-only (localhost / ::1). */
2
+ export declare function isLoopbackHost(host: string): boolean;
@@ -0,0 +1,9 @@
1
+ /** True when the bind address is loopback-only (localhost / ::1). */
2
+ export function isLoopbackHost(host) {
3
+ const normalized = host.trim().toLowerCase();
4
+ return (normalized === "127.0.0.1" ||
5
+ normalized === "localhost" ||
6
+ normalized === "::1" ||
7
+ normalized === "[::1]");
8
+ }
9
+ //# sourceMappingURL=host.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host.js","sourceRoot":"","sources":["../../src/security/host.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,OAAO,CACL,UAAU,KAAK,WAAW;QAC1B,UAAU,KAAK,WAAW;QAC1B,UAAU,KAAK,KAAK;QACpB,UAAU,KAAK,OAAO,CACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import type { NextFunction, Request, Response } from "express";
2
+ /** In-memory sliding-window rate limiter keyed by client identifier (typically IP). */
3
+ export declare class SlidingWindowRateLimiter {
4
+ private readonly windowMs;
5
+ private readonly maxRequests;
6
+ private readonly hits;
7
+ constructor(maxRequestsPerMinute: number);
8
+ /** Returns whether the request is allowed and the current window count after the decision. */
9
+ allow(key: string): {
10
+ allowed: boolean;
11
+ count: number;
12
+ };
13
+ get maxPerWindow(): number;
14
+ /** Drop stale entries (call periodically to limit memory growth). */
15
+ prune(): void;
16
+ }
17
+ /** Express middleware that enforces per-IP RPM when `MCP_RATE_LIMIT_RPM` > 0. */
18
+ export declare function rateLimitMiddleware(limiter: SlidingWindowRateLimiter): (req: Request, res: Response, next: NextFunction) => void;
@@ -0,0 +1,71 @@
1
+ import { config } from "../config.js";
2
+ import { limitActorFromRequest, logLimitReached } from "../log.js";
3
+ /** In-memory sliding-window rate limiter keyed by client identifier (typically IP). */
4
+ export class SlidingWindowRateLimiter {
5
+ windowMs;
6
+ maxRequests;
7
+ hits = new Map();
8
+ constructor(maxRequestsPerMinute) {
9
+ this.windowMs = 60_000;
10
+ this.maxRequests = maxRequestsPerMinute;
11
+ }
12
+ /** Returns whether the request is allowed and the current window count after the decision. */
13
+ allow(key) {
14
+ const now = Date.now();
15
+ const windowStart = now - this.windowMs;
16
+ const timestamps = (this.hits.get(key) ?? []).filter((t) => t > windowStart);
17
+ if (timestamps.length >= this.maxRequests) {
18
+ this.hits.set(key, timestamps);
19
+ return { allowed: false, count: timestamps.length };
20
+ }
21
+ timestamps.push(now);
22
+ this.hits.set(key, timestamps);
23
+ return { allowed: true, count: timestamps.length };
24
+ }
25
+ get maxPerWindow() {
26
+ return this.maxRequests;
27
+ }
28
+ /** Drop stale entries (call periodically to limit memory growth). */
29
+ prune() {
30
+ const windowStart = Date.now() - this.windowMs;
31
+ for (const [key, timestamps] of this.hits) {
32
+ const fresh = timestamps.filter((t) => t > windowStart);
33
+ if (fresh.length === 0)
34
+ this.hits.delete(key);
35
+ else
36
+ this.hits.set(key, fresh);
37
+ }
38
+ }
39
+ }
40
+ /** Express middleware that enforces per-IP RPM when `MCP_RATE_LIMIT_RPM` > 0. */
41
+ export function rateLimitMiddleware(limiter) {
42
+ return (req, res, next) => {
43
+ if (config.rateLimitRpm <= 0) {
44
+ next();
45
+ return;
46
+ }
47
+ const key = req.ip || req.socket.remoteAddress || "unknown";
48
+ const { allowed, count } = limiter.allow(key);
49
+ if (allowed) {
50
+ next();
51
+ return;
52
+ }
53
+ logLimitReached({
54
+ limit: "MCP_RATE_LIMIT_RPM",
55
+ configured: String(config.rateLimitRpm),
56
+ route: req.originalUrl,
57
+ actor: limitActorFromRequest(req),
58
+ detail: `requests_in_window=${count}`
59
+ });
60
+ if (req.originalUrl.startsWith("/admin")) {
61
+ res.status(429).json({ ok: false, error: "Too many requests" });
62
+ return;
63
+ }
64
+ res.status(429).json({
65
+ jsonrpc: "2.0",
66
+ error: { code: -32003, message: "Too many requests" },
67
+ id: null
68
+ });
69
+ };
70
+ }
71
+ //# sourceMappingURL=rate-limit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limit.js","sourceRoot":"","sources":["../../src/security/rate-limit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ /** Strip file paths and URLs from CLI stderr before returning to agents. */
2
+ export declare function sanitizeCliError(text: string): string;
3
+ /** Map internal controller errors to agent-safe messages (no URLs or raw HTTP bodies). */
4
+ export declare function sanitizeControllerError(message: string): string;
5
+ /** Normalize unknown thrown values into a safe error string. */
6
+ export declare function sanitizeUnknownError(error: unknown): string;
@@ -0,0 +1,33 @@
1
+ const MAX_ERROR_LENGTH = 200;
2
+ const PATH_PATTERN = /(?:\/[\w.-]+)+/g;
3
+ const URL_PATTERN = /https?:\/\/[^\s]+/gi;
4
+ /** Strip file paths and URLs from CLI stderr before returning to agents. */
5
+ export function sanitizeCliError(text) {
6
+ let cleaned = text.replace(URL_PATTERN, "[url]").replace(PATH_PATTERN, "[path]");
7
+ cleaned = cleaned.replace(/\s+/g, " ").trim();
8
+ if (cleaned.length <= MAX_ERROR_LENGTH)
9
+ return cleaned;
10
+ return `${cleaned.slice(0, MAX_ERROR_LENGTH - 1)}…`;
11
+ }
12
+ /** Map internal controller errors to agent-safe messages (no URLs or raw HTTP bodies). */
13
+ export function sanitizeControllerError(message) {
14
+ if (/failed to reach controller/i.test(message)) {
15
+ return "Could not reach the Anka controller";
16
+ }
17
+ if (/controller request failed/i.test(message)) {
18
+ const httpMatch = message.match(/HTTP (\d{3})/);
19
+ if (httpMatch) {
20
+ return `Controller rejected the request (HTTP ${httpMatch[1]})`;
21
+ }
22
+ return "Controller rejected the request";
23
+ }
24
+ return "Controller operation failed";
25
+ }
26
+ /** Normalize unknown thrown values into a safe error string. */
27
+ export function sanitizeUnknownError(error) {
28
+ if (error instanceof Error) {
29
+ return sanitizeControllerError(error.message);
30
+ }
31
+ return "Operation failed";
32
+ }
33
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/security/sanitize.ts"],"names":[],"mappings":"AAAA,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAC7B,MAAM,YAAY,GAAG,iBAAiB,CAAC;AACvC,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAE1C,4EAA4E;AAC5E,MAAM,UAAU,gBAAgB,CAAC,IAAY;IAC3C,IAAI,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;IACjF,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,IAAI,OAAO,CAAC,MAAM,IAAI,gBAAgB;QAAE,OAAO,OAAO,CAAC;IACvD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,gBAAgB,GAAG,CAAC,CAAC,GAAG,CAAC;AACtD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,uBAAuB,CAAC,OAAe;IACrD,IAAI,6BAA6B,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAChD,OAAO,qCAAqC,CAAC;IAC/C,CAAC;IACD,IAAI,4BAA4B,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;QAChD,IAAI,SAAS,EAAE,CAAC;YACd,OAAO,yCAAyC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC;QAClE,CAAC;QACD,OAAO,iCAAiC,CAAC;IAC3C,CAAC;IACD,OAAO,6BAA6B,CAAC;AACvC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,oBAAoB,CAAC,KAAc;IACjD,IAAI,KAAK,YAAY,KAAK,EAAE,CAAC;QAC3B,OAAO,uBAAuB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;IACD,OAAO,kBAAkB,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ /** VM/template/name identifier: non-empty, bounded, must not start with "-". */
3
+ export declare const boundedName: z.ZodString;
4
+ /** Controller vmid, instance_id, tag, and similar opaque ids. */
5
+ export declare const uuidLike: z.ZodString;
6
+ /** Optional bounded label or external reference. */
7
+ export declare const optionalBoundedString: z.ZodString;
8
+ /** Local start_vm wait timeout in seconds. */
9
+ export declare const timeoutSecondsSchema: z.ZodNumber;
@@ -0,0 +1,20 @@
1
+ import { z } from "zod";
2
+ /** VM/template/name identifier: non-empty, bounded, must not start with "-". */
3
+ export const boundedName = z
4
+ .string()
5
+ .trim()
6
+ .min(1)
7
+ .max(128)
8
+ .regex(/^[^-]/, 'must not start with "-"');
9
+ /** Controller vmid, instance_id, tag, and similar opaque ids. */
10
+ export const uuidLike = z
11
+ .string()
12
+ .trim()
13
+ .min(1)
14
+ .max(64)
15
+ .regex(/^[a-zA-Z0-9._-]+$/);
16
+ /** Optional bounded label or external reference. */
17
+ export const optionalBoundedString = z.string().trim().min(1).max(512);
18
+ /** Local start_vm wait timeout in seconds. */
19
+ export const timeoutSecondsSchema = z.number().positive().max(3600);
20
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../../src/security/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,gFAAgF;AAChF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC;KACzB,MAAM,EAAE;KACR,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,GAAG,CAAC;KACR,KAAK,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAC;AAE7C,iEAAiE;AACjE,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC;KACtB,MAAM,EAAE;KACR,IAAI,EAAE;KACN,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,EAAE,CAAC;KACP,KAAK,CAAC,mBAAmB,CAAC,CAAC;AAE9B,oDAAoD;AACpD,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AAEvE,8CAA8C;AAC9C,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ /** Build a fresh MCP server instance with all tools registered. */
3
+ export declare function createServer(): McpServer;
package/dist/server.js ADDED
@@ -0,0 +1,13 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { config } from "./config.js";
3
+ import { registerTools } from "./tools/index.js";
4
+ /** Build a fresh MCP server instance with all tools registered. */
5
+ export function createServer() {
6
+ const server = new McpServer({
7
+ name: config.serverName,
8
+ version: config.serverVersion
9
+ });
10
+ registerTools(server);
11
+ return server;
12
+ }
13
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,mEAAmE;AACnE,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC,UAAU;QACvB,OAAO,EAAE,MAAM,CAAC,aAAa;KAC9B,CAAC,CAAC;IAEH,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface SshKeypair {
2
+ privateKeyPath: string;
3
+ publicKey: string;
4
+ }
5
+ /** Generate a throwaway ed25519 keypair in a temp dir. */
6
+ export declare function generateSshKeypair(): Promise<SshKeypair>;
7
+ /** Shell script that installs `publicKey` into ~/.ssh/authorized_keys. */
8
+ export declare function buildAuthorizedKeysStartupScript(publicKey: string): string;
9
+ /** Base64-encode a startup script for the controller API `startup_script` field. */
10
+ export declare function encodeStartupScript(script: string): string;
11
+ export declare function buildSshCommand(options: {
12
+ privateKeyPath: string;
13
+ host: string;
14
+ port: number;
15
+ user: string;
16
+ }): string;
17
+ /** Verify the MCP key can authenticate over the forwarded SSH port. */
18
+ export declare function probeSshAuth(options: {
19
+ privateKeyPath: string;
20
+ host: string;
21
+ port: number;
22
+ user: string;
23
+ }): Promise<boolean>;
@@ -0,0 +1,73 @@
1
+ import { execFile } from "node:child_process";
2
+ import { mkdtemp, readFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
4
+ import { join } from "node:path";
5
+ import { promisify } from "node:util";
6
+ const execFileAsync = promisify(execFile);
7
+ /** Generate a throwaway ed25519 keypair in a temp dir. */
8
+ export async function generateSshKeypair() {
9
+ const dir = await mkdtemp(join(tmpdir(), "anka-mcp-ssh-"));
10
+ const privateKeyPath = join(dir, "id_ed25519");
11
+ await execFileAsync("ssh-keygen", [
12
+ "-t",
13
+ "ed25519",
14
+ "-N",
15
+ "",
16
+ "-C",
17
+ "anka-mcp",
18
+ "-f",
19
+ privateKeyPath,
20
+ "-q"
21
+ ]);
22
+ const publicKey = (await readFile(`${privateKeyPath}.pub`, "utf8")).trim();
23
+ return { privateKeyPath, publicKey };
24
+ }
25
+ /** Shell script that installs `publicKey` into ~/.ssh/authorized_keys. */
26
+ export function buildAuthorizedKeysStartupScript(publicKey) {
27
+ const escaped = publicKey.replace(/\\/g, "\\\\").replace(/'/g, "'\\''");
28
+ return ("set -e; " +
29
+ "mkdir -p ~/.ssh; chmod 700 ~/.ssh; " +
30
+ `grep -qxF '${escaped}' ~/.ssh/authorized_keys 2>/dev/null || printf '%s\\n' '${escaped}' >> ~/.ssh/authorized_keys; ` +
31
+ "chmod 600 ~/.ssh/authorized_keys");
32
+ }
33
+ /** Base64-encode a startup script for the controller API `startup_script` field. */
34
+ export function encodeStartupScript(script) {
35
+ return Buffer.from(script, "utf8").toString("base64");
36
+ }
37
+ export function buildSshCommand(options) {
38
+ return (`ssh -i ${options.privateKeyPath} -p ${options.port} ` +
39
+ `-o IdentitiesOnly=yes -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ` +
40
+ `${options.user}@${options.host}`);
41
+ }
42
+ const SSH_PROBE_CONNECT_TIMEOUT_SECONDS = 5;
43
+ /** Verify the MCP key can authenticate over the forwarded SSH port. */
44
+ export async function probeSshAuth(options) {
45
+ try {
46
+ await execFileAsync("ssh", [
47
+ "-i",
48
+ options.privateKeyPath,
49
+ "-p",
50
+ String(options.port),
51
+ "-o",
52
+ "IdentitiesOnly=yes",
53
+ "-o",
54
+ "StrictHostKeyChecking=no",
55
+ "-o",
56
+ "UserKnownHostsFile=/dev/null",
57
+ "-o",
58
+ "BatchMode=yes",
59
+ "-o",
60
+ `ConnectTimeout=${SSH_PROBE_CONNECT_TIMEOUT_SECONDS}`,
61
+ `${options.user}@${options.host}`,
62
+ "true"
63
+ ], {
64
+ timeout: (SSH_PROBE_CONNECT_TIMEOUT_SECONDS + 5) * 1000,
65
+ env: { ...process.env, SSH_AUTH_SOCK: "" }
66
+ });
67
+ return true;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ }
73
+ //# sourceMappingURL=ssh-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-key.js","sourceRoot":"","sources":["../src/ssh-key.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAO1C,0DAA0D;AAC1D,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,eAAe,CAAC,CAAC,CAAC;IAC3D,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC;IAC/C,MAAM,aAAa,CAAC,YAAY,EAAE;QAChC,IAAI;QACJ,SAAS;QACT,IAAI;QACJ,EAAE;QACF,IAAI;QACJ,UAAU;QACV,IAAI;QACJ,cAAc;QACd,IAAI;KACL,CAAC,CAAC;IACH,MAAM,SAAS,GAAG,CAAC,MAAM,QAAQ,CAAC,GAAG,cAAc,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC3E,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,CAAC;AACvC,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,gCAAgC,CAAC,SAAiB;IAChE,MAAM,OAAO,GAAG,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACxE,OAAO,CACL,UAAU;QACV,qCAAqC;QACrC,cAAc,OAAO,2DAA2D,OAAO,+BAA+B;QACtH,kCAAkC,CACnC,CAAC;AACJ,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,OAAO,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AACxD,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAK/B;IACC,OAAO,CACL,UAAU,OAAO,CAAC,cAAc,OAAO,OAAO,CAAC,IAAI,GAAG;QACtD,oFAAoF;QACpF,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE,CAClC,CAAC;AACJ,CAAC;AAED,MAAM,iCAAiC,GAAG,CAAC,CAAC;AAE5C,uEAAuE;AACvE,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,OAKlC;IACC,IAAI,CAAC;QACH,MAAM,aAAa,CACjB,KAAK,EACL;YACE,IAAI;YACJ,OAAO,CAAC,cAAc;YACtB,IAAI;YACJ,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC;YACpB,IAAI;YACJ,oBAAoB;YACpB,IAAI;YACJ,0BAA0B;YAC1B,IAAI;YACJ,8BAA8B;YAC9B,IAAI;YACJ,eAAe;YACf,IAAI;YACJ,kBAAkB,iCAAiC,EAAE;YACrD,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,IAAI,EAAE;YACjC,MAAM;SACP,EACD;YACE,OAAO,EAAE,CAAC,iCAAiC,GAAG,CAAC,CAAC,GAAG,IAAI;YACvD,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,aAAa,EAAE,EAAE,EAAE;SAC3C,CACF,CAAC;QACF,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,10 @@
1
+ export interface CleanupResult {
2
+ terminated: string[];
3
+ failed: {
4
+ instance_id: string;
5
+ error: string;
6
+ }[];
7
+ skipped?: string;
8
+ }
9
+ /** Best-effort terminate all controller instances owned by a revoked credential. */
10
+ export declare function cleanupCredentialInstances(credentialId: string, instanceIds: string[]): Promise<CleanupResult>;
@@ -0,0 +1,28 @@
1
+ import { config } from "../config.js";
2
+ import { controller } from "../controller.js";
3
+ import { getTokenStore } from "./store.js";
4
+ /** Best-effort terminate all controller instances owned by a revoked credential. */
5
+ export async function cleanupCredentialInstances(credentialId, instanceIds) {
6
+ if (!config.revokeCleanupEnabled) {
7
+ return { terminated: [], failed: [] };
8
+ }
9
+ if (!config.controllerEnabled) {
10
+ getTokenStore().deleteInstancesForCredential(credentialId);
11
+ return { terminated: [], failed: [], skipped: "controller disabled" };
12
+ }
13
+ const terminated = [];
14
+ const failed = [];
15
+ const store = getTokenStore();
16
+ for (const instanceId of instanceIds) {
17
+ try {
18
+ await controller.terminateVm(instanceId);
19
+ store.releaseInstance(credentialId, instanceId);
20
+ terminated.push(instanceId);
21
+ }
22
+ catch (error) {
23
+ failed.push({ instance_id: instanceId, error: String(error) });
24
+ }
25
+ }
26
+ return { terminated, failed };
27
+ }
28
+ //# sourceMappingURL=cleanup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cleanup.js","sourceRoot":"","sources":["../../src/tokens/cleanup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAQ3C,oFAAoF;AACpF,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,YAAoB,EACpB,WAAqB;IAErB,IAAI,CAAC,MAAM,CAAC,oBAAoB,EAAE,CAAC;QACjC,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IACxC,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,iBAAiB,EAAE,CAAC;QAC9B,aAAa,EAAE,CAAC,4BAA4B,CAAC,YAAY,CAAC,CAAC;QAC3D,OAAO,EAAE,UAAU,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACxE,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,MAAM,GAA6C,EAAE,CAAC;IAC5D,MAAM,KAAK,GAAG,aAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC;YACzC,KAAK,CAAC,eAAe,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;YAChD,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACjE,CAAC;IACH,CAAC;IAED,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC;AAChC,CAAC"}