@opensecurity/zonzon-core 0.1.2 → 0.1.3

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 (71) hide show
  1. package/dist/audit.d.ts +10 -0
  2. package/dist/audit.js +39 -0
  3. package/dist/cache-layer.test.d.ts +1 -0
  4. package/dist/cache-layer.test.js +205 -0
  5. package/dist/cache-multi-question.test.d.ts +1 -0
  6. package/dist/cache-multi-question.test.js +187 -0
  7. package/dist/dns-handler.d.ts +27 -0
  8. package/dist/dns-handler.js +323 -0
  9. package/dist/dns-service.d.ts +45 -0
  10. package/dist/dns-service.js +546 -0
  11. package/dist/dns-service.test.d.ts +1 -0
  12. package/dist/dns-service.test.js +306 -0
  13. package/dist/dns-wireformat.test.d.ts +1 -0
  14. package/dist/dns-wireformat.test.js +669 -0
  15. package/dist/firewall.d.ts +9 -0
  16. package/dist/firewall.js +62 -0
  17. package/dist/http-body-forwarding-integration.test.d.ts +1 -0
  18. package/dist/http-body-forwarding-integration.test.js +318 -0
  19. package/dist/http-body-forwarding.test.d.ts +1 -0
  20. package/dist/http-body-forwarding.test.js +84 -0
  21. package/dist/http-handler.d.ts +21 -0
  22. package/dist/http-handler.js +429 -0
  23. package/dist/http-proxy.d.ts +14 -0
  24. package/dist/http-proxy.js +135 -0
  25. package/dist/http-proxy.test.d.ts +1 -0
  26. package/dist/http-proxy.test.js +375 -0
  27. package/{src/index.ts → dist/index.d.ts} +1 -1
  28. package/dist/index.js +10 -0
  29. package/dist/rate-limiter.d.ts +11 -0
  30. package/dist/rate-limiter.js +33 -0
  31. package/dist/rate-limiter.test.d.ts +1 -0
  32. package/dist/rate-limiter.test.js +149 -0
  33. package/dist/schema.d.ts +12 -0
  34. package/dist/schema.js +124 -0
  35. package/dist/schema.test.d.ts +1 -0
  36. package/dist/schema.test.js +586 -0
  37. package/dist/sni-proxy.d.ts +12 -0
  38. package/dist/sni-proxy.js +141 -0
  39. package/dist/srv-record.test.d.ts +1 -0
  40. package/dist/srv-record.test.js +186 -0
  41. package/dist/tcp-connection-limit.test.d.ts +1 -0
  42. package/dist/tcp-connection-limit.test.js +89 -0
  43. package/dist/types.d.ts +145 -0
  44. package/dist/types.js +34 -0
  45. package/dist/wildcard-matching.test.d.ts +1 -0
  46. package/dist/wildcard-matching.test.js +162 -0
  47. package/package.json +4 -1
  48. package/src/audit.ts +0 -43
  49. package/src/cache-layer.test.ts +0 -236
  50. package/src/cache-multi-question.test.ts +0 -263
  51. package/src/dns-handler.ts +0 -355
  52. package/src/dns-service.test.ts +0 -371
  53. package/src/dns-service.ts +0 -655
  54. package/src/dns-wireformat.test.ts +0 -771
  55. package/src/env.d.ts +0 -1
  56. package/src/firewall.ts +0 -66
  57. package/src/http-body-forwarding-integration.test.ts +0 -357
  58. package/src/http-body-forwarding.test.ts +0 -101
  59. package/src/http-handler.ts +0 -489
  60. package/src/http-proxy.test.ts +0 -440
  61. package/src/http-proxy.ts +0 -148
  62. package/src/rate-limiter.test.ts +0 -144
  63. package/src/rate-limiter.ts +0 -50
  64. package/src/schema.test.ts +0 -685
  65. package/src/schema.ts +0 -137
  66. package/src/sni-proxy.ts +0 -164
  67. package/src/srv-record.test.ts +0 -211
  68. package/src/tcp-connection-limit.test.ts +0 -110
  69. package/src/types.ts +0 -168
  70. package/src/wildcard-matching.test.ts +0 -196
  71. package/tsconfig.json +0 -9
package/src/env.d.ts DELETED
@@ -1 +0,0 @@
1
- /// <reference types="node" />
package/src/firewall.ts DELETED
@@ -1,66 +0,0 @@
1
- import * as net from "net";
2
- import { FirewallConfig } from "./types.js";
3
-
4
- export class FirewallEngine {
5
- private ipToInt(ip: string): number {
6
- return ip.split('.').reduce((int, octet) => (int << 8) + parseInt(octet, 10), 0) >>> 0;
7
- }
8
-
9
- private matchCidr(ip: string, cidr: string): boolean {
10
- try {
11
- const [range, bits] = cidr.split('/');
12
- const mask = bits ? ~(Math.pow(2, 32 - parseInt(bits, 10)) - 1) : 0xFFFFFFFF;
13
- return (this.ipToInt(ip) & mask) === (this.ipToInt(range) & mask);
14
- } catch {
15
- return false;
16
- }
17
- }
18
-
19
- private matchDomain(domain: string, pattern: string): boolean {
20
- if (pattern === "*") return true;
21
- const normDomain = domain.toLowerCase().replace(/\.$/, "");
22
- const normPattern = pattern.toLowerCase().replace(/\.$/, "");
23
-
24
- if (normPattern === normDomain) return true;
25
- if (normPattern.startsWith("*.")) {
26
- const suffix = normPattern.slice(2);
27
- return normDomain === suffix || normDomain.endsWith("." + suffix);
28
- }
29
- return false;
30
- }
31
-
32
- public evaluateIp(ip: string, fw?: FirewallConfig): "ALLOW" | "DENY" {
33
- if (!fw) return "ALLOW";
34
- if (!net.isIPv4(ip)) return "DENY";
35
-
36
- if (fw.blocklist_ips && fw.blocklist_ips.includes(ip)) return "DENY";
37
-
38
- for (const range of fw.blocklist_ranges || []) {
39
- if (this.matchCidr(ip, range)) return "DENY";
40
- }
41
-
42
- if (fw.allowlist_ips && fw.allowlist_ips.includes(ip)) return "ALLOW";
43
-
44
- for (const range of fw.allowlist_ranges || []) {
45
- if (this.matchCidr(ip, range)) return "ALLOW";
46
- }
47
-
48
- return fw.defaultPolicy === "allow" ? "ALLOW" : "DENY";
49
- }
50
-
51
- public evaluateDomain(domain: string, fw?: FirewallConfig): "ALLOW" | "DENY" {
52
- if (!fw) return "ALLOW";
53
-
54
- for (const pattern of fw.blocklist_domains || []) {
55
- if (this.matchDomain(domain, pattern)) return "DENY";
56
- }
57
-
58
- for (const pattern of fw.allowlist_domains || []) {
59
- if (this.matchDomain(domain, pattern)) return "ALLOW";
60
- }
61
-
62
- return fw.defaultPolicy === "allow" ? "ALLOW" : "DENY";
63
- }
64
- }
65
-
66
- export const firewallEngine = new FirewallEngine();
@@ -1,357 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "assert";
3
- import * as http from "http";
4
-
5
- describe("HTTP Body Forwarding Integration", () => {
6
- let upstreamServer: http.Server;
7
- let receivedBodies: string[] = [];
8
-
9
- const TEST_PORT = 19876;
10
- const PROXY_PORT = 19877;
11
-
12
- function startUpstreamServer(): Promise<number> {
13
- return new Promise((resolve) => {
14
- upstreamServer = http.createServer((req: http.IncomingMessage, res: http.ServerResponse) => {
15
- if (req.method !== "GET" && req.method !== "HEAD") {
16
- const chunks: Buffer[] = [];
17
- req.on("data", (chunk: Buffer) => {
18
- chunks.push(chunk);
19
- });
20
- req.on("end", () => {
21
- const body = Buffer.concat(chunks).toString("utf-8");
22
- if (body.length > 0) {
23
- receivedBodies.push(body);
24
- }
25
- res.writeHead(200, { "Content-Type": "text/plain" });
26
- res.end(`upstream ${req.method} body size: ${Buffer.concat(chunks).length}`);
27
- });
28
- } else {
29
- res.writeHead(200, { "Content-Type": "text/plain" });
30
- res.end("GET/HEAD received");
31
- }
32
- });
33
-
34
- upstreamServer.listen(TEST_PORT, "127.0.0.1", () => {
35
- resolve(TEST_PORT);
36
- });
37
- });
38
- }
39
-
40
- function stopUpstreamServer(): Promise<void> {
41
- return new Promise((resolve) => {
42
- if (upstreamServer) {
43
- upstreamServer.close(() => resolve());
44
- } else {
45
- resolve();
46
- }
47
- });
48
- }
49
-
50
- function sendPostRequest(url: string, body: string): Promise<{ status: number; response: string }> {
51
- return new Promise((resolve, reject) => {
52
- const parsed = new URL(url);
53
- const postData = Buffer.from(body);
54
-
55
- const options = {
56
- hostname: "127.0.0.1",
57
- port: parsed.port ? parseInt(parsed.port, 10) : PROXY_PORT,
58
- path: parsed.pathname + parsed.search,
59
- method: "POST",
60
- headers: {
61
- Host: parsed.hostname,
62
- "Content-Type": "text/plain",
63
- "Content-Length": postData.length,
64
- },
65
- };
66
-
67
- const req = http.request(options, (res: http.IncomingMessage) => {
68
- let responseBody = "";
69
- res.on("data", (chunk: Buffer) => {
70
- responseBody += chunk.toString();
71
- });
72
- res.on("end", () => {
73
- resolve({ status: res.statusCode || 500, response: responseBody });
74
- });
75
- });
76
-
77
- req.on("error", reject);
78
- req.write(postData);
79
- req.end();
80
- });
81
- }
82
-
83
- function sendPutRequest(url: string, body: string): Promise<{ status: number; response: string }> {
84
- return new Promise((resolve, reject) => {
85
- const parsed = new URL(url);
86
- const putData = Buffer.from(body);
87
-
88
- const options = {
89
- hostname: "127.0.0.1",
90
- port: parsed.port ? parseInt(parsed.port, 10) : PROXY_PORT,
91
- path: parsed.pathname + parsed.search,
92
- method: "PUT",
93
- headers: {
94
- Host: parsed.hostname,
95
- "Content-Type": "text/plain",
96
- "Content-Length": putData.length,
97
- },
98
- };
99
-
100
- const req = http.request(options, (res: http.IncomingMessage) => {
101
- let responseBody = "";
102
- res.on("data", (chunk: Buffer) => {
103
- responseBody += chunk.toString();
104
- });
105
- res.on("end", () => {
106
- resolve({ status: res.statusCode || 500, response: responseBody });
107
- });
108
- });
109
-
110
- req.on("error", reject);
111
- req.write(putData);
112
- req.end();
113
- });
114
- }
115
-
116
- function get(url: string): Promise<{ status: number; response: string }> {
117
- return new Promise((resolve, reject) => {
118
- const parsed = new URL(url);
119
-
120
- const req = http.request({
121
- hostname: "127.0.0.1",
122
- port: parsed.port ? parseInt(parsed.port, 10) : PROXY_PORT,
123
- path: parsed.pathname + parsed.search,
124
- method: "GET",
125
- headers: { Host: parsed.hostname },
126
- }, (res: http.IncomingMessage) => {
127
- let responseBody = "";
128
- res.on("data", (chunk: Buffer) => {
129
- responseBody += chunk.toString();
130
- });
131
- res.on("end", () => {
132
- resolve({ status: res.statusCode || 500, response: responseBody });
133
- });
134
- });
135
-
136
- req.on("error", reject);
137
- req.end();
138
- });
139
- }
140
-
141
- it("forwards POST body to upstream when forwardRequestBody is enabled", async () => {
142
- const { DevDnsServer } = await import("./dns-service.js");
143
- const { HttpHandler } = await import("./http-handler.js");
144
-
145
- receivedBodies = [];
146
- const port = await startUpstreamServer();
147
-
148
- try {
149
- const config: any = {
150
- port: 53,
151
- hosts: {
152
- "app.loop": {
153
- records: [{ type: "A", address: "127.0.0.1" }],
154
- http_proxy: {
155
- enabled: true,
156
- upstream: `http://127.0.0.1:${port}`,
157
- headers: {},
158
- forwardRequestBody: true,
159
- },
160
- },
161
- },
162
- };
163
-
164
- const dnsServer = new DevDnsServer(config);
165
- const handler = new HttpHandler(dnsServer, config, PROXY_PORT);
166
- await handler.start();
167
-
168
- try {
169
- await new Promise<void>((resolve) => setTimeout(resolve, 100));
170
-
171
- const bodyContent = "hello world test payload";
172
- const result = await sendPostRequest(`http://app.loop/test`, bodyContent);
173
-
174
- assert.strictEqual(result.status, 200);
175
- assert.ok(receivedBodies.length > 0);
176
- assert.strictEqual(receivedBodies[0], bodyContent);
177
- } finally {
178
- await handler.stop();
179
- }
180
- } finally {
181
- await stopUpstreamServer();
182
- }
183
- });
184
-
185
- it("forwards PUT body to upstream when forwardRequestBody is enabled", async () => {
186
- const { DevDnsServer } = await import("./dns-service.js");
187
- const { HttpHandler } = await import("./http-handler.js");
188
-
189
- receivedBodies = [];
190
- const port = await startUpstreamServer();
191
-
192
- try {
193
- const config: any = {
194
- port: 53,
195
- hosts: {
196
- "app.loop": {
197
- records: [{ type: "A", address: "127.0.0.1" }],
198
- http_proxy: {
199
- enabled: true,
200
- upstream: `http://127.0.0.1:${port}`,
201
- headers: {},
202
- forwardRequestBody: true,
203
- },
204
- },
205
- },
206
- };
207
-
208
- const dnsServer = new DevDnsServer(config);
209
- const handler = new HttpHandler(dnsServer, config, PROXY_PORT);
210
- await handler.start();
211
-
212
- try {
213
- await new Promise<void>((resolve) => setTimeout(resolve, 100));
214
-
215
- const bodyContent = '{"key":"value","count":42}';
216
- const result = await sendPutRequest(`http://app.loop/api/update`, bodyContent);
217
-
218
- assert.strictEqual(result.status, 200);
219
- assert.ok(receivedBodies.length > 0);
220
- assert.strictEqual(receivedBodies[0], bodyContent);
221
- } finally {
222
- await handler.stop();
223
- }
224
- } finally {
225
- await stopUpstreamServer();
226
- }
227
- });
228
-
229
- it("does not forward body for GET requests even when forwarding is enabled", async () => {
230
- const { DevDnsServer } = await import("./dns-service.js");
231
- const { HttpHandler } = await import("./http-handler.js");
232
-
233
- receivedBodies = [];
234
- const port = await startUpstreamServer();
235
-
236
- try {
237
- const config: any = {
238
- port: 53,
239
- hosts: {
240
- "app.loop": {
241
- records: [{ type: "A", address: "127.0.0.1" }],
242
- http_proxy: {
243
- enabled: true,
244
- upstream: `http://127.0.0.1:${port}`,
245
- headers: {},
246
- forwardRequestBody: true,
247
- },
248
- },
249
- },
250
- };
251
-
252
- const dnsServer = new DevDnsServer(config);
253
- const handler = new HttpHandler(dnsServer, config, PROXY_PORT);
254
- await handler.start();
255
-
256
- try {
257
- await new Promise<void>((resolve) => setTimeout(resolve, 100));
258
-
259
- const result = await get("http://app.loop/data");
260
-
261
- assert.strictEqual(result.status, 200);
262
- assert.strictEqual(receivedBodies.length, 0);
263
- } finally {
264
- await handler.stop();
265
- }
266
- } finally {
267
- await stopUpstreamServer();
268
- }
269
- });
270
-
271
- it("rejects oversized body with 413 when forwarding is enabled", async () => {
272
- const { DevDnsServer } = await import("./dns-service.js");
273
- const { HttpHandler } = await import("./http-handler.js");
274
-
275
- receivedBodies = [];
276
- const port = await startUpstreamServer();
277
-
278
- try {
279
- const config: any = {
280
- port: 53,
281
- hosts: {
282
- "app.loop": {
283
- records: [{ type: "A", address: "127.0.0.1" }],
284
- http_proxy: {
285
- enabled: true,
286
- upstream: `http://127.0.0.1:${port}`,
287
- headers: {},
288
- forwardRequestBody: true,
289
- maxRequestBodyBytes: 100,
290
- },
291
- },
292
- },
293
- };
294
-
295
- const dnsServer = new DevDnsServer(config);
296
- const handler = new HttpHandler(dnsServer, config, PROXY_PORT);
297
- await handler.start();
298
-
299
- try {
300
- await new Promise<void>((resolve) => setTimeout(resolve, 100));
301
-
302
- const largeBody = "x".repeat(200);
303
- const result = await sendPostRequest(`http://app.loop/upload`, largeBody);
304
-
305
- assert.strictEqual(result.status, 413);
306
- assert.strictEqual(receivedBodies.length, 0);
307
- } finally {
308
- await handler.stop();
309
- }
310
- } finally {
311
- await stopUpstreamServer();
312
- }
313
- });
314
-
315
- it("does not forward body when forwardRequestBody is disabled", async () => {
316
- const { DevDnsServer } = await import("./dns-service.js");
317
- const { HttpHandler } = await import("./http-handler.js");
318
-
319
- receivedBodies = [];
320
- const port = await startUpstreamServer();
321
-
322
- try {
323
- const config: any = {
324
- port: 53,
325
- hosts: {
326
- "app.loop": {
327
- records: [{ type: "A", address: "127.0.0.1" }],
328
- http_proxy: {
329
- enabled: true,
330
- upstream: `http://127.0.0.1:${port}`,
331
- headers: {},
332
- forwardRequestBody: false,
333
- },
334
- },
335
- },
336
- };
337
-
338
- const dnsServer = new DevDnsServer(config);
339
- const handler = new HttpHandler(dnsServer, config, PROXY_PORT);
340
- await handler.start();
341
-
342
- try {
343
- await new Promise<void>((resolve) => setTimeout(resolve, 100));
344
-
345
- const bodyContent = "this should not be forwarded";
346
- const result = await sendPostRequest(`http://app.loop/submit`, bodyContent);
347
-
348
- assert.strictEqual(result.status, 200);
349
- assert.strictEqual(receivedBodies.length, 0);
350
- } finally {
351
- await handler.stop();
352
- }
353
- } finally {
354
- await stopUpstreamServer();
355
- }
356
- });
357
- });
@@ -1,101 +0,0 @@
1
- import { describe, it } from "node:test";
2
- import assert from "assert";
3
- import { HttpProxyService } from "./http-proxy.js";
4
- import { HostConfig } from "./types.js";
5
-
6
- function makeBodyForwardingConfig(): HostConfig {
7
- return {
8
- records: [{ type: "A", address: "127.0.0.1" }],
9
- http_proxy: {
10
- enabled: true,
11
- upstream: "http://upstream.example.com",
12
- headers: { "X-Forward-Body": "true" },
13
- },
14
- redirect: undefined,
15
- };
16
- }
17
-
18
- describe("HttpProxyService - Body Forwarding Configuration", () => {
19
- const proxy = new HttpProxyService();
20
-
21
- it("accepts host config with body forwarding enabled", () => {
22
- const config: HostConfig & { http_proxy?: { enabled?: boolean; upstream?: string; headers?: Record<string, string>; forwardRequestBody?: boolean } } = {
23
- records: [{ type: "A", address: "127.0.0.1" }],
24
- http_proxy: {
25
- enabled: true,
26
- upstream: "http://upstream.example.com",
27
- headers: {},
28
- forwardRequestBody: true,
29
- },
30
- };
31
-
32
- assert.strictEqual(config.http_proxy?.forwardRequestBody, true);
33
- });
34
-
35
- it("defaults body forwarding to false when not specified", () => {
36
- const config = makeBodyForwardingConfig();
37
- assert.ok(!config.http_proxy?.forwardRequestBody);
38
- });
39
-
40
- it("rejects body forwarding when proxy is disabled", () => {
41
- const config: HostConfig & { http_proxy?: { enabled?: boolean; upstream?: string; headers?: Record<string, string>; forwardRequestBody?: boolean } } = {
42
- records: [{ type: "A", address: "127.0.0.1" }],
43
- http_proxy: {
44
- enabled: false,
45
- upstream: "",
46
- headers: {},
47
- forwardRequestBody: true,
48
- },
49
- };
50
-
51
- assert.strictEqual(config.http_proxy?.enabled, false);
52
- });
53
- });
54
-
55
- describe("HttpProxyService - Body Forwarding Header", () => {
56
- const proxy = new HttpProxyService();
57
-
58
- it("injects X-Forwarded-Body header when body forwarding is enabled", () => {
59
- const config: HostConfig & { http_proxy?: { enabled?: boolean; upstream?: string; headers?: Record<string, string>; forwardRequestBody?: boolean } } = makeBodyForwardingConfig();
60
- config.http_proxy!.forwardRequestBody = true;
61
-
62
- const request = {
63
- hostname: "app.loop",
64
- originalUrl: "/api/submit",
65
- method: "POST",
66
- headers: { "Content-Type": "application/json" },
67
- body: Buffer.from('{"key":"value"}'),
68
- };
69
-
70
- const result = proxy.getUpstreamHeaders(config, request as any);
71
- assert.ok("X-Body-Forwarded" in result.upstreamHeaders || "X-Body-Size" in result.upstreamHeaders);
72
- });
73
-
74
- it("does not inject body forwarding header when disabled", () => {
75
- const config: HostConfig & { http_proxy?: { enabled?: boolean; upstream?: string; headers?: Record<string, string>; forwardRequestBody?: boolean } } = makeBodyForwardingConfig();
76
- config.http_proxy!.forwardRequestBody = false;
77
-
78
- const request = {
79
- hostname: "app.loop",
80
- originalUrl: "/api/submit",
81
- method: "POST",
82
- headers: { "Content-Type": "application/json" },
83
- };
84
-
85
- const result = proxy.getUpstreamHeaders(config, request as any);
86
- assert.ok(!("X-Body-Forwarded" in result.upstreamHeaders));
87
- });
88
- });
89
-
90
- describe("HttpProxyService - Body size validation", () => {
91
- const proxy = new HttpProxyService();
92
-
93
- it("limits forwarded body size to configured maximum", () => {
94
- const largeBody = Buffer.alloc(10 * 1024 * 1024, "x");
95
- assert.ok(largeBody.length > 5 * 1024 * 1024);
96
- assert.doesNotThrow(() => {
97
- const config: HostConfig & { http_proxy?: { enabled?: boolean; upstream?: string; headers?: Record<string, string>; forwardRequestBody?: boolean; maxRequestBodyBytes?: number } } = makeBodyForwardingConfig();
98
- config.http_proxy!.forwardRequestBody = true;
99
- });
100
- });
101
- });