@push.rocks/smartproxy 19.5.2 → 19.5.4

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.
@@ -0,0 +1,86 @@
1
+ # SmartProxy Module Problems
2
+
3
+ Based on test analysis, the following potential issues have been identified in the SmartProxy module:
4
+
5
+ ## 1. HttpProxy Route Configuration Issue
6
+ **Location**: `ts/proxies/http-proxy/http-proxy.ts:380`
7
+ **Problem**: The HttpProxy is trying to read the 'type' property of an undefined object when updating route configurations.
8
+ **Evidence**: `test.http-forwarding-fix.ts` fails with:
9
+ ```
10
+ TypeError: Cannot read properties of undefined (reading 'type')
11
+ at HttpProxy.updateRouteConfigs (/mnt/data/lossless/push.rocks/smartproxy/ts/proxies/http-proxy/http-proxy.ts:380:24)
12
+ ```
13
+ **Impact**: Routes with `useHttpProxy` configuration may not work properly.
14
+
15
+ ## 2. Connection Forwarding Issues
16
+ **Problem**: Basic TCP forwarding appears to not be working correctly after the simplification to just 'forward' and 'socket-handler' action types.
17
+ **Evidence**: Multiple forwarding tests timeout waiting for data to be forwarded:
18
+ - `test.forwarding-fix-verification.ts` - times out waiting for forwarded data
19
+ - `test.connection-forwarding.ts` - times out on SNI-based forwarding
20
+ **Impact**: The 'forward' action type may not be properly forwarding connections to target servers.
21
+
22
+ ## 3. Missing Certificate Manager Methods
23
+ **Problem**: Tests expect `provisionAllCertificates` method on certificate manager but it may not exist or may not be properly initialized.
24
+ **Evidence**: Multiple tests fail with "this.certManager.provisionAllCertificates is not a function"
25
+ **Impact**: Certificate provisioning may not work as expected.
26
+
27
+ ## 4. Route Update Mechanism
28
+ **Problem**: The route update mechanism may have issues preserving certificate manager callbacks and other state.
29
+ **Evidence**: Tests specifically designed to verify callback preservation after route updates.
30
+ **Impact**: Dynamic route updates might break certificate management functionality.
31
+
32
+ ## 5. Route-Specific Security Not Fully Implemented
33
+ **Problem**: While the route definitions support security configurations (ipAllowList, ipBlockList, authentication), these are not being enforced at the route level.
34
+ **Evidence**:
35
+ - SecurityManager has methods like `isIPAuthorized` for route-specific security
36
+ - Route connection handler only checks global IP validation, not route-specific security rules
37
+ - No evidence of route.action.security being checked when handling connections
38
+ **Impact**: Route-specific security rules defined in configuration are not enforced, potentially allowing unauthorized access.
39
+ **Status**: ✅ FIXED - Route-specific IP allow/block lists are now enforced when a route is matched. Authentication is logged as not enforceable for non-terminated connections.
40
+ **Additional Fix**: Removed security checks from route matching logic - security is now properly enforced AFTER a route is matched, not during matching.
41
+
42
+ ## 6. Security Property Location Consolidation
43
+ **Problem**: Security was defined in two places - route.security and route.action.security - causing confusion.
44
+ **Status**: ✅ FIXED - Consolidated to only route.security. Removed action.security from types and updated all references.
45
+
46
+ ## Recommendations
47
+
48
+ 1. **Verify Forward Action Implementation**: Check that the 'forward' action type properly establishes bidirectional data flow between client and target server. ✅ FIXED - Basic forwarding now works correctly.
49
+
50
+ 2. **Fix HttpProxy Route Handling**: Ensure that route objects passed to HttpProxy.updateRouteConfigs have the expected structure with all required properties. ✅ FIXED - Routes now preserve their structure.
51
+
52
+ 3. **Review Certificate Manager API**: Ensure all expected methods exist and are properly documented.
53
+
54
+ 4. **Add Integration Tests**: Many unit tests are testing internal implementation details. Consider adding more integration tests that test the public API.
55
+
56
+ 5. **Implement Route-Specific Security**: Add security checks when a route is matched to enforce route-specific IP allow/block lists and authentication rules. ✅ FIXED - IP allow/block lists are now enforced at the route level.
57
+
58
+ 6. **Fix TLS Detection Logic**: The connection handler was treating all connections as TLS. This has been partially fixed but needs proper testing for all TLS modes.
59
+
60
+ ## 7. HTTP Domain Matching Issue
61
+ **Problem**: Routes with domain restrictions fail to match HTTP connections because domain information is only available after HTTP headers are received, but route matching happens immediately upon connection.
62
+ **Evidence**:
63
+ - `test.http-port8080-forwarding.ts` - "No route found for connection on port 8080" despite having a matching route
64
+ - HTTP connections provide domain info via the Host header, which arrives after the initial TCP connection
65
+ - Route matching in `handleInitialData` happens before HTTP headers are parsed
66
+ **Impact**: HTTP routes with domain restrictions cannot be matched, forcing users to remove domain restrictions for HTTP routes.
67
+ **Root Cause**: For non-TLS connections, SmartProxy attempts to match routes immediately, but the domain information needed for matching is only available after parsing HTTP headers.
68
+ **Status**: ✅ FIXED - Added skipDomainCheck parameter to route matching for HTTP proxy ports. When a port is configured with useHttpProxy and the connection is not TLS, domain validation is skipped at the initial route matching stage, allowing the HttpProxy to handle domain-based routing after headers are received.
69
+
70
+ ## 8. HttpProxy Plain HTTP Forwarding Issue
71
+ **Problem**: HttpProxy is an HTTPS server but SmartProxy forwards plain HTTP connections to it via `useHttpProxy` configuration.
72
+ **Evidence**:
73
+ - `test.http-port8080-forwarding.ts` - Connection immediately closed after forwarding to HttpProxy
74
+ - HttpProxy is created with `http2.createSecureServer` expecting TLS connections
75
+ - SmartProxy forwards raw HTTP data to HttpProxy's HTTPS port
76
+ **Impact**: Plain HTTP connections cannot be handled by HttpProxy, despite `useHttpProxy` configuration suggesting this should work.
77
+ **Root Cause**: Design mismatch - HttpProxy is designed for HTTPS/TLS termination, not plain HTTP forwarding.
78
+ **Status**: Documented. The `useHttpProxy` configuration should only be used for ports that receive TLS connections requiring termination. For plain HTTP forwarding, use direct forwarding without HttpProxy.
79
+
80
+ ## 9. Route Security Configuration Location Issue
81
+ **Problem**: Tests were placing security configuration in `route.action.security` instead of `route.security`.
82
+ **Evidence**:
83
+ - `test.route-security.ts` - IP block list test failing because security was in wrong location
84
+ - IRouteConfig interface defines security at route level, not inside action
85
+ **Impact**: Security rules defined in action.security were ignored, causing tests to fail.
86
+ **Status**: ✅ FIXED - Updated tests to place security configuration at the correct location (route.security).
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '19.5.2',
6
+ version: '19.5.3',
7
7
  description: 'A powerful proxy package with unified route-based configuration for high traffic management. Features include SSL/TLS support, flexible routing patterns, WebSocket handling, advanced security options, and automatic ACME certificate management.'
8
8
  }
@@ -153,7 +153,7 @@ export function convertLegacyConfigToRouteConfig(
153
153
 
154
154
  // Add authentication if present
155
155
  if (legacyConfig.authentication) {
156
- routeConfig.action.security = {
156
+ routeConfig.security = {
157
157
  authentication: {
158
158
  type: 'basic',
159
159
  credentials: [{
@@ -73,10 +73,7 @@ export class HttpProxyBridge {
73
73
  }
74
74
 
75
75
  return {
76
- domain,
77
- target: route.action.target,
78
- tls: route.action.tls,
79
- security: route.action.security,
76
+ ...route, // Keep the original route structure
80
77
  match: {
81
78
  ...route.match,
82
79
  domains: domain // Ensure domains is always set for HttpProxy
@@ -233,9 +233,6 @@ export interface IRouteAction {
233
233
  // Load balancing options
234
234
  loadBalancing?: IRouteLoadBalancing;
235
235
 
236
- // Security options
237
- security?: IRouteSecurity;
238
-
239
236
  // Advanced options
240
237
  advanced?: IRouteAdvanced;
241
238
 
@@ -175,13 +175,12 @@ export class NFTablesManager {
175
175
  };
176
176
 
177
177
  // Add security-related options
178
- const security = action.security || route.security;
179
- if (security?.ipAllowList?.length) {
180
- options.ipAllowList = security.ipAllowList;
178
+ if (route.security?.ipAllowList?.length) {
179
+ options.ipAllowList = route.security.ipAllowList;
181
180
  }
182
181
 
183
- if (security?.ipBlockList?.length) {
184
- options.ipBlockList = security.ipBlockList;
182
+ if (route.security?.ipBlockList?.length) {
183
+ options.ipBlockList = route.security.ipBlockList;
185
184
  }
186
185
 
187
186
  // Add QoS options
@@ -146,18 +146,42 @@ export class RouteConnectionHandler {
146
146
  );
147
147
  }
148
148
 
149
- // Start TLS SNI handling
150
- this.handleTlsConnection(socket, record);
149
+ // Handle the connection - wait for initial data to determine if it's TLS
150
+ this.handleInitialData(socket, record);
151
151
  }
152
152
 
153
153
  /**
154
- * Handle a connection and wait for TLS handshake for SNI extraction if needed
154
+ * Handle initial data from a connection to determine routing
155
155
  */
156
- private handleTlsConnection(socket: plugins.net.Socket, record: IConnectionRecord): void {
156
+ private handleInitialData(socket: plugins.net.Socket, record: IConnectionRecord): void {
157
157
  const connectionId = record.id;
158
158
  const localPort = record.localPort;
159
159
  let initialDataReceived = false;
160
160
 
161
+ // Check if any routes on this port require TLS handling
162
+ const allRoutes = this.routeManager.getAllRoutes();
163
+ const needsTlsHandling = allRoutes.some(route => {
164
+ // Check if route matches this port
165
+ const matchesPort = this.routeManager.getRoutesForPort(localPort).includes(route);
166
+
167
+ return matchesPort &&
168
+ route.action.type === 'forward' &&
169
+ route.action.tls &&
170
+ (route.action.tls.mode === 'terminate' ||
171
+ route.action.tls.mode === 'passthrough');
172
+ });
173
+
174
+ // If no routes require TLS handling and it's not port 443, route immediately
175
+ if (!needsTlsHandling && localPort !== 443) {
176
+ // Set up error handler
177
+ socket.on('error', this.connectionManager.handleError('incoming', record));
178
+
179
+ // Route immediately for non-TLS connections
180
+ this.routeConnection(socket, record, '', undefined);
181
+ return;
182
+ }
183
+
184
+ // Otherwise, wait for initial data to check if it's TLS
161
185
  // Set an initial timeout for handshake data
162
186
  let initialTimeout: NodeJS.Timeout | null = setTimeout(() => {
163
187
  if (!initialDataReceived) {
@@ -296,6 +320,12 @@ export class RouteConnectionHandler {
296
320
  const localPort = record.localPort;
297
321
  const remoteIP = record.remoteIP;
298
322
 
323
+ // Check if this is an HTTP proxy port
324
+ const isHttpProxyPort = this.settings.useHttpProxy?.includes(localPort);
325
+
326
+ // For HTTP proxy ports without TLS, skip domain check since domain info comes from HTTP headers
327
+ const skipDomainCheck = isHttpProxyPort && !record.isTLS;
328
+
299
329
  // Find matching route
300
330
  const routeMatch = this.routeManager.findMatchingRoute({
301
331
  port: localPort,
@@ -303,6 +333,7 @@ export class RouteConnectionHandler {
303
333
  clientIp: remoteIP,
304
334
  path: undefined, // We don't have path info at this point
305
335
  tlsVersion: undefined, // We don't extract TLS version yet
336
+ skipDomainCheck: skipDomainCheck,
306
337
  });
307
338
 
308
339
  if (!routeMatch) {
@@ -382,6 +413,56 @@ export class RouteConnectionHandler {
382
413
  });
383
414
  }
384
415
 
416
+ // Apply route-specific security checks
417
+ if (route.security) {
418
+ // Check IP allow/block lists
419
+ if (route.security.ipAllowList || route.security.ipBlockList) {
420
+ const isIPAllowed = this.securityManager.isIPAuthorized(
421
+ remoteIP,
422
+ route.security.ipAllowList || [],
423
+ route.security.ipBlockList || []
424
+ );
425
+
426
+ if (!isIPAllowed) {
427
+ logger.log('warn', `IP ${remoteIP} blocked by route security for route ${route.name || 'unnamed'} (connection: ${connectionId})`, {
428
+ connectionId,
429
+ remoteIP,
430
+ routeName: route.name || 'unnamed',
431
+ component: 'route-handler'
432
+ });
433
+ socket.end();
434
+ this.connectionManager.cleanupConnection(record, 'route_ip_blocked');
435
+ return;
436
+ }
437
+ }
438
+
439
+ // Check max connections per route
440
+ if (route.security.maxConnections !== undefined) {
441
+ // TODO: Implement per-route connection tracking
442
+ // For now, log that this feature is not yet implemented
443
+ if (this.settings.enableDetailedLogging) {
444
+ logger.log('warn', `Route ${route.name} has maxConnections=${route.security.maxConnections} configured but per-route connection limits are not yet implemented`, {
445
+ connectionId,
446
+ routeName: route.name,
447
+ component: 'route-handler'
448
+ });
449
+ }
450
+ }
451
+
452
+ // Check authentication requirements
453
+ if (route.security.authentication || route.security.basicAuth || route.security.jwtAuth) {
454
+ // Authentication checks would typically happen at the HTTP layer
455
+ // For non-HTTP connections or passthrough, we can't enforce authentication
456
+ if (route.action.type === 'forward' && route.action.tls?.mode !== 'terminate') {
457
+ logger.log('warn', `Route ${route.name} has authentication configured but it cannot be enforced for non-terminated connections`, {
458
+ connectionId,
459
+ routeName: route.name,
460
+ tlsMode: route.action.tls?.mode || 'none',
461
+ component: 'route-handler'
462
+ });
463
+ }
464
+ }
465
+ }
385
466
 
386
467
  // Handle the route based on its action type
387
468
  switch (route.action.type) {
@@ -634,6 +715,18 @@ export class RouteConnectionHandler {
634
715
  // No TLS settings - check if this port should use HttpProxy
635
716
  const isHttpProxyPort = this.settings.useHttpProxy?.includes(record.localPort);
636
717
 
718
+ // Debug logging
719
+ if (this.settings.enableDetailedLogging) {
720
+ logger.log('debug', `Checking HttpProxy forwarding: port=${record.localPort}, useHttpProxy=${JSON.stringify(this.settings.useHttpProxy)}, isHttpProxyPort=${isHttpProxyPort}, hasHttpProxy=${!!this.httpProxyBridge.getHttpProxy()}`, {
721
+ connectionId,
722
+ localPort: record.localPort,
723
+ useHttpProxy: this.settings.useHttpProxy,
724
+ isHttpProxyPort,
725
+ hasHttpProxy: !!this.httpProxyBridge.getHttpProxy(),
726
+ component: 'route-handler'
727
+ });
728
+ }
729
+
637
730
  if (isHttpProxyPort && this.httpProxyBridge.getHttpProxy()) {
638
731
  // Forward non-TLS connections to HttpProxy if configured
639
732
  if (this.settings.enableDetailedLogging) {
@@ -211,9 +211,10 @@ export class RouteManager extends plugins.EventEmitter {
211
211
 
212
212
  /**
213
213
  * Check if a client IP is allowed by a route's security settings
214
+ * @deprecated Security is now checked in route-connection-handler.ts after route matching
214
215
  */
215
216
  private isClientIpAllowed(route: IRouteConfig, clientIp: string): boolean {
216
- const security = route.action.security;
217
+ const security = route.security;
217
218
 
218
219
  if (!security) {
219
220
  return true; // No security settings means allowed
@@ -330,8 +331,9 @@ export class RouteManager extends plugins.EventEmitter {
330
331
  clientIp: string;
331
332
  path?: string;
332
333
  tlsVersion?: string;
334
+ skipDomainCheck?: boolean;
333
335
  }): IRouteMatchResult | null {
334
- const { port, domain, clientIp, path, tlsVersion } = options;
336
+ const { port, domain, clientIp, path, tlsVersion, skipDomainCheck } = options;
335
337
 
336
338
  // Get all routes for this port
337
339
  const routesForPort = this.getRoutesForPort(port);
@@ -340,7 +342,7 @@ export class RouteManager extends plugins.EventEmitter {
340
342
  for (const route of routesForPort) {
341
343
  // Check domain match
342
344
  // If the route has domain restrictions and we have a domain to check
343
- if (route.match.domains) {
345
+ if (route.match.domains && !skipDomainCheck) {
344
346
  // If no domain was provided (non-TLS or no SNI), this route doesn't match
345
347
  if (!domain) {
346
348
  continue;
@@ -351,6 +353,7 @@ export class RouteManager extends plugins.EventEmitter {
351
353
  }
352
354
  }
353
355
  // If route has no domain restrictions, it matches all domains
356
+ // If skipDomainCheck is true, we skip domain validation for HTTP connections
354
357
 
355
358
  // Check path match if specified in both route and request
356
359
  if (path && route.match.path) {
@@ -371,12 +374,8 @@ export class RouteManager extends plugins.EventEmitter {
371
374
  continue;
372
375
  }
373
376
 
374
- // Check security settings
375
- if (!this.isClientIpAllowed(route, clientIp)) {
376
- continue;
377
- }
378
-
379
377
  // All checks passed, this route matches
378
+ // NOTE: Security is checked AFTER route matching in route-connection-handler.ts
380
379
  return { route };
381
380
  }
382
381
 
@@ -625,14 +625,6 @@ export function createNfTablesRoute(
625
625
  }
626
626
  };
627
627
 
628
- // Add security if allowed or blocked IPs are specified
629
- if (options.ipAllowList?.length || options.ipBlockList?.length) {
630
- action.security = {
631
- ipAllowList: options.ipAllowList,
632
- ipBlockList: options.ipBlockList
633
- };
634
- }
635
-
636
628
  // Add TLS options if needed
637
629
  if (options.useTls) {
638
630
  action.tls = {
@@ -641,11 +633,21 @@ export function createNfTablesRoute(
641
633
  }
642
634
 
643
635
  // Create the route config
644
- return {
636
+ const routeConfig: IRouteConfig = {
645
637
  name,
646
638
  match,
647
639
  action
648
640
  };
641
+
642
+ // Add security if allowed or blocked IPs are specified
643
+ if (options.ipAllowList?.length || options.ipBlockList?.length) {
644
+ routeConfig.security = {
645
+ ipAllowList: options.ipAllowList,
646
+ ipBlockList: options.ipBlockList
647
+ };
648
+ }
649
+
650
+ return routeConfig;
649
651
  }
650
652
 
651
653
  /**