winhelm-mcp 1.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 (125) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +21 -0
  3. package/README.md +646 -0
  4. package/dist/config/config-manager.d.ts +50 -0
  5. package/dist/config/config-manager.d.ts.map +1 -0
  6. package/dist/config/config-manager.js +300 -0
  7. package/dist/config/config-manager.js.map +1 -0
  8. package/dist/config/default-config.d.ts +4 -0
  9. package/dist/config/default-config.d.ts.map +1 -0
  10. package/dist/config/default-config.js +48 -0
  11. package/dist/config/default-config.js.map +1 -0
  12. package/dist/config/profiles.d.ts +16 -0
  13. package/dist/config/profiles.d.ts.map +1 -0
  14. package/dist/config/profiles.js +208 -0
  15. package/dist/config/profiles.js.map +1 -0
  16. package/dist/engine/filesystem.d.ts +52 -0
  17. package/dist/engine/filesystem.d.ts.map +1 -0
  18. package/dist/engine/filesystem.js +499 -0
  19. package/dist/engine/filesystem.js.map +1 -0
  20. package/dist/engine/http-client.d.ts +10 -0
  21. package/dist/engine/http-client.d.ts.map +1 -0
  22. package/dist/engine/http-client.js +93 -0
  23. package/dist/engine/http-client.js.map +1 -0
  24. package/dist/engine/pdf-generator.d.ts +15 -0
  25. package/dist/engine/pdf-generator.d.ts.map +1 -0
  26. package/dist/engine/pdf-generator.js +325 -0
  27. package/dist/engine/pdf-generator.js.map +1 -0
  28. package/dist/engine/powershell-runner.d.ts +23 -0
  29. package/dist/engine/powershell-runner.d.ts.map +1 -0
  30. package/dist/engine/powershell-runner.js +148 -0
  31. package/dist/engine/powershell-runner.js.map +1 -0
  32. package/dist/engine/ripgrep.d.ts +20 -0
  33. package/dist/engine/ripgrep.d.ts.map +1 -0
  34. package/dist/engine/ripgrep.js +173 -0
  35. package/dist/engine/ripgrep.js.map +1 -0
  36. package/dist/engine/system.d.ts +67 -0
  37. package/dist/engine/system.d.ts.map +1 -0
  38. package/dist/engine/system.js +558 -0
  39. package/dist/engine/system.js.map +1 -0
  40. package/dist/engine/task-manager.d.ts +66 -0
  41. package/dist/engine/task-manager.d.ts.map +1 -0
  42. package/dist/engine/task-manager.js +203 -0
  43. package/dist/engine/task-manager.js.map +1 -0
  44. package/dist/gateway/dashboard-html.d.ts +7 -0
  45. package/dist/gateway/dashboard-html.d.ts.map +1 -0
  46. package/dist/gateway/dashboard-html.js +624 -0
  47. package/dist/gateway/dashboard-html.js.map +1 -0
  48. package/dist/gateway/preview-html.d.ts +5 -0
  49. package/dist/gateway/preview-html.d.ts.map +1 -0
  50. package/dist/gateway/preview-html.js +351 -0
  51. package/dist/gateway/preview-html.js.map +1 -0
  52. package/dist/gateway/rate-limiter.d.ts +8 -0
  53. package/dist/gateway/rate-limiter.d.ts.map +1 -0
  54. package/dist/gateway/rate-limiter.js +51 -0
  55. package/dist/gateway/rate-limiter.js.map +1 -0
  56. package/dist/gateway/server.d.ts +18 -0
  57. package/dist/gateway/server.d.ts.map +1 -0
  58. package/dist/gateway/server.js +337 -0
  59. package/dist/gateway/server.js.map +1 -0
  60. package/dist/gateway/sse-gateway.d.ts +43 -0
  61. package/dist/gateway/sse-gateway.d.ts.map +1 -0
  62. package/dist/gateway/sse-gateway.js +161 -0
  63. package/dist/gateway/sse-gateway.js.map +1 -0
  64. package/dist/gateway/streamable-gateway.d.ts +39 -0
  65. package/dist/gateway/streamable-gateway.d.ts.map +1 -0
  66. package/dist/gateway/streamable-gateway.js +176 -0
  67. package/dist/gateway/streamable-gateway.js.map +1 -0
  68. package/dist/index.d.ts +5 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +164 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/tools/desktop-tools.d.ts +3 -0
  73. package/dist/tools/desktop-tools.d.ts.map +1 -0
  74. package/dist/tools/desktop-tools.js +280 -0
  75. package/dist/tools/desktop-tools.js.map +1 -0
  76. package/dist/tools/file-tools.d.ts +3 -0
  77. package/dist/tools/file-tools.d.ts.map +1 -0
  78. package/dist/tools/file-tools.js +433 -0
  79. package/dist/tools/file-tools.js.map +1 -0
  80. package/dist/tools/index.d.ts +12 -0
  81. package/dist/tools/index.d.ts.map +1 -0
  82. package/dist/tools/index.js +16 -0
  83. package/dist/tools/index.js.map +1 -0
  84. package/dist/tools/network-tools.d.ts +3 -0
  85. package/dist/tools/network-tools.d.ts.map +1 -0
  86. package/dist/tools/network-tools.js +108 -0
  87. package/dist/tools/network-tools.js.map +1 -0
  88. package/dist/tools/registry.d.ts +50 -0
  89. package/dist/tools/registry.d.ts.map +1 -0
  90. package/dist/tools/registry.js +54 -0
  91. package/dist/tools/registry.js.map +1 -0
  92. package/dist/tools/terminal-tools.d.ts +3 -0
  93. package/dist/tools/terminal-tools.d.ts.map +1 -0
  94. package/dist/tools/terminal-tools.js +152 -0
  95. package/dist/tools/terminal-tools.js.map +1 -0
  96. package/dist/tools/tool-wrapper.d.ts +7 -0
  97. package/dist/tools/tool-wrapper.d.ts.map +1 -0
  98. package/dist/tools/tool-wrapper.js +47 -0
  99. package/dist/tools/tool-wrapper.js.map +1 -0
  100. package/dist/types/index.d.ts +261 -0
  101. package/dist/types/index.d.ts.map +1 -0
  102. package/dist/types/index.js +2 -0
  103. package/dist/types/index.js.map +1 -0
  104. package/dist/utils/file-logger.d.ts +24 -0
  105. package/dist/utils/file-logger.d.ts.map +1 -0
  106. package/dist/utils/file-logger.js +117 -0
  107. package/dist/utils/file-logger.js.map +1 -0
  108. package/dist/utils/logger.d.ts +51 -0
  109. package/dist/utils/logger.d.ts.map +1 -0
  110. package/dist/utils/logger.js +189 -0
  111. package/dist/utils/logger.js.map +1 -0
  112. package/dist/utils/sanitizer.d.ts +13 -0
  113. package/dist/utils/sanitizer.d.ts.map +1 -0
  114. package/dist/utils/sanitizer.js +60 -0
  115. package/dist/utils/sanitizer.js.map +1 -0
  116. package/docs/EXAMPLES.md +171 -0
  117. package/docs/PROFILES.md +423 -0
  118. package/docs/SECURITY.md +190 -0
  119. package/docs/TOOLS.md +366 -0
  120. package/manifest.json +74 -0
  121. package/package.json +89 -0
  122. package/scripts/build-exe.ps1 +37 -0
  123. package/scripts/manage-service.ps1 +112 -0
  124. package/server.json +23 -0
  125. package/winhelm.config.example.json +30 -0
@@ -0,0 +1,351 @@
1
+ import path from "node:path";
2
+ import fs from "node:fs/promises";
3
+ import { existsSync, statSync } from "node:fs";
4
+ import { ConfigManager } from "../config/config-manager.js";
5
+ import { markdownToHtml } from "../engine/pdf-generator.js";
6
+ /**
7
+ * Generate standalone responsive Dark-Mode HTML preview page for any file
8
+ */
9
+ export async function getFilePreviewHtml(filePath) {
10
+ const configManager = ConfigManager.getInstance();
11
+ const resolvedPath = path.resolve(filePath);
12
+ const pathCheck = configManager.isPathAllowed(resolvedPath);
13
+ if (!pathCheck.allowed) {
14
+ return renderErrorPage(resolvedPath, pathCheck.reason || "Access to path is blocked by security policy.");
15
+ }
16
+ if (!existsSync(resolvedPath)) {
17
+ return renderErrorPage(resolvedPath, "File does not exist.");
18
+ }
19
+ const stat = statSync(resolvedPath);
20
+ if (stat.isDirectory()) {
21
+ return renderErrorPage(resolvedPath, "Target is a directory, not a file.");
22
+ }
23
+ const fileName = path.basename(resolvedPath);
24
+ const ext = path.extname(resolvedPath).toLowerCase();
25
+ const fileSizeKb = (stat.size / 1024).toFixed(1);
26
+ const imageExts = [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".ico"];
27
+ const isImage = imageExts.includes(ext);
28
+ const isPdf = ext === ".pdf";
29
+ let bodyContent = "";
30
+ let lineCount = 0;
31
+ if (isImage) {
32
+ const rawBuffer = await fs.readFile(resolvedPath);
33
+ const mime = ext === ".svg" ? "image/svg+xml" : `image/${ext.replace(".", "")}`;
34
+ const base64 = rawBuffer.toString("base64");
35
+ bodyContent = `
36
+ <div class="image-container">
37
+ <img src="data:${mime};base64,${base64}" alt="${escapeHtml(fileName)}" />
38
+ </div>
39
+ `;
40
+ }
41
+ else if (isPdf) {
42
+ const rawBuffer = await fs.readFile(resolvedPath);
43
+ const base64 = rawBuffer.toString("base64");
44
+ bodyContent = `
45
+ <div class="pdf-container">
46
+ <iframe src="data:application/pdf;base64,${base64}" width="100%" height="800px"></iframe>
47
+ </div>
48
+ `;
49
+ }
50
+ else {
51
+ // Text / Code / Markdown
52
+ try {
53
+ const content = await fs.readFile(resolvedPath, "utf-8");
54
+ const lines = content.split(/\r?\n/);
55
+ lineCount = lines.length;
56
+ if (ext === ".md" || ext === ".markdown") {
57
+ const renderedMd = markdownToHtml(content, fileName);
58
+ // Extract inner body from renderedMd
59
+ const bodyMatch = renderedMd.match(/<body>([\s\S]*?)<\/body>/i);
60
+ const mdHtml = bodyMatch ? bodyMatch[1] : renderedMd;
61
+ bodyContent = `
62
+ <div class="tabs">
63
+ <button class="tab-btn active" onclick="switchTab('rendered')">Preview</button>
64
+ <button class="tab-btn" onclick="switchTab('raw')">Raw Markdown</button>
65
+ </div>
66
+ <div id="tab-rendered" class="tab-content active markdown-body">
67
+ ${mdHtml}
68
+ </div>
69
+ <div id="tab-raw" class="tab-content">
70
+ <pre class="code-view"><code>${escapeHtml(content)}</code></pre>
71
+ </div>
72
+ `;
73
+ }
74
+ else {
75
+ // Source Code or JSON with line numbers
76
+ const numberedLines = lines
77
+ .map((line, idx) => `<div class="code-line"><span class="line-num">${idx + 1}</span><span class="line-code">${escapeHtml(line)}</span></div>`)
78
+ .join("");
79
+ bodyContent = `
80
+ <div class="code-container">
81
+ ${numberedLines}
82
+ </div>
83
+ `;
84
+ }
85
+ }
86
+ catch (err) {
87
+ return renderErrorPage(resolvedPath, `Error reading file: ${err.message}`);
88
+ }
89
+ }
90
+ return `<!DOCTYPE html>
91
+ <html lang="en">
92
+ <head>
93
+ <meta charset="UTF-8">
94
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
95
+ <title>${escapeHtml(fileName)} - WinHelm Preview</title>
96
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220%22><text y=%2226%22 font-size=%2226%22>⚡</text></svg>">
97
+ <style>
98
+ :root {
99
+ --bg: #0d1117;
100
+ --card-bg: #161b22;
101
+ --border: #30363d;
102
+ --text: #c9d1d9;
103
+ --text-muted: #8b949e;
104
+ --accent: #58a6ff;
105
+ --accent-hover: #1f6feb;
106
+ --code-bg: #090d13;
107
+ }
108
+ * { box-sizing: border-box; }
109
+ body {
110
+ margin: 0;
111
+ padding: 0;
112
+ background-color: var(--bg);
113
+ color: var(--text);
114
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
115
+ }
116
+ .header {
117
+ background-color: var(--card-bg);
118
+ border-bottom: 1px solid var(--border);
119
+ padding: 12px 24px;
120
+ display: flex;
121
+ align-items: center;
122
+ justify-content: space-between;
123
+ position: sticky;
124
+ top: 0;
125
+ z-index: 100;
126
+ }
127
+ .file-info {
128
+ display: flex;
129
+ align-items: center;
130
+ gap: 12px;
131
+ }
132
+ .file-name {
133
+ font-size: 16px;
134
+ font-weight: 600;
135
+ color: #f0f6fc;
136
+ }
137
+ .file-meta {
138
+ font-size: 12px;
139
+ color: var(--text-muted);
140
+ background: #21262d;
141
+ padding: 2px 8px;
142
+ border-radius: 12px;
143
+ border: 1px solid var(--border);
144
+ }
145
+ .actions {
146
+ display: flex;
147
+ gap: 8px;
148
+ }
149
+ .btn {
150
+ background: #21262d;
151
+ color: var(--text);
152
+ border: 1px solid var(--border);
153
+ padding: 6px 12px;
154
+ border-radius: 6px;
155
+ cursor: pointer;
156
+ font-size: 13px;
157
+ text-decoration: none;
158
+ transition: background 0.2s;
159
+ }
160
+ .btn:hover {
161
+ background: #30363d;
162
+ color: #fff;
163
+ }
164
+ .btn-primary {
165
+ background: #238636;
166
+ border-color: #2ea043;
167
+ color: #fff;
168
+ }
169
+ .btn-primary:hover {
170
+ background: #2ea043;
171
+ }
172
+ .container {
173
+ max-width: 1200px;
174
+ margin: 24px auto;
175
+ padding: 0 16px;
176
+ }
177
+ .preview-box {
178
+ background: var(--card-bg);
179
+ border: 1px solid var(--border);
180
+ border-radius: 8px;
181
+ overflow: hidden;
182
+ padding: 24px;
183
+ }
184
+ .tabs {
185
+ display: flex;
186
+ gap: 8px;
187
+ margin-bottom: 16px;
188
+ border-bottom: 1px solid var(--border);
189
+ padding-bottom: 8px;
190
+ }
191
+ .tab-btn {
192
+ background: none;
193
+ border: none;
194
+ color: var(--text-muted);
195
+ padding: 6px 16px;
196
+ cursor: pointer;
197
+ font-weight: 500;
198
+ font-size: 14px;
199
+ border-radius: 6px;
200
+ }
201
+ .tab-btn.active {
202
+ background: #21262d;
203
+ color: var(--accent);
204
+ }
205
+ .tab-content { display: none; }
206
+ .tab-content.active { display: block; }
207
+ .code-container {
208
+ background: var(--code-bg);
209
+ border-radius: 6px;
210
+ font-family: "Cascadia Code", Consolas, monospace;
211
+ font-size: 13px;
212
+ overflow-x: auto;
213
+ padding: 12px 0;
214
+ }
215
+ .code-line {
216
+ display: flex;
217
+ line-height: 1.6;
218
+ }
219
+ .code-line:hover { background: #161b22; }
220
+ .line-num {
221
+ width: 50px;
222
+ text-align: right;
223
+ padding-right: 16px;
224
+ color: #484f58;
225
+ user-select: none;
226
+ }
227
+ .line-code {
228
+ white-space: pre;
229
+ color: #e6edf3;
230
+ flex: 1;
231
+ padding-right: 16px;
232
+ }
233
+ .image-container {
234
+ text-align: center;
235
+ padding: 30px;
236
+ }
237
+ .image-container img {
238
+ max-width: 100%;
239
+ border-radius: 6px;
240
+ border: 1px solid var(--border);
241
+ }
242
+ /* Markdown dark styles */
243
+ .markdown-body {
244
+ color: #e6edf3;
245
+ line-height: 1.7;
246
+ }
247
+ .markdown-body h1, .markdown-body h2 {
248
+ border-bottom: 1px solid var(--border);
249
+ padding-bottom: 0.3em;
250
+ }
251
+ .markdown-body table {
252
+ border-collapse: collapse;
253
+ width: 100%;
254
+ margin: 16px 0;
255
+ }
256
+ .markdown-body th, .markdown-body td {
257
+ border: 1px solid var(--border);
258
+ padding: 8px 12px;
259
+ }
260
+ .markdown-body th { background: #21262d; }
261
+ .markdown-body pre {
262
+ background: var(--code-bg);
263
+ border: 1px solid var(--border);
264
+ padding: 14px;
265
+ border-radius: 6px;
266
+ overflow-x: auto;
267
+ }
268
+ .markdown-body blockquote {
269
+ border-left: 4px solid var(--accent);
270
+ margin: 0;
271
+ padding: 4px 16px;
272
+ color: var(--text-muted);
273
+ background: #161b22;
274
+ }
275
+ </style>
276
+ </head>
277
+ <body>
278
+ <div class="header">
279
+ <div class="file-info">
280
+ <span style="font-size: 20px;">📄</span>
281
+ <span class="file-name">${escapeHtml(fileName)}</span>
282
+ <span class="file-meta">${fileSizeKb} KB</span>
283
+ ${lineCount > 0 ? `<span class="file-meta">${lineCount} lines</span>` : ""}
284
+ <span class="file-meta">${escapeHtml(ext.toUpperCase() || "FILE")}</span>
285
+ </div>
286
+ <div class="actions">
287
+ <button class="btn" onclick="copyPath()">Copy Path</button>
288
+ <a class="btn" href="/dashboard">Dashboard</a>
289
+ </div>
290
+ </div>
291
+
292
+ <div class="container">
293
+ <div class="preview-box">
294
+ ${bodyContent}
295
+ </div>
296
+ </div>
297
+
298
+ <script>
299
+ function switchTab(tab) {
300
+ document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
301
+ document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
302
+ if (tab === 'rendered') {
303
+ document.querySelectorAll('.tab-btn')[0].classList.add('active');
304
+ document.getElementById('tab-rendered').classList.add('active');
305
+ } else {
306
+ document.querySelectorAll('.tab-btn')[1].classList.add('active');
307
+ document.getElementById('tab-raw').classList.add('active');
308
+ }
309
+ }
310
+ function copyPath() {
311
+ navigator.clipboard.writeText(${JSON.stringify(resolvedPath)}).then(() => {
312
+ alert('Copied path to clipboard!');
313
+ });
314
+ }
315
+ </script>
316
+ </body>
317
+ </html>`;
318
+ }
319
+ function renderErrorPage(targetPath, message) {
320
+ return `<!DOCTYPE html>
321
+ <html>
322
+ <head>
323
+ <meta charset="utf-8">
324
+ <title>Preview Error - WinHelm</title>
325
+ <style>
326
+ body { background: #0d1117; color: #f85149; font-family: sans-serif; padding: 40px; text-align: center; }
327
+ .box { background: #161b22; border: 1px solid #30363d; border-radius: 8px; max-width: 600px; margin: 0 auto; padding: 30px; }
328
+ h2 { color: #f85149; }
329
+ p { color: #8b949e; }
330
+ a { color: #58a6ff; text-decoration: none; }
331
+ </style>
332
+ </head>
333
+ <body>
334
+ <div class="box">
335
+ <h2>Access Blocked or File Not Found</h2>
336
+ <p>${escapeHtml(message)}</p>
337
+ <p><code>${escapeHtml(targetPath)}</code></p>
338
+ <a href="/dashboard">Return to Dashboard</a>
339
+ </div>
340
+ </body>
341
+ </html>`;
342
+ }
343
+ function escapeHtml(str) {
344
+ return str
345
+ .replace(/&/g, "&amp;")
346
+ .replace(/</g, "&lt;")
347
+ .replace(/>/g, "&gt;")
348
+ .replace(/"/g, "&quot;")
349
+ .replace(/'/g, "&#039;");
350
+ }
351
+ //# sourceMappingURL=preview-html.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-html.js","sourceRoot":"","sources":["../../src/gateway/preview-html.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { Request, Response, NextFunction } from "express";
2
+ export declare class RateLimiter {
3
+ private clients;
4
+ private cleanupTimer;
5
+ constructor();
6
+ middleware(): (req: Request, res: Response, next: NextFunction) => void;
7
+ }
8
+ //# sourceMappingURL=rate-limiter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/gateway/rate-limiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAS/D,qBAAa,WAAW;IACtB,OAAO,CAAC,OAAO,CAAmC;IAClD,OAAO,CAAC,YAAY,CAAiB;IAErC,cAWC;IAEM,UAAU,UACF,OAAO,OAAO,QAAQ,QAAQ,YAAY,KAAG,IAAI,CAoC/D;CACF"}
@@ -0,0 +1,51 @@
1
+ import { logger } from "../utils/logger.js";
2
+ import { ConfigManager } from "../config/config-manager.js";
3
+ export class RateLimiter {
4
+ clients = new Map();
5
+ cleanupTimer;
6
+ constructor() {
7
+ // Prune stale records every minute
8
+ this.cleanupTimer = setInterval(() => {
9
+ const now = Date.now();
10
+ for (const [key, record] of this.clients.entries()) {
11
+ if (now > record.resetTime) {
12
+ this.clients.delete(key);
13
+ }
14
+ }
15
+ }, 60000);
16
+ this.cleanupTimer.unref();
17
+ }
18
+ middleware() {
19
+ return (req, res, next) => {
20
+ // Exclude internal monitor & health endpoints from rate limiting
21
+ if (req.path === "/health" ||
22
+ req.path === "/" ||
23
+ req.path === "/dashboard" ||
24
+ req.path.startsWith("/api/monitor")) {
25
+ return next();
26
+ }
27
+ const config = ConfigManager.getInstance().getConfig();
28
+ const limit = config.rateLimitPerMinute || 120;
29
+ const key = req.ip || req.socket.remoteAddress || "global";
30
+ const now = Date.now();
31
+ let record = this.clients.get(key);
32
+ if (!record || now > record.resetTime) {
33
+ record = { count: 1, resetTime: now + 60000 };
34
+ this.clients.set(key, record);
35
+ return next();
36
+ }
37
+ record.count++;
38
+ if (record.count > limit) {
39
+ logger.security(`Rate limit exceeded (${record.count}/${limit} req/min)`, `IP: ${key}`);
40
+ res.status(429).json({
41
+ error: "Too Many Requests",
42
+ message: `Rate limit of ${limit} requests per minute exceeded. Please slow down.`,
43
+ retryAfterSeconds: Math.ceil((record.resetTime - now) / 1000),
44
+ });
45
+ return;
46
+ }
47
+ next();
48
+ };
49
+ }
50
+ }
51
+ //# sourceMappingURL=rate-limiter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rate-limiter.js","sourceRoot":"","sources":["../../src/gateway/rate-limiter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAO5D,MAAM,OAAO,WAAW;IACd,OAAO,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC1C,YAAY,CAAiB;IAErC;QACE,mCAAmC;QACnC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE;YACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,KAAK,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC;gBACnD,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;oBAC3B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC3B,CAAC;YACH,CAAC;QACH,CAAC,EAAE,KAAK,CAAC,CAAC;QACV,IAAI,CAAC,YAAY,CAAC,KAAK,EAAE,CAAC;IAC5B,CAAC;IAEM,UAAU;QACf,OAAO,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAQ,EAAE;YAC/D,iEAAiE;YACjE,IACE,GAAG,CAAC,IAAI,KAAK,SAAS;gBACtB,GAAG,CAAC,IAAI,KAAK,GAAG;gBAChB,GAAG,CAAC,IAAI,KAAK,YAAY;gBACzB,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC,EACnC,CAAC;gBACD,OAAO,IAAI,EAAE,CAAC;YAChB,CAAC;YAED,MAAM,MAAM,GAAG,aAAa,CAAC,WAAW,EAAE,CAAC,SAAS,EAAE,CAAC;YACvD,MAAM,KAAK,GAAG,MAAM,CAAC,kBAAkB,IAAI,GAAG,CAAC;YAC/C,MAAM,GAAG,GAAG,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,QAAQ,CAAC;YAC3D,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAEvB,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACnC,IAAI,CAAC,MAAM,IAAI,GAAG,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;gBACtC,MAAM,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,SAAS,EAAE,GAAG,GAAG,KAAK,EAAE,CAAC;gBAC9C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;gBAC9B,OAAO,IAAI,EAAE,CAAC;YAChB,CAAC;YAED,MAAM,CAAC,KAAK,EAAE,CAAC;YACf,IAAI,MAAM,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC;gBACzB,MAAM,CAAC,QAAQ,CAAC,wBAAwB,MAAM,CAAC,KAAK,IAAI,KAAK,WAAW,EAAE,OAAO,GAAG,EAAE,CAAC,CAAC;gBACxF,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;oBACnB,KAAK,EAAE,mBAAmB;oBAC1B,OAAO,EAAE,iBAAiB,KAAK,kDAAkD;oBACjF,iBAAiB,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC;iBAC9D,CAAC,CAAC;gBACH,OAAO;YACT,CAAC;YAED,IAAI,EAAE,CAAC;QACT,CAAC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,18 @@
1
+ import { type Express } from "express";
2
+ import type { Server } from "node:http";
3
+ export interface ServerOptions {
4
+ port: number;
5
+ host: string;
6
+ authToken?: string | null;
7
+ }
8
+ export declare function safeCompare(a: string, b: string): boolean;
9
+ /**
10
+ * Validate Host header against loopback, bound interface, and allowed hosts whitelist (DNS Rebinding protection)
11
+ */
12
+ export declare function isHostAllowed(hostHeader?: string, allowedHosts?: string[], bindHost?: string): boolean;
13
+ export declare function createServer(options: ServerOptions): {
14
+ app: Express;
15
+ start: () => Promise<Server>;
16
+ stop: (server: Server) => Promise<void>;
17
+ };
18
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/gateway/server.ts"],"names":[],"mappings":"AAAA,OAAgB,EAAE,KAAK,OAAO,EAAkD,MAAM,SAAS,CAAC;AAGhG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAaxC,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAKzD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAC3B,UAAU,CAAC,EAAE,MAAM,EACnB,YAAY,GAAE,MAAM,EAAoD,EACxE,QAAQ,SAAc,GACrB,OAAO,CAwCT;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,aAAa,GAAG;IACpD,GAAG,EAAE,OAAO,CAAC;IACb,KAAK,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACzC,CA0TA"}