wslog-proxy 0.0.0-stage → 0.1.1
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.
- package/LICENSE +21 -0
- package/README.md +110 -2
- package/dist/cli/args.d.ts +14 -0
- package/dist/cli/args.js +98 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +78 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/server/app.d.ts +19 -0
- package/dist/server/app.js +96 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/config.d.ts +51 -0
- package/dist/server/config.js +62 -0
- package/dist/server/config.js.map +1 -0
- package/dist/server/connections.d.ts +21 -0
- package/dist/server/connections.js +81 -0
- package/dist/server/connections.js.map +1 -0
- package/dist/server/context.d.ts +37 -0
- package/dist/server/context.js +64 -0
- package/dist/server/context.js.map +1 -0
- package/dist/server/demo/bots.d.ts +4 -0
- package/dist/server/demo/bots.js +90 -0
- package/dist/server/demo/bots.js.map +1 -0
- package/dist/server/demo/chat-server.d.ts +9 -0
- package/dist/server/demo/chat-server.js +90 -0
- package/dist/server/demo/chat-server.js.map +1 -0
- package/dist/server/demo/controller.d.ts +21 -0
- package/dist/server/demo/controller.js +37 -0
- package/dist/server/demo/controller.js.map +1 -0
- package/dist/server/events.d.ts +19 -0
- package/dist/server/events.js +19 -0
- package/dist/server/events.js.map +1 -0
- package/dist/server/http/api.d.ts +6 -0
- package/dist/server/http/api.js +89 -0
- package/dist/server/http/api.js.map +1 -0
- package/dist/server/http/auth.d.ts +26 -0
- package/dist/server/http/auth.js +78 -0
- package/dist/server/http/auth.js.map +1 -0
- package/dist/server/http/live.d.ts +26 -0
- package/dist/server/http/live.js +117 -0
- package/dist/server/http/live.js.map +1 -0
- package/dist/server/http/router.d.ts +31 -0
- package/dist/server/http/router.js +89 -0
- package/dist/server/http/router.js.map +1 -0
- package/dist/server/http/server.d.ts +17 -0
- package/dist/server/http/server.js +82 -0
- package/dist/server/http/server.js.map +1 -0
- package/dist/server/http/static.d.ts +3 -0
- package/dist/server/http/static.js +56 -0
- package/dist/server/http/static.js.map +1 -0
- package/dist/server/http/upgrade.d.ts +3 -0
- package/dist/server/http/upgrade.js +13 -0
- package/dist/server/http/upgrade.js.map +1 -0
- package/dist/server/masking.d.ts +11 -0
- package/dist/server/masking.js +104 -0
- package/dist/server/masking.js.map +1 -0
- package/dist/server/paths.d.ts +12 -0
- package/dist/server/paths.js +30 -0
- package/dist/server/paths.js.map +1 -0
- package/dist/server/plugins.d.ts +20 -0
- package/dist/server/plugins.js +4 -0
- package/dist/server/plugins.js.map +1 -0
- package/dist/server/proxy/headers.d.ts +4 -0
- package/dist/server/proxy/headers.js +39 -0
- package/dist/server/proxy/headers.js.map +1 -0
- package/dist/server/proxy/proxy.d.ts +41 -0
- package/dist/server/proxy/proxy.js +188 -0
- package/dist/server/proxy/proxy.js.map +1 -0
- package/dist/server/recorder.d.ts +34 -0
- package/dist/server/recorder.js +65 -0
- package/dist/server/recorder.js.map +1 -0
- package/dist/server/storage/index.d.ts +14 -0
- package/dist/server/storage/index.js +40 -0
- package/dist/server/storage/index.js.map +1 -0
- package/dist/server/storage/jsonl.d.ts +30 -0
- package/dist/server/storage/jsonl.js +107 -0
- package/dist/server/storage/jsonl.js.map +1 -0
- package/dist/server/storage/memory.d.ts +24 -0
- package/dist/server/storage/memory.js +74 -0
- package/dist/server/storage/memory.js.map +1 -0
- package/dist/server/storage/sqlite.d.ts +23 -0
- package/dist/server/storage/sqlite.js +125 -0
- package/dist/server/storage/sqlite.js.map +1 -0
- package/dist/server/storage/types.d.ts +30 -0
- package/dist/server/storage/types.js +2 -0
- package/dist/server/storage/types.js.map +1 -0
- package/dist/server/targets.d.ts +41 -0
- package/dist/server/targets.js +156 -0
- package/dist/server/targets.js.map +1 -0
- package/dist/server/util/logger.d.ts +16 -0
- package/dist/server/util/logger.js +19 -0
- package/dist/server/util/logger.js.map +1 -0
- package/dist/shared/filter.d.ts +14 -0
- package/dist/shared/filter.js +41 -0
- package/dist/shared/filter.js.map +1 -0
- package/dist/shared/index.d.ts +2 -0
- package/dist/shared/index.js +3 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/protocol.d.ts +124 -0
- package/dist/shared/protocol.js +9 -0
- package/dist/shared/protocol.js.map +1 -0
- package/dist/web/assets/index-C3OwozRd.css +1 -0
- package/dist/web/assets/index-D2oUxHhH.js +11 -0
- package/dist/web/favicon.svg +1 -0
- package/dist/web/index.html +15 -0
- package/package.json +84 -4
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
export const MASK = '***';
|
|
2
|
+
const identity = { fields: [], text: (s) => s, url: (s) => redactUrlPassword(s) };
|
|
3
|
+
/**
|
|
4
|
+
* Longest backslash run treated as JSON escaping. Each nesting level of
|
|
5
|
+
* JSON-in-a-string doubles it (1, 3, 7, 15, 31), so 31 covers five levels.
|
|
6
|
+
* Bounding it keeps masking linear: an unbounded run made it quadratic, and
|
|
7
|
+
* one 100 KB message stalled the proxy for seconds.
|
|
8
|
+
*/
|
|
9
|
+
const MAX_ESCAPE_RUN = 31;
|
|
10
|
+
export function createMasker(fields) {
|
|
11
|
+
const keys = new Set(fields.map((f) => f.toLowerCase()).filter(Boolean));
|
|
12
|
+
if (keys.size === 0)
|
|
13
|
+
return identity;
|
|
14
|
+
const isMasked = (key) => keys.has(key.toLowerCase());
|
|
15
|
+
const alternation = [...keys].map(escapeRegExp).join('|');
|
|
16
|
+
// "token": "abc" 'token': 'abc' "token": 123
|
|
17
|
+
const quotedPattern = new RegExp(`(["'](?:${alternation})["']\\s*:\\s*)("(?:[^"\\\\]|\\\\.)*"|'[^']*'|[^,}\\]\\s]+)`, 'gi');
|
|
18
|
+
// token=abc token: abc
|
|
19
|
+
const plainPattern = new RegExp(`(\\b(?:${alternation})\\s*[=:]\\s*)([^\\s&,;"'}\\]]+)`, 'gi');
|
|
20
|
+
// \"token\":\"abc\" JSON escaped inside a string, any depth (the backslash run must repeat).
|
|
21
|
+
// The run must start at a non-backslash, so a long run is tried once, not from every position.
|
|
22
|
+
const escapedPattern = new RegExp(`(?<!\\\\)((\\\\{1,${MAX_ESCAPE_RUN}})"(?:${alternation})\\2"\\s*:\\s*)(\\2"(?:(?!\\2").)*\\2"|[^,}\\]\\s\\\\]+)`, 'gi');
|
|
23
|
+
function maskValue(value) {
|
|
24
|
+
if (isRawJson(value))
|
|
25
|
+
return value;
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return value.map(maskValue);
|
|
28
|
+
if (value && typeof value === 'object') {
|
|
29
|
+
const out = {};
|
|
30
|
+
for (const [k, v] of Object.entries(value))
|
|
31
|
+
out[k] = isMasked(k) ? MASK : maskValue(v);
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
// Strings can carry secrets too: JSON-encoded payloads (echoes, envelopes) and URLs.
|
|
35
|
+
if (typeof value === 'string')
|
|
36
|
+
return maskText(value);
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
function maskText(input) {
|
|
40
|
+
const json = maskJson(input);
|
|
41
|
+
if (json !== undefined)
|
|
42
|
+
return json;
|
|
43
|
+
return input
|
|
44
|
+
.replace(escapedPattern, (_m, prefix, slashes) => `${prefix}${slashes}"${MASK}${slashes}"`)
|
|
45
|
+
.replace(quotedPattern, (_m, prefix) => `${prefix}"${MASK}"`)
|
|
46
|
+
.replace(plainPattern, (_m, prefix) => `${prefix}${MASK}`);
|
|
47
|
+
}
|
|
48
|
+
function maskJson(input) {
|
|
49
|
+
const first = input.trimStart()[0];
|
|
50
|
+
if (first !== '{' && first !== '[')
|
|
51
|
+
return undefined;
|
|
52
|
+
try {
|
|
53
|
+
const parsed = parseLossless(input);
|
|
54
|
+
const masked = JSON.stringify(maskValue(parsed));
|
|
55
|
+
// Keep the original formatting when nothing needed masking.
|
|
56
|
+
return masked === JSON.stringify(parsed) ? input : masked;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
fields: [...keys],
|
|
64
|
+
text: maskText,
|
|
65
|
+
url(input) {
|
|
66
|
+
const redacted = redactUrlPassword(input);
|
|
67
|
+
const qIndex = redacted.indexOf('?');
|
|
68
|
+
if (qIndex === -1)
|
|
69
|
+
return redacted;
|
|
70
|
+
const params = new URLSearchParams(redacted.slice(qIndex + 1));
|
|
71
|
+
let changed = false;
|
|
72
|
+
for (const key of [...params.keys()]) {
|
|
73
|
+
if (isMasked(key)) {
|
|
74
|
+
params.set(key, MASK);
|
|
75
|
+
changed = true;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return changed ? `${redacted.slice(0, qIndex)}?${params.toString()}` : redacted;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** `ws://user:secret@host/x` → `ws://user:***@host/x`. */
|
|
83
|
+
export function redactUrlPassword(input) {
|
|
84
|
+
return input.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/?#@:]*:)[^/?#@]*@/i, `$1${MASK}@`);
|
|
85
|
+
}
|
|
86
|
+
// `JSON.rawJSON` keeps a number's exact source text through parse → stringify
|
|
87
|
+
// (Node 21+). Without it, 12345678901234567890 would be stored as 12345678901234567000.
|
|
88
|
+
const jsonExt = JSON;
|
|
89
|
+
const isRawJson = (value) => jsonExt.isRawJSON?.(value) ?? false;
|
|
90
|
+
/** A run this long may be a number that does not survive a double round trip. */
|
|
91
|
+
const MAYBE_LOSSY_NUMBER = /\d[\d.eE+-]{15}/;
|
|
92
|
+
/** JSON.parse, but numbers keep their exact text. Throws (→ text masking) if that is not possible. */
|
|
93
|
+
function parseLossless(input) {
|
|
94
|
+
if (!MAYBE_LOSSY_NUMBER.test(input))
|
|
95
|
+
return JSON.parse(input);
|
|
96
|
+
const { rawJSON } = jsonExt;
|
|
97
|
+
if (!rawJSON)
|
|
98
|
+
throw new Error('JSON.rawJSON is not available');
|
|
99
|
+
return JSON.parse(input, (_key, value, context) => typeof value === 'number' && context?.source !== undefined && JSON.stringify(value) !== context.source ? rawJSON(context.source) : value);
|
|
100
|
+
}
|
|
101
|
+
function escapeRegExp(s) {
|
|
102
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=masking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"masking.js","sourceRoot":"","sources":["../../src/server/masking.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Package root, from both `src/server/` (tsx) and `dist/server/` (built). */
|
|
2
|
+
export declare const packageRoot: string;
|
|
3
|
+
export declare const defaultWebRoot: string;
|
|
4
|
+
/** Where the CLI remembers targets between runs. */
|
|
5
|
+
export declare function defaultTargetsFile(env?: NodeJS.ProcessEnv): string;
|
|
6
|
+
/**
|
|
7
|
+
* Create `path` if needed and make it readable by the current user only.
|
|
8
|
+
* Traffic and target files can hold tokens; `mode` alone would not tighten
|
|
9
|
+
* a file that already exists.
|
|
10
|
+
*/
|
|
11
|
+
export declare function ensurePrivateFile(path: string): void;
|
|
12
|
+
export declare function readVersion(): string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { chmodSync, closeSync, openSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
/** Package root, from both `src/server/` (tsx) and `dist/server/` (built). */
|
|
6
|
+
export const packageRoot = fileURLToPath(new URL('../../', import.meta.url));
|
|
7
|
+
export const defaultWebRoot = join(packageRoot, 'dist', 'web');
|
|
8
|
+
/** Where the CLI remembers targets between runs. */
|
|
9
|
+
export function defaultTargetsFile(env = process.env) {
|
|
10
|
+
return join(env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'wslog', 'targets.json');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Create `path` if needed and make it readable by the current user only.
|
|
14
|
+
* Traffic and target files can hold tokens; `mode` alone would not tighten
|
|
15
|
+
* a file that already exists.
|
|
16
|
+
*/
|
|
17
|
+
export function ensurePrivateFile(path) {
|
|
18
|
+
closeSync(openSync(path, 'a', 0o600));
|
|
19
|
+
chmodSync(path, 0o600);
|
|
20
|
+
}
|
|
21
|
+
export function readVersion() {
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(readFileSync(join(packageRoot, 'package.json'), 'utf8'));
|
|
24
|
+
return pkg.version ?? '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return '0.0.0';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/server/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvE,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,8EAA8E;AAC9E,MAAM,CAAC,MAAM,WAAW,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAE7E,MAAM,CAAC,MAAM,cAAc,GAAG,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC;AAE/D,oDAAoD;AACpD,MAAM,UAAU,kBAAkB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACrE,OAAO,IAAI,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC;AAC1F,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC;IACtC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACzB,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAyB,CAAC;QACxG,OAAO,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC;IACjB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WslogContext } from './context.js';
|
|
2
|
+
import type { MessageTransform } from './recorder.js';
|
|
3
|
+
/**
|
|
4
|
+
* Extension point for wslog. A plugin gets the full context and can:
|
|
5
|
+
* - subscribe to events: ctx.bus.on('message', ...)
|
|
6
|
+
* - add HTTP routes: ctx.router.get('/api/my-plugin', ...)
|
|
7
|
+
* - change or drop messages: api.transform(draft => ...)
|
|
8
|
+
*
|
|
9
|
+
* Plugins run in order, after built-in masking.
|
|
10
|
+
*/
|
|
11
|
+
export interface WslogPlugin {
|
|
12
|
+
name: string;
|
|
13
|
+
setup(ctx: WslogContext, api: PluginApi): void | Promise<void>;
|
|
14
|
+
/** Called on shutdown, in reverse order. */
|
|
15
|
+
teardown?(): void | Promise<void>;
|
|
16
|
+
}
|
|
17
|
+
export interface PluginApi {
|
|
18
|
+
transform(fn: MessageTransform): void;
|
|
19
|
+
}
|
|
20
|
+
export declare function definePlugin(plugin: WslogPlugin): WslogPlugin;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plugins.js","sourceRoot":"","sources":["../../src/server/plugins.ts"],"names":[],"mappings":"AAsBA,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { IncomingMessage, OutgoingHttpHeaders } from 'node:http';
|
|
2
|
+
/** Copy the client's handshake headers (cookies, auth, origin, ...) to the upstream request. */
|
|
3
|
+
export declare function forwardHeaders(req: IncomingMessage): OutgoingHttpHeaders;
|
|
4
|
+
export declare function parseProtocols(header: string | undefined): string[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** Headers that describe the client↔proxy hop and must not be copied upstream. */
|
|
2
|
+
const HOP_BY_HOP = new Set([
|
|
3
|
+
'host',
|
|
4
|
+
'connection',
|
|
5
|
+
'upgrade',
|
|
6
|
+
'keep-alive',
|
|
7
|
+
'proxy-connection',
|
|
8
|
+
'transfer-encoding',
|
|
9
|
+
'content-length',
|
|
10
|
+
'sec-websocket-key',
|
|
11
|
+
'sec-websocket-version',
|
|
12
|
+
'sec-websocket-extensions',
|
|
13
|
+
'sec-websocket-protocol',
|
|
14
|
+
'sec-websocket-accept',
|
|
15
|
+
]);
|
|
16
|
+
/** Copy the client's handshake headers (cookies, auth, origin, ...) to the upstream request. */
|
|
17
|
+
export function forwardHeaders(req) {
|
|
18
|
+
const headers = {};
|
|
19
|
+
for (const [name, value] of Object.entries(req.headers)) {
|
|
20
|
+
if (value === undefined || HOP_BY_HOP.has(name))
|
|
21
|
+
continue;
|
|
22
|
+
headers[name] = value;
|
|
23
|
+
}
|
|
24
|
+
const clientIp = req.socket.remoteAddress;
|
|
25
|
+
if (clientIp) {
|
|
26
|
+
const prior = req.headers['x-forwarded-for'];
|
|
27
|
+
headers['x-forwarded-for'] = prior ? `${String(prior)}, ${clientIp}` : clientIp;
|
|
28
|
+
}
|
|
29
|
+
return headers;
|
|
30
|
+
}
|
|
31
|
+
export function parseProtocols(header) {
|
|
32
|
+
if (!header)
|
|
33
|
+
return [];
|
|
34
|
+
return header
|
|
35
|
+
.split(',')
|
|
36
|
+
.map((p) => p.trim())
|
|
37
|
+
.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=headers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headers.js","sourceRoot":"","sources":["../../../src/server/proxy/headers.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC;IACzB,MAAM;IACN,YAAY;IACZ,SAAS;IACT,YAAY;IACZ,kBAAkB;IAClB,mBAAmB;IACnB,gBAAgB;IAChB,mBAAmB;IACnB,uBAAuB;IACvB,0BAA0B;IAC1B,wBAAwB;IACxB,sBAAsB;CACvB,CAAC,CAAC;AAEH,gGAAgG;AAChG,MAAM,UAAU,cAAc,CAAC,GAAoB;IACjD,MAAM,OAAO,GAAwB,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;QACxD,IAAI,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAS;QAC1D,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACxB,CAAC;IACD,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,aAAa,CAAC;IAC1C,IAAI,QAAQ,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QAC7C,OAAO,CAAC,iBAAiB,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;IAClF,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,MAA0B;IACvD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IACvB,OAAO,MAAM;SACV,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { IncomingMessage } from 'node:http';
|
|
2
|
+
import type { Duplex } from 'node:stream';
|
|
3
|
+
import type { TargetInfo } from '../../shared/protocol.js';
|
|
4
|
+
import type { ConnectionRegistry } from '../connections.js';
|
|
5
|
+
import type { Masker } from '../masking.js';
|
|
6
|
+
import type { Recorder } from '../recorder.js';
|
|
7
|
+
import type { Logger } from '../util/logger.js';
|
|
8
|
+
export interface ProxyDeps {
|
|
9
|
+
connections: ConnectionRegistry;
|
|
10
|
+
recorder: Recorder;
|
|
11
|
+
masker: Masker;
|
|
12
|
+
logger: Logger;
|
|
13
|
+
handshakeTimeoutMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Bridges a client WebSocket to its target server and records every frame.
|
|
17
|
+
*
|
|
18
|
+
* The upstream connection is opened first; the client handshake is only
|
|
19
|
+
* completed once the server accepted, so the negotiated subprotocol and
|
|
20
|
+
* any rejection status (401, 404, ...) are passed through to the client.
|
|
21
|
+
*/
|
|
22
|
+
export declare class WebSocketProxy {
|
|
23
|
+
private readonly deps;
|
|
24
|
+
private readonly wss;
|
|
25
|
+
private readonly selectedProtocol;
|
|
26
|
+
private readonly sockets;
|
|
27
|
+
/** Open bridges by connection id, so removing a target can disconnect its apps. */
|
|
28
|
+
private readonly bridges;
|
|
29
|
+
constructor(deps: ProxyDeps);
|
|
30
|
+
handleUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, target: TargetInfo): void;
|
|
31
|
+
/**
|
|
32
|
+
* An app used a proxy URL with no target behind it. Record the attempt so the
|
|
33
|
+
* dashboard can say so, instead of the app failing with a 404 nobody sees.
|
|
34
|
+
*/
|
|
35
|
+
rejectUnknownTarget(req: IncomingMessage, socket: Duplex, targetId: string): void;
|
|
36
|
+
private bridge;
|
|
37
|
+
/** Disconnect apps whose target is no longer in `targets`, so nothing keeps recording unseen. */
|
|
38
|
+
closeRemovedTargets(targets: readonly TargetInfo[]): void;
|
|
39
|
+
/** Close every proxied connection (server shutdown). */
|
|
40
|
+
closeAll(): void;
|
|
41
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import WebSocket, { WebSocketServer } from 'ws';
|
|
2
|
+
import { buildUpstreamUrl } from '../targets.js';
|
|
3
|
+
import { rejectUpgrade } from '../http/upgrade.js';
|
|
4
|
+
import { forwardHeaders, parseProtocols } from './headers.js';
|
|
5
|
+
/**
|
|
6
|
+
* Bridges a client WebSocket to its target server and records every frame.
|
|
7
|
+
*
|
|
8
|
+
* The upstream connection is opened first; the client handshake is only
|
|
9
|
+
* completed once the server accepted, so the negotiated subprotocol and
|
|
10
|
+
* any rejection status (401, 404, ...) are passed through to the client.
|
|
11
|
+
*/
|
|
12
|
+
export class WebSocketProxy {
|
|
13
|
+
deps;
|
|
14
|
+
wss;
|
|
15
|
+
selectedProtocol = new WeakMap();
|
|
16
|
+
sockets = new Set();
|
|
17
|
+
/** Open bridges by connection id, so removing a target can disconnect its apps. */
|
|
18
|
+
bridges = new Map();
|
|
19
|
+
constructor(deps) {
|
|
20
|
+
this.deps = deps;
|
|
21
|
+
this.wss = new WebSocketServer({
|
|
22
|
+
noServer: true,
|
|
23
|
+
handleProtocols: (_offered, req) => this.selectedProtocol.get(req) ?? false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
handleUpgrade(req, socket, head, target) {
|
|
27
|
+
const { connections, masker, logger } = this.deps;
|
|
28
|
+
const requestPath = req.url ?? target.proxyPath;
|
|
29
|
+
const upstreamUrl = buildUpstreamUrl(target, requestPath);
|
|
30
|
+
const connection = connections.create({
|
|
31
|
+
targetId: target.id,
|
|
32
|
+
path: masker.url(requestPath),
|
|
33
|
+
upstreamUrl: masker.url(upstreamUrl),
|
|
34
|
+
clientAddress: req.socket.remoteAddress ?? 'unknown',
|
|
35
|
+
userAgent: req.headers['user-agent'],
|
|
36
|
+
});
|
|
37
|
+
const upstream = new WebSocket(upstreamUrl, parseProtocols(req.headers['sec-websocket-protocol']), {
|
|
38
|
+
headers: forwardHeaders(req),
|
|
39
|
+
handshakeTimeout: this.deps.handshakeTimeoutMs ?? 10_000,
|
|
40
|
+
followRedirects: false,
|
|
41
|
+
});
|
|
42
|
+
upstream.binaryType = 'nodebuffer';
|
|
43
|
+
let settled = false;
|
|
44
|
+
const fail = (status, message, failure) => {
|
|
45
|
+
if (settled)
|
|
46
|
+
return;
|
|
47
|
+
settled = true;
|
|
48
|
+
logger.debug(`proxy ${connection.id}: ${message}`);
|
|
49
|
+
connections.update(connection.id, { status: 'error', error: message, failure, closedBy: 'proxy' });
|
|
50
|
+
rejectUpgrade(socket, status, message);
|
|
51
|
+
};
|
|
52
|
+
// Client gave up while we were still dialing the server.
|
|
53
|
+
const onEarlyClose = () => {
|
|
54
|
+
if (settled)
|
|
55
|
+
return;
|
|
56
|
+
settled = true;
|
|
57
|
+
upstream.terminate();
|
|
58
|
+
connections.update(connection.id, { status: 'closed', closedBy: 'client' });
|
|
59
|
+
};
|
|
60
|
+
socket.once('close', onEarlyClose);
|
|
61
|
+
upstream.once('unexpected-response', (_req, res) => {
|
|
62
|
+
const status = res.statusCode ?? 502;
|
|
63
|
+
res.resume();
|
|
64
|
+
fail(status, `Server rejected the handshake: ${status} ${res.statusMessage ?? ''}`.trim(), 'rejected');
|
|
65
|
+
upstream.terminate();
|
|
66
|
+
});
|
|
67
|
+
upstream.on('error', (err) => {
|
|
68
|
+
if (!settled)
|
|
69
|
+
fail(502, `Could not reach ${masker.url(upstreamUrl)}: ${err.message}`, 'unreachable');
|
|
70
|
+
});
|
|
71
|
+
upstream.once('open', () => {
|
|
72
|
+
if (settled)
|
|
73
|
+
return upstream.terminate();
|
|
74
|
+
settled = true;
|
|
75
|
+
socket.off('close', onEarlyClose);
|
|
76
|
+
if (upstream.protocol)
|
|
77
|
+
this.selectedProtocol.set(req, upstream.protocol);
|
|
78
|
+
this.wss.handleUpgrade(req, socket, head, (client) => this.bridge(client, upstream, connection.id, target.id));
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* An app used a proxy URL with no target behind it. Record the attempt so the
|
|
83
|
+
* dashboard can say so, instead of the app failing with a 404 nobody sees.
|
|
84
|
+
*/
|
|
85
|
+
rejectUnknownTarget(req, socket, targetId) {
|
|
86
|
+
const { connections, masker } = this.deps;
|
|
87
|
+
const message = 'No target for this proxy URL. Paste the server URL in the dashboard again; the proxy URL will be the same.';
|
|
88
|
+
const connection = connections.create({
|
|
89
|
+
targetId,
|
|
90
|
+
path: masker.url(req.url ?? ''),
|
|
91
|
+
upstreamUrl: '',
|
|
92
|
+
clientAddress: req.socket.remoteAddress ?? 'unknown',
|
|
93
|
+
userAgent: req.headers['user-agent'],
|
|
94
|
+
});
|
|
95
|
+
connections.update(connection.id, { status: 'error', error: message, failure: 'no-target', closedBy: 'proxy' });
|
|
96
|
+
rejectUpgrade(socket, 404, message);
|
|
97
|
+
}
|
|
98
|
+
bridge(client, upstream, connectionId, targetId) {
|
|
99
|
+
const { connections, recorder, logger } = this.deps;
|
|
100
|
+
client.binaryType = 'nodebuffer';
|
|
101
|
+
this.sockets.add(client);
|
|
102
|
+
this.sockets.add(upstream);
|
|
103
|
+
connections.update(connectionId, { status: 'open', protocol: upstream.protocol || undefined });
|
|
104
|
+
// Forward first, then record: recording (masking, storage) must never delay traffic.
|
|
105
|
+
client.on('message', (data, isBinary) => {
|
|
106
|
+
const buffer = toBuffer(data);
|
|
107
|
+
if (upstream.readyState === WebSocket.OPEN)
|
|
108
|
+
upstream.send(buffer, { binary: isBinary });
|
|
109
|
+
recorder.record({ connectionId, targetId, direction: 'out', data: buffer, isBinary });
|
|
110
|
+
});
|
|
111
|
+
upstream.on('message', (data, isBinary) => {
|
|
112
|
+
const buffer = toBuffer(data);
|
|
113
|
+
if (client.readyState === WebSocket.OPEN)
|
|
114
|
+
client.send(buffer, { binary: isBinary });
|
|
115
|
+
recorder.record({ connectionId, targetId, direction: 'in', data: buffer, isBinary });
|
|
116
|
+
});
|
|
117
|
+
let closed = false;
|
|
118
|
+
let closedByProxy = false;
|
|
119
|
+
this.bridges.set(connectionId, {
|
|
120
|
+
targetId,
|
|
121
|
+
close: (why) => {
|
|
122
|
+
closedByProxy = true;
|
|
123
|
+
client.close(1001, why);
|
|
124
|
+
upstream.close(1001, why);
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
const onClose = (side, other) => (code, reason) => {
|
|
128
|
+
this.sockets.delete(side === 'client' ? client : upstream);
|
|
129
|
+
this.sockets.delete(other);
|
|
130
|
+
this.bridges.delete(connectionId);
|
|
131
|
+
closeWith(other, code, reason);
|
|
132
|
+
if (closed)
|
|
133
|
+
return;
|
|
134
|
+
closed = true;
|
|
135
|
+
connections.update(connectionId, {
|
|
136
|
+
status: 'closed',
|
|
137
|
+
closedBy: closedByProxy ? 'proxy' : side,
|
|
138
|
+
closeCode: code,
|
|
139
|
+
closeReason: reason.toString('utf8') || undefined,
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
client.on('close', onClose('client', upstream));
|
|
143
|
+
upstream.on('close', onClose('server', client));
|
|
144
|
+
client.on('error', (err) => {
|
|
145
|
+
logger.debug(`proxy ${connectionId}: client error: ${err.message}`);
|
|
146
|
+
upstream.terminate();
|
|
147
|
+
});
|
|
148
|
+
upstream.on('error', (err) => {
|
|
149
|
+
logger.debug(`proxy ${connectionId}: server error: ${err.message}`);
|
|
150
|
+
connections.update(connectionId, { error: err.message });
|
|
151
|
+
client.terminate();
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
/** Disconnect apps whose target is no longer in `targets`, so nothing keeps recording unseen. */
|
|
155
|
+
closeRemovedTargets(targets) {
|
|
156
|
+
const ids = new Set(targets.map((t) => t.id));
|
|
157
|
+
for (const bridge of this.bridges.values()) {
|
|
158
|
+
if (!ids.has(bridge.targetId))
|
|
159
|
+
bridge.close('Target removed in wslog');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** Close every proxied connection (server shutdown). */
|
|
163
|
+
closeAll() {
|
|
164
|
+
for (const ws of this.sockets)
|
|
165
|
+
ws.close(1001, 'wslog shutting down');
|
|
166
|
+
this.sockets.clear();
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
function toBuffer(data) {
|
|
170
|
+
if (Buffer.isBuffer(data))
|
|
171
|
+
return data;
|
|
172
|
+
if (Array.isArray(data))
|
|
173
|
+
return Buffer.concat(data);
|
|
174
|
+
return Buffer.from(data);
|
|
175
|
+
}
|
|
176
|
+
/** Mirror a close to the other side, mapping codes that may not be sent on the wire. */
|
|
177
|
+
function closeWith(ws, code, reason) {
|
|
178
|
+
if (ws.readyState !== WebSocket.OPEN && ws.readyState !== WebSocket.CONNECTING)
|
|
179
|
+
return;
|
|
180
|
+
if (code === 1006 || code === 1015)
|
|
181
|
+
return ws.terminate(); // abnormal: no close frame was received
|
|
182
|
+
const sendable = (code >= 1000 && code <= 1014 && code !== 1004 && code !== 1005) || (code >= 3000 && code <= 4999);
|
|
183
|
+
if (sendable)
|
|
184
|
+
ws.close(code, reason.subarray(0, 123));
|
|
185
|
+
else
|
|
186
|
+
ws.close(1000);
|
|
187
|
+
}
|
|
188
|
+
//# sourceMappingURL=proxy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proxy.js","sourceRoot":"","sources":["../../../src/server/proxy/proxy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { Direction, MessageRecord } from '../shared/protocol.js';
|
|
2
|
+
import type { ConnectionRegistry } from './connections.js';
|
|
3
|
+
import type { EventBus } from './events.js';
|
|
4
|
+
import type { Masker } from './masking.js';
|
|
5
|
+
import type { MessageStore } from './storage/types.js';
|
|
6
|
+
export type DraftRecord = Omit<MessageRecord, 'id'>;
|
|
7
|
+
/**
|
|
8
|
+
* Runs on every message before it is stored. Return a (possibly changed)
|
|
9
|
+
* draft, or `null` to drop the message. Masking is the first built-in transform.
|
|
10
|
+
*/
|
|
11
|
+
export type MessageTransform = (draft: DraftRecord) => DraftRecord | null;
|
|
12
|
+
export interface Frame {
|
|
13
|
+
connectionId: string;
|
|
14
|
+
targetId: string;
|
|
15
|
+
direction: Direction;
|
|
16
|
+
data: Buffer;
|
|
17
|
+
isBinary: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface RecorderOptions {
|
|
20
|
+
store: MessageStore;
|
|
21
|
+
bus: EventBus;
|
|
22
|
+
connections: ConnectionRegistry;
|
|
23
|
+
maxPayloadBytes: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function maskingTransform(masker: Masker): MessageTransform;
|
|
26
|
+
/** Turns raw proxied frames into stored, masked records and announces them. */
|
|
27
|
+
export declare class Recorder {
|
|
28
|
+
private readonly options;
|
|
29
|
+
private readonly transforms;
|
|
30
|
+
private nextId;
|
|
31
|
+
constructor(options: RecorderOptions);
|
|
32
|
+
use(transform: MessageTransform): void;
|
|
33
|
+
record(frame: Frame): MessageRecord | undefined;
|
|
34
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { isUtf8 } from 'node:buffer';
|
|
2
|
+
export function maskingTransform(masker) {
|
|
3
|
+
if (!masker.fields.length)
|
|
4
|
+
return (draft) => draft;
|
|
5
|
+
return (draft) => {
|
|
6
|
+
if (draft.kind === 'text')
|
|
7
|
+
return { ...draft, payload: masker.text(draft.payload) };
|
|
8
|
+
// Binary frames often carry text too, e.g. `ws.send(Buffer.from(JSON.stringify(msg)))`.
|
|
9
|
+
const text = utf8Text(Buffer.from(draft.payload, 'base64'), draft.truncated);
|
|
10
|
+
if (text === undefined)
|
|
11
|
+
return draft;
|
|
12
|
+
const masked = masker.text(text);
|
|
13
|
+
return masked === text ? draft : { ...draft, payload: Buffer.from(masked, 'utf8').toString('base64') };
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** The bytes as text if they are UTF-8. A truncated frame may end in the middle of a character. */
|
|
17
|
+
function utf8Text(bytes, truncated) {
|
|
18
|
+
if (isUtf8(bytes))
|
|
19
|
+
return bytes.toString('utf8');
|
|
20
|
+
for (let cut = 1; truncated && cut <= 3 && cut < bytes.length; cut++) {
|
|
21
|
+
const head = bytes.subarray(0, bytes.length - cut);
|
|
22
|
+
if (isUtf8(head))
|
|
23
|
+
return head.toString('utf8');
|
|
24
|
+
}
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
/** Turns raw proxied frames into stored, masked records and announces them. */
|
|
28
|
+
export class Recorder {
|
|
29
|
+
options;
|
|
30
|
+
transforms = [];
|
|
31
|
+
nextId;
|
|
32
|
+
constructor(options) {
|
|
33
|
+
this.options = options;
|
|
34
|
+
this.nextId = options.store.lastId() + 1;
|
|
35
|
+
}
|
|
36
|
+
use(transform) {
|
|
37
|
+
this.transforms.push(transform);
|
|
38
|
+
}
|
|
39
|
+
record(frame) {
|
|
40
|
+
const { data, isBinary } = frame;
|
|
41
|
+
const truncated = data.length > this.options.maxPayloadBytes;
|
|
42
|
+
const body = truncated ? data.subarray(0, this.options.maxPayloadBytes) : data;
|
|
43
|
+
let draft = {
|
|
44
|
+
connectionId: frame.connectionId,
|
|
45
|
+
targetId: frame.targetId,
|
|
46
|
+
direction: frame.direction,
|
|
47
|
+
time: Date.now(),
|
|
48
|
+
size: data.length,
|
|
49
|
+
kind: isBinary ? 'binary' : 'text',
|
|
50
|
+
payload: isBinary ? body.toString('base64') : body.toString('utf8'),
|
|
51
|
+
truncated,
|
|
52
|
+
};
|
|
53
|
+
for (const transform of this.transforms) {
|
|
54
|
+
draft = transform(draft);
|
|
55
|
+
if (!draft)
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const record = { id: this.nextId++, ...draft };
|
|
59
|
+
this.options.store.append(record);
|
|
60
|
+
this.options.connections.countMessage(record.connectionId, record.direction, record.size);
|
|
61
|
+
this.options.bus.emit('message', record);
|
|
62
|
+
return record;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/server/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { JsonlStore } from './jsonl.js';
|
|
2
|
+
import { MemoryStore } from './memory.js';
|
|
3
|
+
import { SqliteStore } from './sqlite.js';
|
|
4
|
+
import type { MessageStore, StoreOptions } from './types.js';
|
|
5
|
+
export type { MessageQuery, MessageStore, StoreOptions } from './types.js';
|
|
6
|
+
export { MemoryStore, JsonlStore, SqliteStore };
|
|
7
|
+
type StoreFactory = (path: string, options: StoreOptions) => Promise<MessageStore>;
|
|
8
|
+
/** Register a custom storage backend, used as `--store <scheme>:<path>`. */
|
|
9
|
+
export declare function registerStore(scheme: string, factory: StoreFactory): void;
|
|
10
|
+
export declare function parseStoreSpec(spec: string): {
|
|
11
|
+
scheme: string;
|
|
12
|
+
path: string;
|
|
13
|
+
};
|
|
14
|
+
export declare function createStore(spec: string, options: StoreOptions): Promise<MessageStore>;
|