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