ssh_tunnel_proxy 2.1.1 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/LICENSE +19 -3
  2. package/MIGRATION.md +227 -0
  3. package/README.md +360 -212
  4. package/build/bridge.d.ts +14 -0
  5. package/build/bridge.d.ts.map +1 -0
  6. package/build/bridge.js +12 -0
  7. package/build/bridge.js.map +1 -0
  8. package/build/connection.d.ts +60 -0
  9. package/build/connection.d.ts.map +1 -0
  10. package/build/connection.js +198 -0
  11. package/build/connection.js.map +1 -0
  12. package/build/credentials.d.ts +24 -0
  13. package/build/credentials.d.ts.map +1 -0
  14. package/build/credentials.js +102 -0
  15. package/build/credentials.js.map +1 -0
  16. package/build/errors.d.ts +19 -0
  17. package/build/errors.d.ts.map +1 -0
  18. package/build/errors.js +64 -0
  19. package/build/errors.js.map +1 -0
  20. package/build/forward-spec.d.ts +34 -0
  21. package/build/forward-spec.d.ts.map +1 -0
  22. package/build/forward-spec.js +175 -0
  23. package/build/forward-spec.js.map +1 -0
  24. package/build/index.d.ts +19 -0
  25. package/build/index.d.ts.map +1 -0
  26. package/build/index.js +71 -0
  27. package/build/index.js.map +1 -0
  28. package/build/legacy.d.ts +70 -0
  29. package/build/legacy.d.ts.map +1 -0
  30. package/build/legacy.js +188 -0
  31. package/build/legacy.js.map +1 -0
  32. package/build/local-forward.d.ts +31 -0
  33. package/build/local-forward.d.ts.map +1 -0
  34. package/build/local-forward.js +231 -0
  35. package/build/local-forward.js.map +1 -0
  36. package/build/model.d.ts +126 -0
  37. package/build/model.d.ts.map +1 -0
  38. package/build/model.js +55 -0
  39. package/build/model.js.map +1 -0
  40. package/build/port-policy.d.ts +43 -0
  41. package/build/port-policy.d.ts.map +1 -0
  42. package/build/port-policy.js +138 -0
  43. package/build/port-policy.js.map +1 -0
  44. package/build/remote-forward.d.ts +40 -0
  45. package/build/remote-forward.d.ts.map +1 -0
  46. package/build/remote-forward.js +226 -0
  47. package/build/remote-forward.js.map +1 -0
  48. package/build/tunnel.d.ts +121 -0
  49. package/build/tunnel.d.ts.map +1 -0
  50. package/build/tunnel.js +599 -0
  51. package/build/tunnel.js.map +1 -0
  52. package/examples/config.example.json +86 -0
  53. package/examples/setup_ssh.sh +12 -0
  54. package/examples/tunnel-cli.js +461 -0
  55. package/examples/tunnel-worker.js +98 -0
  56. package/examples/zrok-termux.ts +147 -0
  57. package/package.json +43 -53
  58. package/proto/sshtunnel/v1/tunnel.proto +343 -0
  59. package/dist/TestMessage.d.ts +0 -13
  60. package/dist/TestMessage.js +0 -32
  61. package/dist/TestMessage.js.map +0 -1
  62. package/dist/cjs/TestMessage.d.ts +0 -13
  63. package/dist/cjs/TestMessage.js +0 -32
  64. package/dist/cjs/TestMessage.js.map +0 -1
  65. package/dist/cjs/index.d.ts +0 -73
  66. package/dist/cjs/index.js +0 -621
  67. package/dist/cjs/index.js.map +0 -1
  68. package/dist/cjs/keypair_storage.d.ts +0 -12
  69. package/dist/cjs/keypair_storage.js +0 -143
  70. package/dist/cjs/keypair_storage.js.map +0 -1
  71. package/dist/cjs/ngrok_service.d.ts +0 -11
  72. package/dist/cjs/ngrok_service.js +0 -40
  73. package/dist/cjs/ngrok_service.js.map +0 -1
  74. package/dist/cjs/ssh2-node.d.ts +0 -1
  75. package/dist/cjs/ssh2-node.js +0 -211
  76. package/dist/cjs/ssh2-node.js.map +0 -1
  77. package/dist/esm/TestMessage.d.ts +0 -13
  78. package/dist/esm/TestMessage.js +0 -29
  79. package/dist/esm/TestMessage.js.map +0 -1
  80. package/dist/esm/index.d.ts +0 -73
  81. package/dist/esm/index.js +0 -616
  82. package/dist/esm/index.js.map +0 -1
  83. package/dist/esm/keypair_storage.d.ts +0 -12
  84. package/dist/esm/keypair_storage.js +0 -117
  85. package/dist/esm/keypair_storage.js.map +0 -1
  86. package/dist/esm/ngrok_service.d.ts +0 -11
  87. package/dist/esm/ngrok_service.js +0 -37
  88. package/dist/esm/ngrok_service.js.map +0 -1
  89. package/dist/esm/ssh2-node.d.ts +0 -1
  90. package/dist/esm/ssh2-node.js +0 -184
  91. package/dist/esm/ssh2-node.js.map +0 -1
  92. package/dist/index.d.ts +0 -73
  93. package/dist/index.js +0 -620
  94. package/dist/index.js.map +0 -1
  95. package/dist/keypair_storage.d.ts +0 -12
  96. package/dist/keypair_storage.js +0 -120
  97. package/dist/keypair_storage.js.map +0 -1
  98. package/dist/ngrok_service.d.ts +0 -11
  99. package/dist/ngrok_service.js +0 -40
  100. package/dist/ngrok_service.js.map +0 -1
  101. package/dist/ssh2-node.d.ts +0 -1
  102. package/dist/ssh2-node.js +0 -188
  103. package/dist/ssh2-node.js.map +0 -1
  104. package/dist/types/TestMessage.d.ts +0 -13
  105. package/dist/types/index.d.ts +0 -73
  106. package/dist/types/keypair_storage.d.ts +0 -12
  107. package/dist/types/ngrok_service.d.ts +0 -11
  108. package/dist/types/ssh2-node.d.ts +0 -1
  109. package/ssh2-node +0 -2
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ /*
3
+ * local-forward.ts — ssh -L, as a first-class object.
4
+ *
5
+ * One handle owns one listening socket and every connection accepted on it. The
6
+ * handle can be opened, closed, and re-opened across transport restarts without
7
+ * the caller re-declaring it, which is what makes reconnection transparent.
8
+ *
9
+ * License: MIT
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.LocalForwardHandle = void 0;
13
+ const node_net_1 = require("node:net");
14
+ const connection_1 = require("./connection");
15
+ const errors_1 = require("./errors");
16
+ const forward_spec_1 = require("./forward-spec");
17
+ class LocalForwardHandle {
18
+ forwardId;
19
+ spec;
20
+ state = 'pending';
21
+ server;
22
+ lastError;
23
+ totalConnections = 0;
24
+ connections = new Map();
25
+ bridge;
26
+ idleTimeoutMs;
27
+ constructor(spec, bridge, idleTimeoutMs = 0) {
28
+ this.spec = spec;
29
+ this.forwardId = spec.id ?? (0, forward_spec_1.describeLocalForward)(spec);
30
+ this.bridge = bridge;
31
+ this.idleTimeoutMs = idleTimeoutMs;
32
+ }
33
+ getState() {
34
+ return this.state;
35
+ }
36
+ getStatus() {
37
+ return {
38
+ forwardId: this.forwardId,
39
+ direction: 'local',
40
+ state: this.state,
41
+ listen: { ...this.spec.listen },
42
+ target: { ...this.spec.target },
43
+ assignedPort: this.server?.address()?.port,
44
+ activeConnections: this.connections.size,
45
+ totalConnections: this.totalConnections,
46
+ error: this.lastError,
47
+ };
48
+ }
49
+ listConnections() {
50
+ return [...this.connections.values()].map((connection) => connection.getStatus());
51
+ }
52
+ /** Bind the local listener. Resolves once the port is actually listening. */
53
+ async open() {
54
+ if (this.state === 'active')
55
+ return this.getStatus();
56
+ // A flap can call open() while the previous listener is still bound
57
+ // (state opening/degraded/failed). Reusing it avoids leaking servers.
58
+ if (this.server?.listening) {
59
+ this.lastError = undefined;
60
+ this.transition('active');
61
+ return this.getStatus();
62
+ }
63
+ this.transition('opening');
64
+ const server = (0, node_net_1.createServer)({ allowHalfOpen: true }, (socket) => {
65
+ this.handleSocket(socket);
66
+ });
67
+ this.server = server;
68
+ try {
69
+ await new Promise((resolve, reject) => {
70
+ const onError = (err) => {
71
+ server.removeListener('listening', onListening);
72
+ reject((0, errors_1.asTunnelError)('LISTEN_FAILED', `Cannot bind ${this.spec.listen.host}:${this.spec.listen.port}`, err, { forwardId: this.forwardId }));
73
+ };
74
+ const onListening = () => {
75
+ server.removeListener('error', onError);
76
+ resolve();
77
+ };
78
+ server.once('error', onError);
79
+ server.once('listening', onListening);
80
+ server.listen(this.spec.listen.port, this.spec.listen.host);
81
+ });
82
+ }
83
+ catch (err) {
84
+ this.lastError = err instanceof Error ? err.message : String(err);
85
+ this.server = undefined;
86
+ try {
87
+ server.close();
88
+ }
89
+ catch {
90
+ /* already unusable */
91
+ }
92
+ this.transition('failed');
93
+ throw err;
94
+ }
95
+ // Post-bind errors are reported, not thrown: the listener stays usable.
96
+ server.on('error', (err) => {
97
+ this.lastError = err.message;
98
+ this.bridge.onError((0, errors_1.asTunnelError)('LISTEN_FAILED', `Listener error on ${this.forwardId}`, err, {
99
+ forwardId: this.forwardId,
100
+ }));
101
+ });
102
+ this.transition('active');
103
+ this.bridge.debug(`local forward active ${this.spec.listen.host}:${this.spec.listen.port} -> ${this.spec.target.host}:${this.spec.target.port}`);
104
+ return this.getStatus();
105
+ }
106
+ /**
107
+ * Mark the forward as degraded: the listener stays bound so callers do not see
108
+ * connection-refused during a reconnect, but new connections fail fast until
109
+ * the transport returns.
110
+ */
111
+ markDegraded(reason) {
112
+ if (this.state === 'closed' || this.state === 'closing')
113
+ return;
114
+ this.lastError = reason;
115
+ this.closeConnections();
116
+ this.transition('degraded');
117
+ }
118
+ markActive() {
119
+ if (this.state === 'degraded') {
120
+ this.lastError = undefined;
121
+ this.transition('active');
122
+ }
123
+ }
124
+ async close() {
125
+ if (this.state === 'closed')
126
+ return this.getStatus();
127
+ this.transition('closing');
128
+ this.closeConnections();
129
+ const server = this.server;
130
+ this.server = undefined;
131
+ if (server) {
132
+ await new Promise((resolve) => {
133
+ server.close(() => resolve());
134
+ // close() only waits for existing connections; ours are already gone.
135
+ });
136
+ }
137
+ this.transition('closed');
138
+ return this.getStatus();
139
+ }
140
+ closeConnections() {
141
+ for (const connection of [...this.connections.values()]) {
142
+ connection.close();
143
+ }
144
+ this.connections.clear();
145
+ }
146
+ handleSocket(socket) {
147
+ const limit = this.spec.maxConnections ?? 0;
148
+ if (limit > 0 && this.connections.size >= limit) {
149
+ this.bridge.onError(new errors_1.TunnelError('CONNECTION_LIMIT', `Forward ${this.forwardId} reached maxConnections=${limit}`, { forwardId: this.forwardId }));
150
+ socket.destroy();
151
+ return;
152
+ }
153
+ const sourceHost = socket.remoteAddress ?? this.spec.listen.host;
154
+ const sourcePort = socket.remotePort ?? 0;
155
+ const connection = new connection_1.ProxiedConnection({
156
+ forwardId: this.forwardId,
157
+ direction: 'local',
158
+ source: { host: sourceHost, port: sourcePort },
159
+ destination: { ...this.spec.target },
160
+ idleTimeoutMs: this.idleTimeoutMs,
161
+ onStateChange: (status, previous) => {
162
+ if (status.state === 'closed' || status.state === 'failed') {
163
+ this.connections.delete(status.connectionId);
164
+ }
165
+ this.bridge.onConnectionChanged(status, previous);
166
+ },
167
+ });
168
+ // Adopt the socket immediately so every failure path below destroys it.
169
+ connection.adoptInbound(socket);
170
+ this.connections.set(connection.connectionId, connection);
171
+ this.totalConnections += 1;
172
+ if (this.state !== 'active') {
173
+ connection.fail(new errors_1.TunnelError('TRANSPORT_NOT_READY', `Forward ${this.forwardId} is ${this.state}`, {
174
+ forwardId: this.forwardId,
175
+ connectionId: connection.connectionId,
176
+ }));
177
+ return;
178
+ }
179
+ let client;
180
+ try {
181
+ client = this.bridge.requireClient(this.forwardId);
182
+ }
183
+ catch (err) {
184
+ connection.fail(err instanceof Error ? err : new Error(String(err)));
185
+ return;
186
+ }
187
+ connection.markOpening();
188
+ // Pause until the channel exists so no inbound bytes are dropped.
189
+ socket.pause();
190
+ const openTimer = setTimeout(() => {
191
+ if (connection.isClosed())
192
+ return;
193
+ const wrapped = new errors_1.TunnelError('CHANNEL_OPEN_FAILED', `forwardOut to ${this.spec.target.host}:${this.spec.target.port} timed out`, { forwardId: this.forwardId, connectionId: connection.connectionId });
194
+ this.bridge.onError(wrapped);
195
+ connection.fail(wrapped, 'channel');
196
+ }, 8000);
197
+ try {
198
+ client.forwardOut(sourceHost, sourcePort, this.spec.target.host, this.spec.target.port, (err, stream) => {
199
+ clearTimeout(openTimer);
200
+ if (connection.isClosed()) {
201
+ if (stream && !stream.destroyed)
202
+ stream.destroy();
203
+ return;
204
+ }
205
+ if (err) {
206
+ const wrapped = (0, errors_1.asTunnelError)('CHANNEL_OPEN_FAILED', `forwardOut to ${this.spec.target.host}:${this.spec.target.port} failed`, err, { forwardId: this.forwardId, connectionId: connection.connectionId });
207
+ this.bridge.onError(wrapped);
208
+ connection.fail(wrapped, 'channel');
209
+ return;
210
+ }
211
+ connection.attach(socket, stream);
212
+ socket.resume();
213
+ });
214
+ }
215
+ catch (err) {
216
+ clearTimeout(openTimer);
217
+ const wrapped = (0, errors_1.asTunnelError)('TRANSPORT_NOT_READY', `forwardOut to ${this.spec.target.host}:${this.spec.target.port} failed`, err, { forwardId: this.forwardId, connectionId: connection.connectionId });
218
+ this.bridge.onError(wrapped);
219
+ connection.fail(wrapped, 'channel');
220
+ }
221
+ }
222
+ transition(next) {
223
+ if (this.state === next)
224
+ return;
225
+ const previous = this.state;
226
+ this.state = next;
227
+ this.bridge.onForwardChanged(this.getStatus(), previous);
228
+ }
229
+ }
230
+ exports.LocalForwardHandle = LocalForwardHandle;
231
+ //# sourceMappingURL=local-forward.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-forward.js","sourceRoot":"","sources":["../src/local-forward.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,126 @@
1
+ export declare const MODEL_VERSION = "sshtunnel.v1";
2
+ /** A TCP endpoint. `host` is an IP literal or DNS name, never a URL. */
3
+ export interface Endpoint {
4
+ host: string;
5
+ port: number;
6
+ }
7
+ export declare const CREDENTIAL_SOURCES: readonly ["file", "env", "inline", "agent", "callback"];
8
+ export type CredentialSource = (typeof CREDENTIAL_SOURCES)[number];
9
+ /** Indirect reference to secret material; the model never carries the secret. */
10
+ export interface CredentialRef {
11
+ source: CredentialSource;
12
+ value?: string;
13
+ }
14
+ /**
15
+ * Port authorization is an explicit caller policy. Local listen ports and
16
+ * remote target ports are separate decisions: one governs what this process may
17
+ * bind, the other governs what the SSH peer is asked to reach.
18
+ */
19
+ export interface PortPolicy {
20
+ allowedPrivilegedPorts?: readonly number[];
21
+ allowedLocalListenPorts?: readonly number[];
22
+ allowedRemoteTargetPorts?: readonly number[];
23
+ allowedRemoteHosts?: readonly string[];
24
+ }
25
+ /** ssh -L : listen here, deliver to a host reachable from the SSH peer. */
26
+ export interface LocalForward {
27
+ id?: string;
28
+ listen: Endpoint;
29
+ target: Endpoint;
30
+ exclusive?: boolean;
31
+ maxConnections?: number;
32
+ }
33
+ /** ssh -R : the peer listens, connections are delivered to a host reachable here. */
34
+ export interface RemoteForward {
35
+ id?: string;
36
+ /** Bound on the SSH peer. `port: 0` asks the peer to assign one. */
37
+ bind: Endpoint;
38
+ /** Resolved by this machine. */
39
+ target: Endpoint;
40
+ maxConnections?: number;
41
+ }
42
+ export declare const REACHABILITY_VALUES: readonly ["direct", "zrok", "ngrok", "custom"];
43
+ export type Reachability = (typeof REACHABILITY_VALUES)[number];
44
+ /**
45
+ * How to reach an SSH listener that already exists. This library never
46
+ * provisions a relay; `reachability` is documentation metadata only.
47
+ */
48
+ export interface SshTransport {
49
+ endpoint: Endpoint;
50
+ username: string;
51
+ privateKey?: CredentialRef;
52
+ passphrase?: CredentialRef;
53
+ password?: CredentialRef;
54
+ keepaliveIntervalMs?: number;
55
+ keepaliveCountMax?: number;
56
+ readyTimeoutMs?: number;
57
+ /** `sha256:...` values. Empty or omitted accepts any host key. */
58
+ hostKeyFingerprints?: readonly string[];
59
+ reachability?: Reachability;
60
+ }
61
+ export interface ReconnectPolicy {
62
+ enabled?: boolean;
63
+ /** 0 means unlimited. */
64
+ maxAttempts?: number;
65
+ initialDelayMs?: number;
66
+ maxDelayMs?: number;
67
+ backoffMultiplier?: number;
68
+ /** 0.0 .. 1.0 */
69
+ jitterRatio?: number;
70
+ }
71
+ export interface TunnelConfig {
72
+ id?: string;
73
+ label?: string;
74
+ transport: SshTransport;
75
+ portPolicy?: PortPolicy;
76
+ reconnect?: ReconnectPolicy;
77
+ localForwards?: readonly LocalForward[];
78
+ remoteForwards?: readonly RemoteForward[];
79
+ metadata?: Record<string, string>;
80
+ }
81
+ export declare const TUNNEL_STATES: readonly ["idle", "connecting", "ready", "reconnecting", "closing", "closed", "failed"];
82
+ export type TunnelState = (typeof TUNNEL_STATES)[number];
83
+ export declare const FORWARD_STATES: readonly ["pending", "opening", "active", "degraded", "closing", "closed", "failed"];
84
+ export type ForwardState = (typeof FORWARD_STATES)[number];
85
+ export declare const CONNECTION_STATES: readonly ["accepted", "opening", "piped", "half_closed", "closed", "failed"];
86
+ export type ConnectionState = (typeof CONNECTION_STATES)[number];
87
+ export declare const FORWARD_DIRECTIONS: readonly ["local", "remote"];
88
+ export type ForwardDirection = (typeof FORWARD_DIRECTIONS)[number];
89
+ export interface ConnectionStatus {
90
+ connectionId: string;
91
+ forwardId: string;
92
+ direction: ForwardDirection;
93
+ state: ConnectionState;
94
+ source: Endpoint;
95
+ destination: Endpoint;
96
+ openedAtUnixMs: number;
97
+ closedAtUnixMs?: number;
98
+ bytesToTarget: number;
99
+ bytesFromTarget: number;
100
+ error?: string;
101
+ }
102
+ export interface ForwardStatus {
103
+ forwardId: string;
104
+ direction: ForwardDirection;
105
+ state: ForwardState;
106
+ listen: Endpoint;
107
+ target: Endpoint;
108
+ assignedPort?: number;
109
+ activeConnections: number;
110
+ totalConnections: number;
111
+ error?: string;
112
+ }
113
+ export interface TunnelStatus {
114
+ tunnelId: string;
115
+ state: TunnelState;
116
+ endpoint: Endpoint;
117
+ reconnectAttempt: number;
118
+ connectedAtUnixMs?: number;
119
+ forwards: ForwardStatus[];
120
+ error?: string;
121
+ }
122
+ export declare const DEFAULT_RECONNECT_POLICY: Required<ReconnectPolicy>;
123
+ export declare const DEFAULT_KEEPALIVE_INTERVAL_MS = 10000;
124
+ export declare const DEFAULT_KEEPALIVE_COUNT_MAX = 3;
125
+ export declare const DEFAULT_READY_TIMEOUT_MS = 20000;
126
+ //# sourceMappingURL=model.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.d.ts","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":"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"}
package/build/model.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ /*
3
+ * model.ts — TypeScript projection of proto/sshtunnel/v1/tunnel.proto
4
+ *
5
+ * Hand-maintained rather than generated so the public API stays idiomatic
6
+ * TypeScript (string unions, optional fields, camelCase). The proto file
7
+ * remains the cross-language source of truth; `assertModelParity` in the test
8
+ * suite checks that every proto enum value has a TypeScript counterpart.
9
+ *
10
+ * License: MIT
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.DEFAULT_READY_TIMEOUT_MS = exports.DEFAULT_KEEPALIVE_COUNT_MAX = exports.DEFAULT_KEEPALIVE_INTERVAL_MS = exports.DEFAULT_RECONNECT_POLICY = exports.FORWARD_DIRECTIONS = exports.CONNECTION_STATES = exports.FORWARD_STATES = exports.TUNNEL_STATES = exports.REACHABILITY_VALUES = exports.CREDENTIAL_SOURCES = exports.MODEL_VERSION = void 0;
14
+ exports.MODEL_VERSION = 'sshtunnel.v1';
15
+ exports.CREDENTIAL_SOURCES = ['file', 'env', 'inline', 'agent', 'callback'];
16
+ exports.REACHABILITY_VALUES = ['direct', 'zrok', 'ngrok', 'custom'];
17
+ exports.TUNNEL_STATES = [
18
+ 'idle',
19
+ 'connecting',
20
+ 'ready',
21
+ 'reconnecting',
22
+ 'closing',
23
+ 'closed',
24
+ 'failed',
25
+ ];
26
+ exports.FORWARD_STATES = [
27
+ 'pending',
28
+ 'opening',
29
+ 'active',
30
+ 'degraded',
31
+ 'closing',
32
+ 'closed',
33
+ 'failed',
34
+ ];
35
+ exports.CONNECTION_STATES = [
36
+ 'accepted',
37
+ 'opening',
38
+ 'piped',
39
+ 'half_closed',
40
+ 'closed',
41
+ 'failed',
42
+ ];
43
+ exports.FORWARD_DIRECTIONS = ['local', 'remote'];
44
+ exports.DEFAULT_RECONNECT_POLICY = {
45
+ enabled: true,
46
+ maxAttempts: 10,
47
+ initialDelayMs: 1000,
48
+ maxDelayMs: 30000,
49
+ backoffMultiplier: 2,
50
+ jitterRatio: 0.2,
51
+ };
52
+ exports.DEFAULT_KEEPALIVE_INTERVAL_MS = 10000;
53
+ exports.DEFAULT_KEEPALIVE_COUNT_MAX = 3;
54
+ exports.DEFAULT_READY_TIMEOUT_MS = 20000;
55
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../src/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;GASG;;;AAEU,QAAA,aAAa,GAAG,cAAc,CAAC;AAQ/B,QAAA,kBAAkB,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC;AAwC7E,QAAA,mBAAmB,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAU,CAAC;AA2CrE,QAAA,aAAa,GAAG;IAC3B,MAAM;IACN,YAAY;IACZ,OAAO;IACP,cAAc;IACd,SAAS;IACT,QAAQ;IACR,QAAQ;CACA,CAAC;AAGE,QAAA,cAAc,GAAG;IAC5B,SAAS;IACT,SAAS;IACT,QAAQ;IACR,UAAU;IACV,SAAS;IACT,QAAQ;IACR,QAAQ;CACA,CAAC;AAGE,QAAA,iBAAiB,GAAG;IAC/B,UAAU;IACV,SAAS;IACT,OAAO;IACP,aAAa;IACb,QAAQ;IACR,QAAQ;CACA,CAAC;AAGE,QAAA,kBAAkB,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAU,CAAC;AAuClD,QAAA,wBAAwB,GAA8B;IACjE,OAAO,EAAE,IAAI;IACb,WAAW,EAAE,EAAE;IACf,cAAc,EAAE,IAAI;IACpB,UAAU,EAAE,KAAK;IACjB,iBAAiB,EAAE,CAAC;IACpB,WAAW,EAAE,GAAG;CACjB,CAAC;AAEW,QAAA,6BAA6B,GAAG,KAAK,CAAC;AACtC,QAAA,2BAA2B,GAAG,CAAC,CAAC;AAChC,QAAA,wBAAwB,GAAG,KAAK,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { PortPolicy } from './model';
2
+ export declare const MIN_PORT = 1;
3
+ export declare const MAX_PORT = 65535;
4
+ export declare const FIRST_UNPRIVILEGED_PORT = 1024;
5
+ /** Which side of a forward a port belongs to. */
6
+ export type PortRole = 'local-listen' | 'remote-target' | 'remote-bind';
7
+ /**
8
+ * True when `port` is a syntactically valid TCP port number.
9
+ *
10
+ * Stricter than the v1 `validate_port_number`: fractional values, NaN and
11
+ * numbers parsed out of strings such as `"80abc"` are rejected rather than
12
+ * silently truncated.
13
+ */
14
+ export declare function isValidPort(port: unknown): port is number;
15
+ /**
16
+ * Parse a port from text with no coercion surprises.
17
+ * Returns `undefined` for anything that is not a bare decimal integer.
18
+ */
19
+ export declare function parsePort(text: string): number | undefined;
20
+ /** `port: 0` is legal only for a remote bind, where it means "peer assigns". */
21
+ export declare function isValidBindPort(port: unknown): port is number;
22
+ export interface PortCheckResult {
23
+ allowed: boolean;
24
+ reason?: string;
25
+ }
26
+ /**
27
+ * Evaluate a port against a policy for a given role.
28
+ *
29
+ * Policy semantics, in order:
30
+ * 1. The port must be a valid TCP port (a remote bind may also be 0).
31
+ * 2. A privileged port (< 1024) requires an explicit allowlist entry.
32
+ * 3. If a role-specific allowlist is non-empty, the port must appear in it.
33
+ *
34
+ * An absent or empty policy therefore permits any unprivileged port and no
35
+ * privileged port.
36
+ */
37
+ export declare function checkPort(port: unknown, role: PortRole, policy?: PortPolicy): PortCheckResult;
38
+ /** Throwing form of {@link checkPort}. */
39
+ export declare function assertPortAllowed(port: unknown, role: PortRole, policy?: PortPolicy, forwardId?: string): number;
40
+ /** A hostname or IP literal, syntactically. Does not resolve anything. */
41
+ export declare function isValidHost(host: unknown): host is string;
42
+ export declare function assertHostAllowed(host: unknown, policy?: PortPolicy, forwardId?: string): string;
43
+ //# sourceMappingURL=port-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-policy.d.ts","sourceRoot":"","sources":["../src/port-policy.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE1C,eAAO,MAAM,QAAQ,IAAI,CAAC;AAC1B,eAAO,MAAM,QAAQ,QAAQ,CAAC;AAC9B,eAAO,MAAM,uBAAuB,OAAO,CAAC;AAE5C,iDAAiD;AACjD,MAAM,MAAM,QAAQ,GAAG,cAAc,GAAG,eAAe,GAAG,aAAa,CAAC;AAExE;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAOzD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAK1D;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAE7D;AAMD,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,QAAQ,EACd,MAAM,GAAE,UAAe,GACtB,eAAe,CAoCjB;AAED,0CAA0C;AAC1C,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,QAAQ,EACd,MAAM,GAAE,UAAe,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAQR;AAED,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,MAAM,CAYzD;AAED,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,OAAO,EACb,MAAM,GAAE,UAAe,EACvB,SAAS,CAAC,EAAE,MAAM,GACjB,MAAM,CAaR"}
@@ -0,0 +1,138 @@
1
+ "use strict";
2
+ /*
3
+ * port-policy.ts — pure port authorization.
4
+ *
5
+ * No sockets, no SSH, no filesystem, no network. Everything here is a total
6
+ * function over plain values, which is what makes it cheap to unit test.
7
+ *
8
+ * License: MIT
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.FIRST_UNPRIVILEGED_PORT = exports.MAX_PORT = exports.MIN_PORT = void 0;
12
+ exports.isValidPort = isValidPort;
13
+ exports.parsePort = parsePort;
14
+ exports.isValidBindPort = isValidBindPort;
15
+ exports.checkPort = checkPort;
16
+ exports.assertPortAllowed = assertPortAllowed;
17
+ exports.isValidHost = isValidHost;
18
+ exports.assertHostAllowed = assertHostAllowed;
19
+ const errors_1 = require("./errors");
20
+ exports.MIN_PORT = 1;
21
+ exports.MAX_PORT = 65535;
22
+ exports.FIRST_UNPRIVILEGED_PORT = 1024;
23
+ /**
24
+ * True when `port` is a syntactically valid TCP port number.
25
+ *
26
+ * Stricter than the v1 `validate_port_number`: fractional values, NaN and
27
+ * numbers parsed out of strings such as `"80abc"` are rejected rather than
28
+ * silently truncated.
29
+ */
30
+ function isValidPort(port) {
31
+ return (typeof port === 'number' &&
32
+ Number.isInteger(port) &&
33
+ port >= exports.MIN_PORT &&
34
+ port <= exports.MAX_PORT);
35
+ }
36
+ /**
37
+ * Parse a port from text with no coercion surprises.
38
+ * Returns `undefined` for anything that is not a bare decimal integer.
39
+ */
40
+ function parsePort(text) {
41
+ if (typeof text !== 'string')
42
+ return undefined;
43
+ if (!/^[0-9]{1,5}$/.test(text))
44
+ return undefined;
45
+ const port = Number(text);
46
+ return isValidPort(port) ? port : undefined;
47
+ }
48
+ /** `port: 0` is legal only for a remote bind, where it means "peer assigns". */
49
+ function isValidBindPort(port) {
50
+ return port === 0 || isValidPort(port);
51
+ }
52
+ function inList(list, port) {
53
+ return Array.isArray(list) && list.includes(port);
54
+ }
55
+ /**
56
+ * Evaluate a port against a policy for a given role.
57
+ *
58
+ * Policy semantics, in order:
59
+ * 1. The port must be a valid TCP port (a remote bind may also be 0).
60
+ * 2. A privileged port (< 1024) requires an explicit allowlist entry.
61
+ * 3. If a role-specific allowlist is non-empty, the port must appear in it.
62
+ *
63
+ * An absent or empty policy therefore permits any unprivileged port and no
64
+ * privileged port.
65
+ */
66
+ function checkPort(port, role, policy = {}) {
67
+ const allowZero = role === 'remote-bind';
68
+ if (allowZero ? !isValidBindPort(port) : !isValidPort(port)) {
69
+ return {
70
+ allowed: false,
71
+ reason: `${String(port)} is not a valid TCP port (${exports.MIN_PORT}-${exports.MAX_PORT}${allowZero ? ', or 0 for peer-assigned' : ''})`,
72
+ };
73
+ }
74
+ const value = port;
75
+ if (value !== 0 && value < exports.FIRST_UNPRIVILEGED_PORT) {
76
+ if (!inList(policy.allowedPrivilegedPorts, value)) {
77
+ return {
78
+ allowed: false,
79
+ reason: `privileged port ${value} requires an explicit allowedPrivilegedPorts entry`,
80
+ };
81
+ }
82
+ }
83
+ const roleList = role === 'local-listen'
84
+ ? policy.allowedLocalListenPorts
85
+ : role === 'remote-target'
86
+ ? policy.allowedRemoteTargetPorts
87
+ : undefined;
88
+ if (roleList && roleList.length > 0 && !roleList.includes(value)) {
89
+ return {
90
+ allowed: false,
91
+ reason: `port ${value} is not in the ${role} allowlist`,
92
+ };
93
+ }
94
+ return { allowed: true };
95
+ }
96
+ /** Throwing form of {@link checkPort}. */
97
+ function assertPortAllowed(port, role, policy = {}, forwardId) {
98
+ const result = checkPort(port, role, policy);
99
+ if (!result.allowed) {
100
+ throw new errors_1.TunnelError('PORT_NOT_PERMITTED', `Rejected ${role} port: ${result.reason}`, {
101
+ forwardId,
102
+ });
103
+ }
104
+ return port;
105
+ }
106
+ /** A hostname or IP literal, syntactically. Does not resolve anything. */
107
+ function isValidHost(host) {
108
+ if (typeof host !== 'string')
109
+ return false;
110
+ const value = host.trim();
111
+ if (value.length === 0 || value.length > 255)
112
+ return false;
113
+ if (value !== host)
114
+ return false;
115
+ if (value.includes('/') || value.includes(' '))
116
+ return false;
117
+ // Bracketed or bare IPv6
118
+ if (value.startsWith('[') && value.endsWith(']'))
119
+ return value.length > 2;
120
+ if (/^[0-9a-fA-F:]+$/.test(value) && value.includes(':'))
121
+ return true;
122
+ return /^[A-Za-z0-9_]([A-Za-z0-9_-]*[A-Za-z0-9_])?(\.[A-Za-z0-9_]([A-Za-z0-9_-]*[A-Za-z0-9_])?)*$/.test(value);
123
+ }
124
+ function assertHostAllowed(host, policy = {}, forwardId) {
125
+ if (!isValidHost(host)) {
126
+ throw new errors_1.TunnelError('INVALID_FORWARD_SPEC', `Invalid host: ${JSON.stringify(host)}`, {
127
+ forwardId,
128
+ });
129
+ }
130
+ const allowed = policy.allowedRemoteHosts;
131
+ if (allowed && allowed.length > 0 && !allowed.includes(host)) {
132
+ throw new errors_1.TunnelError('HOST_NOT_PERMITTED', `Host ${host} is not in allowedRemoteHosts`, {
133
+ forwardId,
134
+ });
135
+ }
136
+ return host;
137
+ }
138
+ //# sourceMappingURL=port-policy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"port-policy.js","sourceRoot":"","sources":["../src/port-policy.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAmBH,kCAOC;AAMD,8BAKC;AAGD,0CAEC;AAsBD,8BAwCC;AAGD,8CAaC;AAGD,kCAYC;AAED,8CAiBC;AAxJD,qCAAuC;AAG1B,QAAA,QAAQ,GAAG,CAAC,CAAC;AACb,QAAA,QAAQ,GAAG,KAAK,CAAC;AACjB,QAAA,uBAAuB,GAAG,IAAI,CAAC;AAK5C;;;;;;GAMG;AACH,SAAgB,WAAW,CAAC,IAAa;IACvC,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC;QACtB,IAAI,IAAI,gBAAQ;QAChB,IAAI,IAAI,gBAAQ,CACjB,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,SAAgB,SAAS,CAAC,IAAY;IACpC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IAC/C,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC1B,OAAO,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;AAC9C,CAAC;AAED,gFAAgF;AAChF,SAAgB,eAAe,CAAC,IAAa;IAC3C,OAAO,IAAI,KAAK,CAAC,IAAI,WAAW,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,MAAM,CAAC,IAAmC,EAAE,IAAY;IAC/D,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;AACpD,CAAC;AAOD;;;;;;;;;;GAUG;AACH,SAAgB,SAAS,CACvB,IAAa,EACb,IAAc,EACd,SAAqB,EAAE;IAEvB,MAAM,SAAS,GAAG,IAAI,KAAK,aAAa,CAAC;IAEzC,IAAI,SAAS,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5D,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,CAAC,6BAA6B,gBAAQ,IAAI,gBAAQ,GAAG,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,EAAE,GAAG;SAC1H,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,IAAc,CAAC;IAE7B,IAAI,KAAK,KAAK,CAAC,IAAI,KAAK,GAAG,+BAAuB,EAAE,CAAC;QACnD,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,KAAK,CAAC,EAAE,CAAC;YAClD,OAAO;gBACL,OAAO,EAAE,KAAK;gBACd,MAAM,EAAE,mBAAmB,KAAK,oDAAoD;aACrF,CAAC;QACJ,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GACZ,IAAI,KAAK,cAAc;QACrB,CAAC,CAAC,MAAM,CAAC,uBAAuB;QAChC,CAAC,CAAC,IAAI,KAAK,eAAe;YACxB,CAAC,CAAC,MAAM,CAAC,wBAAwB;YACjC,CAAC,CAAC,SAAS,CAAC;IAElB,IAAI,QAAQ,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACjE,OAAO;YACL,OAAO,EAAE,KAAK;YACd,MAAM,EAAE,QAAQ,KAAK,kBAAkB,IAAI,YAAY;SACxD,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,0CAA0C;AAC1C,SAAgB,iBAAiB,CAC/B,IAAa,EACb,IAAc,EACd,SAAqB,EAAE,EACvB,SAAkB;IAElB,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IAC7C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,oBAAW,CAAC,oBAAoB,EAAE,YAAY,IAAI,UAAU,MAAM,CAAC,MAAM,EAAE,EAAE;YACrF,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAc,CAAC;AACxB,CAAC;AAED,0EAA0E;AAC1E,SAAgB,WAAW,CAAC,IAAa;IACvC,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC3C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,GAAG;QAAE,OAAO,KAAK,CAAC;IAC3D,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IACjC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7D,yBAAyB;IACzB,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IAC1E,IAAI,iBAAiB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACtE,OAAO,2FAA2F,CAAC,IAAI,CACrG,KAAK,CACN,CAAC;AACJ,CAAC;AAED,SAAgB,iBAAiB,CAC/B,IAAa,EACb,SAAqB,EAAE,EACvB,SAAkB;IAElB,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,oBAAW,CAAC,sBAAsB,EAAE,iBAAiB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,EAAE;YACrF,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IACD,MAAM,OAAO,GAAG,MAAM,CAAC,kBAAkB,CAAC;IAC1C,IAAI,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7D,MAAM,IAAI,oBAAW,CAAC,oBAAoB,EAAE,QAAQ,IAAI,+BAA+B,EAAE;YACvF,SAAS;SACV,CAAC,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}