@yan162/changewayguard 6.8.25

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 (285) hide show
  1. package/LICENSE +21 -0
  2. package/OpenClaw-linux_Mac-Guide-zh.md +89 -0
  3. package/dashboard-dist/api/122.index.js +95 -0
  4. package/dashboard-dist/api/122.index.js.map +1 -0
  5. package/dashboard-dist/api/143.index.js +2734 -0
  6. package/dashboard-dist/api/143.index.js.map +1 -0
  7. package/dashboard-dist/api/154.index.js +4151 -0
  8. package/dashboard-dist/api/154.index.js.map +1 -0
  9. package/dashboard-dist/api/173.index.js +24112 -0
  10. package/dashboard-dist/api/173.index.js.map +1 -0
  11. package/dashboard-dist/api/217.index.js +44 -0
  12. package/dashboard-dist/api/217.index.js.map +1 -0
  13. package/dashboard-dist/api/222.index.js +90 -0
  14. package/dashboard-dist/api/222.index.js.map +1 -0
  15. package/dashboard-dist/api/280.index.js +213 -0
  16. package/dashboard-dist/api/280.index.js.map +1 -0
  17. package/dashboard-dist/api/369.index.js +115 -0
  18. package/dashboard-dist/api/369.index.js.map +1 -0
  19. package/dashboard-dist/api/374.index.js +1896 -0
  20. package/dashboard-dist/api/374.index.js.map +1 -0
  21. package/dashboard-dist/api/424.index.js +135 -0
  22. package/dashboard-dist/api/424.index.js.map +1 -0
  23. package/dashboard-dist/api/445.index.js +3562 -0
  24. package/dashboard-dist/api/445.index.js.map +1 -0
  25. package/dashboard-dist/api/555.index.js +496 -0
  26. package/dashboard-dist/api/555.index.js.map +1 -0
  27. package/dashboard-dist/api/573.index.js +806 -0
  28. package/dashboard-dist/api/573.index.js.map +1 -0
  29. package/dashboard-dist/api/580.index.js +1420 -0
  30. package/dashboard-dist/api/580.index.js.map +1 -0
  31. package/dashboard-dist/api/581.index.js +67 -0
  32. package/dashboard-dist/api/581.index.js.map +1 -0
  33. package/dashboard-dist/api/598.index.js +328 -0
  34. package/dashboard-dist/api/598.index.js.map +1 -0
  35. package/dashboard-dist/api/720.index.js +105 -0
  36. package/dashboard-dist/api/720.index.js.map +1 -0
  37. package/dashboard-dist/api/744.index.js +333 -0
  38. package/dashboard-dist/api/744.index.js.map +1 -0
  39. package/dashboard-dist/api/818.index.js +374 -0
  40. package/dashboard-dist/api/818.index.js.map +1 -0
  41. package/dashboard-dist/api/831.index.js +99 -0
  42. package/dashboard-dist/api/831.index.js.map +1 -0
  43. package/dashboard-dist/api/84.index.js +64 -0
  44. package/dashboard-dist/api/84.index.js.map +1 -0
  45. package/dashboard-dist/api/900.index.js +81 -0
  46. package/dashboard-dist/api/900.index.js.map +1 -0
  47. package/dashboard-dist/api/917.index.js +88 -0
  48. package/dashboard-dist/api/917.index.js.map +1 -0
  49. package/dashboard-dist/api/927.index.js +4250 -0
  50. package/dashboard-dist/api/927.index.js.map +1 -0
  51. package/dashboard-dist/api/948.index.js +64 -0
  52. package/dashboard-dist/api/948.index.js.map +1 -0
  53. package/dashboard-dist/api/982.index.js +67 -0
  54. package/dashboard-dist/api/982.index.js.map +1 -0
  55. package/dashboard-dist/api/99.index.js +1176 -0
  56. package/dashboard-dist/api/99.index.js.map +1 -0
  57. package/dashboard-dist/api/drizzle/sqlite/0000_short_captain_stacy.sql +70 -0
  58. package/dashboard-dist/api/drizzle/sqlite/0001_closed_magus.sql +10 -0
  59. package/dashboard-dist/api/drizzle/sqlite/0002_agent_capability_observation.sql +38 -0
  60. package/dashboard-dist/api/drizzle/sqlite/0003_auth_magic_link.sql +28 -0
  61. package/dashboard-dist/api/drizzle/sqlite/0004_static_scan_fields.sql +8 -0
  62. package/dashboard-dist/api/drizzle/sqlite/0005_gateway_activity.sql +24 -0
  63. package/dashboard-dist/api/drizzle/sqlite/0006_sour_marauders.sql +41 -0
  64. package/dashboard-dist/api/drizzle/sqlite/meta/0000_snapshot.json +460 -0
  65. package/dashboard-dist/api/drizzle/sqlite/meta/0001_snapshot.json +536 -0
  66. package/dashboard-dist/api/drizzle/sqlite/meta/0006_snapshot.json +1249 -0
  67. package/dashboard-dist/api/drizzle/sqlite/meta/_journal.json +55 -0
  68. package/dashboard-dist/api/index.js +28482 -0
  69. package/dashboard-dist/api/index.js.map +1 -0
  70. package/dashboard-dist/api/package.json +16 -0
  71. package/dashboard-dist/api/sourcemap-register.cjs +1 -0
  72. package/dashboard-dist/web/assets/index-BKUfzbIg.js +148 -0
  73. package/dashboard-dist/web/assets/index-rHRH99IQ.css +1 -0
  74. package/dashboard-dist/web/changeway-logo.png +0 -0
  75. package/dashboard-dist/web/favicon.svg +29 -0
  76. package/dashboard-dist/web/index.html +15 -0
  77. package/dashboard-dist/web/logo.svg +16 -0
  78. package/dist/agent/activation.d.ts +21 -0
  79. package/dist/agent/activation.d.ts.map +1 -0
  80. package/dist/agent/activation.js +94 -0
  81. package/dist/agent/activation.js.map +1 -0
  82. package/dist/agent/auth.d.ts +73 -0
  83. package/dist/agent/auth.d.ts.map +1 -0
  84. package/dist/agent/auth.js +363 -0
  85. package/dist/agent/auth.js.map +1 -0
  86. package/dist/agent/behavior-detector.d.ts +150 -0
  87. package/dist/agent/behavior-detector.d.ts.map +1 -0
  88. package/dist/agent/behavior-detector.js +559 -0
  89. package/dist/agent/behavior-detector.js.map +1 -0
  90. package/dist/agent/business-reporter.d.ts +114 -0
  91. package/dist/agent/business-reporter.d.ts.map +1 -0
  92. package/dist/agent/business-reporter.js +359 -0
  93. package/dist/agent/business-reporter.js.map +1 -0
  94. package/dist/agent/config-sync.d.ts +70 -0
  95. package/dist/agent/config-sync.d.ts.map +1 -0
  96. package/dist/agent/config-sync.js +133 -0
  97. package/dist/agent/config-sync.js.map +1 -0
  98. package/dist/agent/config.d.ts +98 -0
  99. package/dist/agent/config.d.ts.map +1 -0
  100. package/dist/agent/config.js +348 -0
  101. package/dist/agent/config.js.map +1 -0
  102. package/dist/agent/content-injection-scanner.d.ts +35 -0
  103. package/dist/agent/content-injection-scanner.d.ts.map +1 -0
  104. package/dist/agent/content-injection-scanner.js +270 -0
  105. package/dist/agent/content-injection-scanner.js.map +1 -0
  106. package/dist/agent/engine-log-writer.d.ts +6 -0
  107. package/dist/agent/engine-log-writer.d.ts.map +1 -0
  108. package/dist/agent/engine-log-writer.js +18 -0
  109. package/dist/agent/engine-log-writer.js.map +1 -0
  110. package/dist/agent/env.d.ts +19 -0
  111. package/dist/agent/env.d.ts.map +1 -0
  112. package/dist/agent/env.js +44 -0
  113. package/dist/agent/env.js.map +1 -0
  114. package/dist/agent/event-reporter.d.ts +87 -0
  115. package/dist/agent/event-reporter.d.ts.map +1 -0
  116. package/dist/agent/event-reporter.js +306 -0
  117. package/dist/agent/event-reporter.js.map +1 -0
  118. package/dist/agent/file-watcher.d.ts +50 -0
  119. package/dist/agent/file-watcher.d.ts.map +1 -0
  120. package/dist/agent/file-watcher.js +135 -0
  121. package/dist/agent/file-watcher.js.map +1 -0
  122. package/dist/agent/fs-utils.d.ts +22 -0
  123. package/dist/agent/fs-utils.d.ts.map +1 -0
  124. package/dist/agent/fs-utils.js +41 -0
  125. package/dist/agent/fs-utils.js.map +1 -0
  126. package/dist/agent/gateway-manager.d.ts +59 -0
  127. package/dist/agent/gateway-manager.d.ts.map +1 -0
  128. package/dist/agent/gateway-manager.js +583 -0
  129. package/dist/agent/gateway-manager.js.map +1 -0
  130. package/dist/agent/hook-types.d.ts +276 -0
  131. package/dist/agent/hook-types.d.ts.map +1 -0
  132. package/dist/agent/hook-types.js +51 -0
  133. package/dist/agent/hook-types.js.map +1 -0
  134. package/dist/agent/http-client.d.ts +19 -0
  135. package/dist/agent/http-client.d.ts.map +1 -0
  136. package/dist/agent/http-client.js +37 -0
  137. package/dist/agent/http-client.js.map +1 -0
  138. package/dist/agent/index.d.ts +8 -0
  139. package/dist/agent/index.d.ts.map +1 -0
  140. package/dist/agent/index.js +8 -0
  141. package/dist/agent/index.js.map +1 -0
  142. package/dist/agent/openclaw-hybrid-audit-changeway.js +1447 -0
  143. package/dist/agent/prompt-gate.d.ts +16 -0
  144. package/dist/agent/prompt-gate.d.ts.map +1 -0
  145. package/dist/agent/prompt-gate.js +58 -0
  146. package/dist/agent/prompt-gate.js.map +1 -0
  147. package/dist/agent/prompt-input.d.ts +9 -0
  148. package/dist/agent/prompt-input.d.ts.map +1 -0
  149. package/dist/agent/prompt-input.js +173 -0
  150. package/dist/agent/prompt-input.js.map +1 -0
  151. package/dist/agent/prompt-output.d.ts +4 -0
  152. package/dist/agent/prompt-output.d.ts.map +1 -0
  153. package/dist/agent/prompt-output.js +19 -0
  154. package/dist/agent/prompt-output.js.map +1 -0
  155. package/dist/agent/runner.d.ts +23 -0
  156. package/dist/agent/runner.d.ts.map +1 -0
  157. package/dist/agent/runner.js +165 -0
  158. package/dist/agent/runner.js.map +1 -0
  159. package/dist/agent/runtime-mode.d.ts +10 -0
  160. package/dist/agent/runtime-mode.d.ts.map +1 -0
  161. package/dist/agent/runtime-mode.js +19 -0
  162. package/dist/agent/runtime-mode.js.map +1 -0
  163. package/dist/agent/sanitizer.d.ts +10 -0
  164. package/dist/agent/sanitizer.d.ts.map +1 -0
  165. package/dist/agent/sanitizer.js +175 -0
  166. package/dist/agent/sanitizer.js.map +1 -0
  167. package/dist/agent/scan-activity.d.ts +19 -0
  168. package/dist/agent/scan-activity.d.ts.map +1 -0
  169. package/dist/agent/scan-activity.js +34 -0
  170. package/dist/agent/scan-activity.js.map +1 -0
  171. package/dist/agent/types.d.ts +177 -0
  172. package/dist/agent/types.d.ts.map +1 -0
  173. package/dist/agent/types.js +5 -0
  174. package/dist/agent/types.js.map +1 -0
  175. package/dist/agent/workspace-scanner.d.ts +35 -0
  176. package/dist/agent/workspace-scanner.d.ts.map +1 -0
  177. package/dist/agent/workspace-scanner.js +137 -0
  178. package/dist/agent/workspace-scanner.js.map +1 -0
  179. package/dist/dashboard-launcher.d.ts +52 -0
  180. package/dist/dashboard-launcher.d.ts.map +1 -0
  181. package/dist/dashboard-launcher.js +363 -0
  182. package/dist/dashboard-launcher.js.map +1 -0
  183. package/dist/gateway/activity.d.ts +52 -0
  184. package/dist/gateway/activity.d.ts.map +1 -0
  185. package/dist/gateway/activity.js +111 -0
  186. package/dist/gateway/activity.js.map +1 -0
  187. package/dist/gateway/config.d.ts +50 -0
  188. package/dist/gateway/config.d.ts.map +1 -0
  189. package/dist/gateway/config.js +200 -0
  190. package/dist/gateway/config.js.map +1 -0
  191. package/dist/gateway/handlers/anthropic.d.ts +12 -0
  192. package/dist/gateway/handlers/anthropic.d.ts.map +1 -0
  193. package/dist/gateway/handlers/anthropic.js +254 -0
  194. package/dist/gateway/handlers/anthropic.js.map +1 -0
  195. package/dist/gateway/handlers/gemini.d.ts +12 -0
  196. package/dist/gateway/handlers/gemini.d.ts.map +1 -0
  197. package/dist/gateway/handlers/gemini.js +101 -0
  198. package/dist/gateway/handlers/gemini.js.map +1 -0
  199. package/dist/gateway/handlers/models.d.ts +4 -0
  200. package/dist/gateway/handlers/models.d.ts.map +1 -0
  201. package/dist/gateway/handlers/models.js +36 -0
  202. package/dist/gateway/handlers/models.js.map +1 -0
  203. package/dist/gateway/handlers/openai.d.ts +16 -0
  204. package/dist/gateway/handlers/openai.d.ts.map +1 -0
  205. package/dist/gateway/handlers/openai.js +254 -0
  206. package/dist/gateway/handlers/openai.js.map +1 -0
  207. package/dist/gateway/index.d.ts +27 -0
  208. package/dist/gateway/index.d.ts.map +1 -0
  209. package/dist/gateway/index.js +290 -0
  210. package/dist/gateway/index.js.map +1 -0
  211. package/dist/gateway/mapping-store.d.ts +38 -0
  212. package/dist/gateway/mapping-store.d.ts.map +1 -0
  213. package/dist/gateway/mapping-store.js +74 -0
  214. package/dist/gateway/mapping-store.js.map +1 -0
  215. package/dist/gateway/restorer.d.ts +63 -0
  216. package/dist/gateway/restorer.d.ts.map +1 -0
  217. package/dist/gateway/restorer.js +284 -0
  218. package/dist/gateway/restorer.js.map +1 -0
  219. package/dist/gateway/sanitizer.d.ts +17 -0
  220. package/dist/gateway/sanitizer.d.ts.map +1 -0
  221. package/dist/gateway/sanitizer.js +228 -0
  222. package/dist/gateway/sanitizer.js.map +1 -0
  223. package/dist/gateway/types.d.ts +53 -0
  224. package/dist/gateway/types.d.ts.map +1 -0
  225. package/dist/gateway/types.js +5 -0
  226. package/dist/gateway/types.js.map +1 -0
  227. package/dist/index.d.ts +19 -0
  228. package/dist/index.d.ts.map +1 -0
  229. package/dist/index.js +2990 -0
  230. package/dist/index.js.map +1 -0
  231. package/dist/memory/index.d.ts +5 -0
  232. package/dist/memory/index.d.ts.map +1 -0
  233. package/dist/memory/index.js +5 -0
  234. package/dist/memory/index.js.map +1 -0
  235. package/dist/memory/store.d.ts +82 -0
  236. package/dist/memory/store.d.ts.map +1 -0
  237. package/dist/memory/store.js +194 -0
  238. package/dist/memory/store.js.map +1 -0
  239. package/dist/platform-client/index.d.ts +63 -0
  240. package/dist/platform-client/index.d.ts.map +1 -0
  241. package/dist/platform-client/index.js +294 -0
  242. package/dist/platform-client/index.js.map +1 -0
  243. package/dist/platform-client/types.d.ts +109 -0
  244. package/dist/platform-client/types.d.ts.map +1 -0
  245. package/dist/platform-client/types.js +3 -0
  246. package/dist/platform-client/types.js.map +1 -0
  247. package/dist/workspace-agents-guide.d.ts +22 -0
  248. package/dist/workspace-agents-guide.d.ts.map +1 -0
  249. package/dist/workspace-agents-guide.js +92 -0
  250. package/dist/workspace-agents-guide.js.map +1 -0
  251. package/dist/workspace-agents-sync.d.ts +24 -0
  252. package/dist/workspace-agents-sync.d.ts.map +1 -0
  253. package/dist/workspace-agents-sync.js +41 -0
  254. package/dist/workspace-agents-sync.js.map +1 -0
  255. package/dist/workspace-agents-watcher.d.ts +23 -0
  256. package/dist/workspace-agents-watcher.d.ts.map +1 -0
  257. package/dist/workspace-agents-watcher.js +152 -0
  258. package/dist/workspace-agents-watcher.js.map +1 -0
  259. package/dist/workspace-discovery.d.ts +11 -0
  260. package/dist/workspace-discovery.d.ts.map +1 -0
  261. package/dist/workspace-discovery.js +116 -0
  262. package/dist/workspace-discovery.js.map +1 -0
  263. package/gateway/package-lock.json +597 -0
  264. package/gateway/package.json +57 -0
  265. package/gateway/pnpm-lock.yaml +342 -0
  266. package/gateway/src/activity.ts +142 -0
  267. package/gateway/src/config.ts +246 -0
  268. package/gateway/src/handlers/anthropic.ts +328 -0
  269. package/gateway/src/handlers/gemini.ts +122 -0
  270. package/gateway/src/handlers/models.ts +45 -0
  271. package/gateway/src/handlers/openai.ts +333 -0
  272. package/gateway/src/index.ts +344 -0
  273. package/gateway/src/mapping-store.ts +88 -0
  274. package/gateway/src/restorer.ts +322 -0
  275. package/gateway/src/sanitizer.ts +298 -0
  276. package/gateway/src/types.ts +73 -0
  277. package/gateway/tsconfig.json +20 -0
  278. package/openclaw.plugin.json +86 -0
  279. package/package.json +74 -0
  280. package/samples/Untitled +1 -0
  281. package/samples/clean-email.txt +20 -0
  282. package/samples/test-document.md +53 -0
  283. package/samples/test-email-popup.txt +44 -0
  284. package/samples/test-email.txt +32 -0
  285. package/samples/test-webpage.html +51 -0
@@ -0,0 +1,344 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenGuardrails AI Security Gateway
4
+ *
5
+ * Local HTTP proxy that intercepts LLM API calls, sanitizes sensitive data
6
+ * before sending to providers, and restores it in responses.
7
+ * Supports Anthropic, OpenAI, and Gemini protocols.
8
+ */
9
+
10
+ import { createServer } from "node:http";
11
+ import type { IncomingMessage, ServerResponse } from "node:http";
12
+ import { loadConfig, validateConfig, findBackendByApiKey, findDefaultBackend, findBackendByPathPrefix, getBackendApiType } from "./config.js";
13
+ import type { GatewayConfig, BackendConfig, ApiType } from "./types.js";
14
+ import { handleAnthropicRequest } from "./handlers/anthropic.js";
15
+ import { handleOpenAIRequest } from "./handlers/openai.js";
16
+ import { handleGeminiRequest } from "./handlers/gemini.js";
17
+ import { handleModelsRequest } from "./handlers/models.js";
18
+
19
+ let config: GatewayConfig;
20
+ let currentServer: ReturnType<typeof createServer> | null = null;
21
+
22
+ /**
23
+ * Extract API key from request headers
24
+ */
25
+ function extractApiKey(req: IncomingMessage): string | null {
26
+ // Try x-api-key header (Anthropic style)
27
+ const xApiKey = req.headers["x-api-key"];
28
+ if (xApiKey && typeof xApiKey === "string") {
29
+ return xApiKey;
30
+ }
31
+
32
+ // Try Authorization: Bearer (OpenAI style)
33
+ const auth = req.headers["authorization"];
34
+ if (auth && typeof auth === "string" && auth.startsWith("Bearer ")) {
35
+ return auth.slice(7);
36
+ }
37
+
38
+ // Try x-goog-api-key (Gemini style)
39
+ const googKey = req.headers["x-goog-api-key"];
40
+ if (googKey && typeof googKey === "string") {
41
+ return googKey;
42
+ }
43
+
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Resolve backend for a request based on path prefix, API key, or defaults
49
+ * Priority: pathPrefix > apiKey > defaultBackend
50
+ */
51
+ function resolveBackend(
52
+ req: IncomingMessage,
53
+ apiType: ApiType,
54
+ ): { name: string; backend: BackendConfig } | null {
55
+ const url = req.url || "";
56
+
57
+ // 1. Try to find backend by path prefix (most specific)
58
+ const byPath = findBackendByPathPrefix(url, config);
59
+ if (byPath) {
60
+ return byPath;
61
+ }
62
+
63
+ // 2. Try to find backend by API key
64
+ const apiKey = extractApiKey(req);
65
+ if (apiKey) {
66
+ const byKey = findBackendByApiKey(apiKey, config);
67
+ if (byKey) {
68
+ return byKey;
69
+ }
70
+ }
71
+
72
+ // 3. Fall back to default backend for the API type
73
+ return findDefaultBackend(apiType, config);
74
+ }
75
+
76
+ /**
77
+ * Main request handler
78
+ */
79
+ async function handleRequest(
80
+ req: IncomingMessage,
81
+ res: ServerResponse,
82
+ ): Promise<void> {
83
+ const { method, url } = req;
84
+
85
+ // Log request (skip health checks to reduce noise)
86
+ if (url !== "/health") {
87
+ console.log(`[ai-security-gateway] ${method} ${url}`);
88
+ }
89
+
90
+ // CORS headers (for browser-based clients)
91
+ res.setHeader("Access-Control-Allow-Origin", "*");
92
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
93
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key, anthropic-version");
94
+
95
+ // Handle OPTIONS for CORS preflight
96
+ if (method === "OPTIONS") {
97
+ res.writeHead(204);
98
+ res.end();
99
+ return;
100
+ }
101
+
102
+ // Health check (allow GET)
103
+ if (url === "/health") {
104
+ res.writeHead(200, { "Content-Type": "application/json" });
105
+ res.end(JSON.stringify({ status: "ok", version: "1.0.0" }));
106
+ return;
107
+ }
108
+
109
+ // Handle GET /v1/models — proxy to configured backend's models endpoint
110
+ if (method === "GET" && url === "/v1/models") {
111
+ await handleModelsRequest(res, config);
112
+ return;
113
+ }
114
+
115
+ // Only allow POST for API endpoints
116
+ if (method !== "POST") {
117
+ res.writeHead(405, { "Content-Type": "application/json" });
118
+ res.end(JSON.stringify({ error: "Method not allowed" }));
119
+ return;
120
+ }
121
+
122
+ // Route to appropriate handler based on path suffix
123
+ // This allows flexible path prefixes (e.g., /v1/coding/chat/completions)
124
+ try {
125
+ if (url?.endsWith("/messages")) {
126
+ // Anthropic Messages API (matches /v1/messages, /v1/xxx/messages, etc.)
127
+ const resolved = resolveBackend(req, "anthropic");
128
+ if (!resolved) {
129
+ res.writeHead(500, { "Content-Type": "application/json" });
130
+ res.end(JSON.stringify({ error: "No Anthropic-compatible backend configured" }));
131
+ return;
132
+ }
133
+ await handleAnthropicRequest(req, res, resolved.backend);
134
+ } else if (url?.endsWith("/chat/completions")) {
135
+ // OpenAI/OpenRouter Chat Completions API
136
+ // Try to extract backend name from URL: /backend/{name}/chat/completions
137
+ const backendMatch = url.match(/^\/backend\/([^/]+)\//);
138
+ let resolved: { name: string; backend: BackendConfig } | null = null;
139
+
140
+ if (backendMatch) {
141
+ const backendName = backendMatch[1];
142
+ const backend = config.backends[backendName];
143
+ if (backend) {
144
+ resolved = { name: backendName, backend };
145
+ console.log(`[ai-security-gateway] Backend from URL: ${backendName}`);
146
+ }
147
+ }
148
+
149
+ // Fallback to path prefix or default
150
+ if (!resolved) {
151
+ resolved = resolveBackend(req, "openai");
152
+ console.log(`[ai-security-gateway] Resolved backend: ${resolved?.name}`);
153
+ }
154
+
155
+ // Check explicit routing config
156
+ const explicitBackendName = config.routing?.["/v1/chat/completions"];
157
+ const backend = explicitBackendName
158
+ ? config.backends[explicitBackendName]
159
+ : resolved?.backend;
160
+
161
+ if (!backend) {
162
+ res.writeHead(500, { "Content-Type": "application/json" });
163
+ res.end(JSON.stringify({ error: "No OpenAI-compatible backend configured" }));
164
+ return;
165
+ }
166
+
167
+ const extraHeaders: Record<string, string> = {};
168
+ if (backend.referer) {
169
+ extraHeaders["HTTP-Referer"] = backend.referer;
170
+ }
171
+ if (backend.title) {
172
+ extraHeaders["X-Title"] = backend.title;
173
+ }
174
+ await handleOpenAIRequest(req, res, backend, extraHeaders);
175
+ } else if (url?.match(/\/models\/(.+):generateContent$/)) {
176
+ // Gemini API (matches any path ending with /models/{model}:generateContent)
177
+ const match = url.match(/\/models\/(.+):generateContent$/);
178
+ const modelName = match?.[1];
179
+ if (modelName) {
180
+ const resolved = resolveBackend(req, "gemini");
181
+ if (!resolved) {
182
+ res.writeHead(500, { "Content-Type": "application/json" });
183
+ res.end(JSON.stringify({ error: "No Gemini backend configured" }));
184
+ return;
185
+ }
186
+ await handleGeminiRequest(req, res, resolved.backend, modelName);
187
+ } else {
188
+ res.writeHead(404, { "Content-Type": "application/json" });
189
+ res.end(JSON.stringify({ error: "Model name required" }));
190
+ }
191
+ } else {
192
+ // Unknown endpoint
193
+ res.writeHead(404, { "Content-Type": "application/json" });
194
+ res.end(JSON.stringify({ error: "Not found", url }));
195
+ }
196
+ } catch (error) {
197
+ console.error("[ai-security-gateway] Request handler error:", error);
198
+ res.writeHead(500, { "Content-Type": "application/json" });
199
+ res.end(
200
+ JSON.stringify({
201
+ error: "Internal server error",
202
+ message: error instanceof Error ? error.message : String(error),
203
+ }),
204
+ );
205
+ }
206
+ }
207
+
208
+ /**
209
+ * Stop the gateway server
210
+ */
211
+ export function stopGateway(): Promise<void> {
212
+ return new Promise((resolve) => {
213
+ if (currentServer) {
214
+ currentServer.close(() => {
215
+ currentServer = null;
216
+ console.log("[ai-security-gateway] Server stopped");
217
+ resolve();
218
+ });
219
+ } else {
220
+ resolve();
221
+ }
222
+ });
223
+ }
224
+
225
+ /**
226
+ * Check if gateway is running
227
+ */
228
+ export function isGatewayServerRunning(): boolean {
229
+ return currentServer !== null;
230
+ }
231
+
232
+ /**
233
+ * Start gateway server
234
+ * @param configPath - Path to config file
235
+ * @param embedded - If true, don't call process.exit on errors (for in-process use)
236
+ */
237
+ export function startGateway(configPath?: string, embedded = false): void {
238
+ // Stop existing server if running (same process)
239
+ if (currentServer) {
240
+ if (!embedded) console.log("[ai-security-gateway] Stopping existing server for restart...");
241
+ currentServer.close();
242
+ currentServer = null;
243
+ }
244
+
245
+ try {
246
+ // Load and validate configuration
247
+ config = loadConfig(configPath);
248
+ validateConfig(config);
249
+
250
+ if (!embedded) {
251
+ console.log("[ai-security-gateway] Configuration loaded:");
252
+ console.log(` Port: ${config.port}`);
253
+ console.log(
254
+ ` Backends: ${Object.keys(config.backends).join(", ") || "(none)"}`,
255
+ );
256
+ }
257
+
258
+ // Create HTTP server
259
+ const server = createServer(handleRequest);
260
+ currentServer = server;
261
+
262
+ // Handle server errors
263
+ server.on("error", (err: NodeJS.ErrnoException) => {
264
+ console.error("[ai-security-gateway] Server error:", err);
265
+ currentServer = null;
266
+ if (!embedded) {
267
+ process.exit(1);
268
+ }
269
+ throw err;
270
+ });
271
+
272
+ // Start listening
273
+ server.listen(config.port, "127.0.0.1", () => {
274
+ if (!embedded) {
275
+ console.log(
276
+ `[ai-security-gateway] Server listening on http://127.0.0.1:${config.port}`,
277
+ );
278
+ console.log("[ai-security-gateway] Ready to proxy requests");
279
+ console.log("");
280
+ console.log("Endpoints:");
281
+ console.log(` POST http://127.0.0.1:${config.port}/v1/messages - Anthropic`);
282
+ console.log(` POST http://127.0.0.1:${config.port}/v1/chat/completions - OpenAI / OpenRouter`);
283
+ console.log(` POST http://127.0.0.1:${config.port}/v1/models/:model:generateContent - Gemini`);
284
+ console.log(` GET http://127.0.0.1:${config.port}/v1/models - List models (OpenAI / OpenRouter)`);
285
+ console.log(` GET http://127.0.0.1:${config.port}/health - Health check`);
286
+ }
287
+ });
288
+
289
+ // In embedded mode, don't let the server prevent process exit
290
+ if (embedded) {
291
+ server.unref();
292
+ }
293
+
294
+ // Only register shutdown handlers if not embedded
295
+ if (!embedded) {
296
+ process.on("SIGINT", () => {
297
+ console.log("\n[ai-security-gateway] Shutting down...");
298
+ server.close(() => {
299
+ console.log("[ai-security-gateway] Server stopped");
300
+ process.exit(0);
301
+ });
302
+ });
303
+
304
+ process.on("SIGTERM", () => {
305
+ console.log("\n[ai-security-gateway] Shutting down...");
306
+ server.close(() => {
307
+ console.log("[ai-security-gateway] Server stopped");
308
+ process.exit(0);
309
+ });
310
+ });
311
+ }
312
+ } catch (error) {
313
+ console.error("[ai-security-gateway] Failed to start:", error);
314
+ currentServer = null;
315
+ if (!embedded) {
316
+ process.exit(1);
317
+ }
318
+ throw error;
319
+ }
320
+ }
321
+
322
+ // Re-export for programmatic use
323
+ export { sanitize, sanitizeMessages } from "./sanitizer.js";
324
+ export { restore, restoreJSON, restoreSSELine } from "./restorer.js";
325
+ export {
326
+ addActivityListener,
327
+ removeActivityListener,
328
+ clearActivityListeners,
329
+ } from "./activity.js";
330
+ // stopGateway and isGatewayServerRunning are already exported above
331
+ export type {
332
+ GatewayConfig,
333
+ MappingTable,
334
+ SanitizeResult,
335
+ EntityMatch,
336
+ GatewayActivityEvent,
337
+ ActivityListener,
338
+ } from "./types.js";
339
+
340
+ // Start if run directly
341
+ if (import.meta.url === `file://${process.argv[1]}`) {
342
+ const configPath = process.argv[2];
343
+ startGateway(configPath);
344
+ }
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Persistent Mapping Store
3
+ *
4
+ * Maintains a global mapping between original values and placeholders.
5
+ * This ensures the same sensitive data always gets the same placeholder,
6
+ * allowing restoration to work across multiple requests in a conversation.
7
+ */
8
+
9
+ import type { MappingTable } from "./types.js";
10
+
11
+ // Global mapping: original value -> placeholder
12
+ const originalToPlaceholder = new Map<string, string>();
13
+
14
+ // Global mapping: placeholder -> original value (reverse lookup for restoration)
15
+ const placeholderToOriginal = new Map<string, string>();
16
+
17
+ // Global counter per entity type
18
+ const typeCounters = new Map<string, number>();
19
+
20
+ /**
21
+ * Get or create a placeholder for an original value
22
+ * If the value was seen before, returns the same placeholder
23
+ */
24
+ export function getOrCreatePlaceholder(originalValue: string, entityType: string): string {
25
+ // Check if we already have a placeholder for this value
26
+ const existing = originalToPlaceholder.get(originalValue);
27
+ if (existing) {
28
+ return existing;
29
+ }
30
+
31
+ // Create a new placeholder
32
+ const counter = (typeCounters.get(entityType) ?? 0) + 1;
33
+ typeCounters.set(entityType, counter);
34
+
35
+ const paddedId = counter.toString().padStart(8, "0");
36
+ const placeholder = `__PII_${entityType}_${paddedId}__`;
37
+
38
+ // Store both directions
39
+ originalToPlaceholder.set(originalValue, placeholder);
40
+ placeholderToOriginal.set(placeholder, originalValue);
41
+
42
+ return placeholder;
43
+ }
44
+
45
+ /**
46
+ * Get the original value for a placeholder
47
+ */
48
+ export function getOriginalValue(placeholder: string): string | undefined {
49
+ return placeholderToOriginal.get(placeholder);
50
+ }
51
+
52
+ /**
53
+ * Check if a string is a known placeholder
54
+ */
55
+ export function isKnownPlaceholder(text: string): boolean {
56
+ return placeholderToOriginal.has(text);
57
+ }
58
+
59
+ /**
60
+ * Get all placeholders and their original values as a MappingTable
61
+ * This is used for restoration
62
+ */
63
+ export function getGlobalMappingTable(): MappingTable {
64
+ return new Map(placeholderToOriginal);
65
+ }
66
+
67
+ /**
68
+ * Get current statistics
69
+ */
70
+ export function getMappingStats(): { totalMappings: number; byType: Record<string, number> } {
71
+ const byType: Record<string, number> = {};
72
+ for (const [type, count] of typeCounters.entries()) {
73
+ byType[type] = count;
74
+ }
75
+ return {
76
+ totalMappings: placeholderToOriginal.size,
77
+ byType,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Clear all mappings (for testing or reset)
83
+ */
84
+ export function clearMappings(): void {
85
+ originalToPlaceholder.clear();
86
+ placeholderToOriginal.clear();
87
+ typeCounters.clear();
88
+ }