@push.rocks/smartproxy 27.12.8 → 27.13.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 (33) hide show
  1. package/changelog.md +7 -1
  2. package/dist_rust/rustproxy_linux_amd64 +0 -0
  3. package/dist_rust/rustproxy_linux_arm64 +0 -0
  4. package/dist_ts/00_commitinfo_data.js +1 -1
  5. package/dist_ts/index.d.ts +1 -1
  6. package/dist_ts/plugins.d.ts +4 -2
  7. package/dist_ts/plugins.js +5 -3
  8. package/dist_ts/proxies/smart-proxy/challenge-provider-relay-server.d.ts +31 -0
  9. package/dist_ts/proxies/smart-proxy/challenge-provider-relay-server.js +297 -0
  10. package/dist_ts/proxies/smart-proxy/models/index.d.ts +1 -1
  11. package/dist_ts/proxies/smart-proxy/models/interfaces.d.ts +13 -0
  12. package/dist_ts/proxies/smart-proxy/models/route-types.d.ts +2 -0
  13. package/dist_ts/proxies/smart-proxy/models/route-types.js +1 -1
  14. package/dist_ts/proxies/smart-proxy/models/rust-types.d.ts +4 -1
  15. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.d.ts +2 -1
  16. package/dist_ts/proxies/smart-proxy/rust-proxy-bridge.js +4 -1
  17. package/dist_ts/proxies/smart-proxy/smart-proxy.d.ts +19 -0
  18. package/dist_ts/proxies/smart-proxy/smart-proxy.js +339 -89
  19. package/dist_ts/proxies/smart-proxy/utils/rust-config.d.ts +3 -2
  20. package/dist_ts/proxies/smart-proxy/utils/rust-config.js +19 -2
  21. package/package.json +2 -1
  22. package/readme.md +64 -1
  23. package/ts/00_commitinfo_data.ts +1 -1
  24. package/ts/index.ts +1 -1
  25. package/ts/plugins.ts +4 -1
  26. package/ts/proxies/smart-proxy/challenge-provider-relay-server.ts +338 -0
  27. package/ts/proxies/smart-proxy/models/index.ts +1 -1
  28. package/ts/proxies/smart-proxy/models/interfaces.ts +16 -0
  29. package/ts/proxies/smart-proxy/models/route-types.ts +3 -0
  30. package/ts/proxies/smart-proxy/models/rust-types.ts +7 -1
  31. package/ts/proxies/smart-proxy/rust-proxy-bridge.ts +6 -0
  32. package/ts/proxies/smart-proxy/smart-proxy.ts +400 -94
  33. package/ts/proxies/smart-proxy/utils/rust-config.ts +21 -0
@@ -1,6 +1,6 @@
1
1
  import type { IAcmeOptions, ISmartProxyOptions } from '../models/interfaces.js';
2
2
  import type { IRouteAction, IRouteConfig, IRouteMatch, IRouteTarget, ITargetMatch } from '../models/route-types.js';
3
- import type { IRustProxyOptions, IRustRouteAction, IRustRouteConfig, IRustRouteMatch, IRustRouteTarget, IRustTargetMatch, TRustHeaderMatchers } from '../models/rust-types.js';
3
+ import type { IRustProxyOptions, IRustChallengeOptions, IRustRouteAction, IRustRouteConfig, IRustRouteMatch, IRustRouteTarget, IRustTargetMatch, TRustHeaderMatchers } from '../models/rust-types.js';
4
4
  export declare function serializeHeaderMatchValue(value: string | RegExp): string;
5
5
  export declare function serializeHeaderMatchers(headers?: Record<string, string | RegExp>): TRustHeaderMatchers | undefined;
6
6
  export declare function serializeTargetMatchForRust(match?: ITargetMatch): IRustTargetMatch | undefined;
@@ -8,4 +8,5 @@ export declare function serializeRouteMatchForRust(match: IRouteMatch): IRustRou
8
8
  export declare function serializeRouteTargetForRust(target: IRouteTarget): IRustRouteTarget;
9
9
  export declare function serializeRouteActionForRust(action: IRouteAction): IRustRouteAction;
10
10
  export declare function serializeRouteForRust(route: IRouteConfig): IRustRouteConfig;
11
- export declare function buildRustProxyOptions(settings: ISmartProxyOptions, routes: IRustRouteConfig[], acmeOverride?: IAcmeOptions): IRustProxyOptions;
11
+ export declare function buildChallengeOptionsForRust(settings: ISmartProxyOptions): IRustChallengeOptions | undefined;
12
+ export declare function buildRustProxyOptions(settings: ISmartProxyOptions, routes: IRustRouteConfig[], acmeOverride?: IAcmeOptions, challengeOverride?: IRustChallengeOptions): IRustProxyOptions;
@@ -96,7 +96,23 @@ function serializeDefaultsForRust(defaults) {
96
96
  preserveSourceIp: preserveSourceIP,
97
97
  };
98
98
  }
99
- export function buildRustProxyOptions(settings, routes, acmeOverride) {
99
+ export function buildChallengeOptionsForRust(settings) {
100
+ const hasChallengeRoutes = settings.routes.some((route) => Boolean(route.security?.challenge));
101
+ if (!hasChallengeRoutes) {
102
+ return undefined;
103
+ }
104
+ const runtimeOptions = settings.challenge || {};
105
+ return {
106
+ cookieSigningKey: runtimeOptions.cookieSigningKey || '',
107
+ pendingCookieName: runtimeOptions.pendingCookieName || '__smartproxy_challenge_pending',
108
+ clearanceCookieName: runtimeOptions.clearanceCookieName || '__smartproxy_clearance',
109
+ reservedPathPrefix: runtimeOptions.reservedPathPrefix || '/.well-known/smartproxy-challenge',
110
+ relaySocketPath: runtimeOptions.relaySocketPath || '',
111
+ relayTimeoutMs: runtimeOptions.relayTimeoutMs ?? 5_000,
112
+ pendingTtlSeconds: runtimeOptions.pendingTtlSeconds ?? 300,
113
+ };
114
+ }
115
+ export function buildRustProxyOptions(settings, routes, acmeOverride, challengeOverride) {
100
116
  const acme = acmeOverride !== undefined ? acmeOverride : settings.acme;
101
117
  return {
102
118
  routes,
@@ -127,7 +143,8 @@ export function buildRustProxyOptions(settings, routes, acmeOverride) {
127
143
  extendedKeepAliveLifetime: settings.extendedKeepAliveLifetime,
128
144
  metrics: settings.metrics,
129
145
  securityPolicy: settings.securityPolicy,
146
+ challenge: challengeOverride || buildChallengeOptionsForRust(settings),
130
147
  acme: serializeAcmeForRust(acme),
131
148
  };
132
149
  }
133
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1jb25maWcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3V0aWxzL3J1c3QtY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWVBLE1BQU0scUJBQXFCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztBQUVqRSxNQUFNLFVBQVUseUJBQXlCLENBQUMsS0FBc0I7SUFDOUQsSUFBSSxPQUFPLEtBQUssS0FBSyxRQUFRLEVBQUUsQ0FBQztRQUM5QixPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxNQUFNLGdCQUFnQixHQUFHLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFDLHFCQUFxQixDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQzdHLElBQUksZ0JBQWdCLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ2hDLE1BQU0sSUFBSSxLQUFLLENBQ2IsZ0VBQWdFLGdCQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUM5RixDQUFDO0lBQ0osQ0FBQztJQUVELE9BQU8sSUFBSSxLQUFLLENBQUMsTUFBTSxJQUFJLEtBQUssQ0FBQyxLQUFLLEVBQUUsQ0FBQztBQUMzQyxDQUFDO0FBRUQsTUFBTSxVQUFVLHVCQUF1QixDQUFDLE9BQXlDO0lBQy9FLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNiLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRCxPQUFPLE1BQU0sQ0FBQyxXQUFXLENBQ3ZCLE1BQU0sQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxD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
150
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1jb25maWcuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9wcm94aWVzL3NtYXJ0LXByb3h5L3V0aWxzL3J1c3QtY29uZmlnLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWdCQSxNQUFNLHFCQUFxQixHQUFHLElBQUksR0FBRyxDQUFDLENBQUMsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFFakUsTUFBTSxVQUFVLHlCQUF5QixDQUFDLEtBQXNCO0lBQzlELElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDOUIsT0FBTyxLQUFLLENBQUM7SUFDZixDQUFDO0lBRUQsTUFBTSxnQkFBZ0IsR0FBRyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxDQUFDLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxDQUFDLElBQUksRUFBRSxFQUFFLENBQUMsQ0FBQyxxQkFBcUIsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM3RyxJQUFJLGdCQUFnQixDQUFDLE1BQU0sR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUNoQyxNQUFNLElBQUksS0FBSyxDQUNiLGdFQUFnRSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FDOUYsQ0FBQztJQUNKLENBQUM7SUFFRCxPQUFPLElBQUksS0FBSyxDQUFDLE1BQU0sSUFBSSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUM7QUFDM0MsQ0FBQztBQUVELE1BQU0sVUFBVSx1QkFBdUIsQ0FBQyxPQUF5QztJQUMvRSxJQUFJLENBQUMsT0FBTyxFQUFFLENBQUM7UUFDYixPQUFPLFNBQVMsQ0FBQztJQUNuQixDQUFDO0lBRUQsT0FBTyxNQUFNLENBQUMsV0FBVyxDQUN2QixNQUFNLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxDQUFDLEdBQUcs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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@push.rocks/smartproxy",
3
- "version": "27.12.8",
3
+ "version": "27.13.0",
4
4
  "private": false,
5
5
  "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.",
6
6
  "main": "dist_ts/index.js",
@@ -21,6 +21,7 @@
21
21
  "ws": "^8.20.1"
22
22
  },
23
23
  "dependencies": {
24
+ "@push.rocks/smartchallenge": "^1.1.0",
24
25
  "@push.rocks/smartcrypto": "^2.0.4",
25
26
  "@push.rocks/smartlog": "^3.2.2",
26
27
  "@push.rocks/smartnftables": "^1.2.0",
package/readme.md CHANGED
@@ -9,9 +9,11 @@ For reporting bugs, issues, or security vulnerabilities, please visit [community
9
9
  ## 📦 Installation
10
10
 
11
11
  ```bash
12
- pnpm add @push.rocks/smartproxy
12
+ pnpm add @push.rocks/smartproxy @push.rocks/smartchallenge
13
13
  ```
14
14
 
15
+ `@push.rocks/smartchallenge` is only required when you use the route challenge example below.
16
+
15
17
  ## 🎯 What is SmartProxy?
16
18
 
17
19
  SmartProxy is a production-ready proxy solution that takes the complexity out of traffic management. Under the hood, all networking — TCP, UDP, TLS, HTTP reverse proxy, QUIC/HTTP3, connection tracking, security enforcement, and NFTables — is handled by a **Rust engine** for maximum performance, while you configure everything through a clean TypeScript API with full type safety.
@@ -556,6 +558,55 @@ Comprehensive per-route security options:
556
558
 
557
559
  Security options are configured directly on each route's `security` property — no separate helpers needed.
558
560
 
561
+ ### 🛡️ Route Challenge Enforcement
562
+
563
+ SmartProxy can enforce browser-facing challenges through providers from `@push.rocks/smartchallenge`. Route configs store challenge intent only: `providerId`, `challengeType`, `policyRef`, `settings`, `applyTo`, and `clearance`. Runtime wiring such as provider endpoints, socket paths, ports, deployment IDs, secrets, API tokens, and credentials must stay outside `security.challenge`.
564
+
565
+ ```typescript
566
+ import { SmartProxy } from '@push.rocks/smartproxy';
567
+ import { SmartChallengeProvider, WaitChallengeType } from '@push.rocks/smartchallenge';
568
+
569
+ const provider = new SmartChallengeProvider({
570
+ challengeTypes: [new WaitChallengeType()],
571
+ });
572
+
573
+ const proxy = new SmartProxy({
574
+ routes: [{
575
+ name: 'wait-protected-app',
576
+ match: {
577
+ ports: 443,
578
+ domains: 'app.example.com',
579
+ protocol: 'http',
580
+ },
581
+ action: {
582
+ type: 'forward',
583
+ targets: [{ host: 'app-backend', port: 8080 }],
584
+ tls: { mode: 'terminate', certificate: 'auto' },
585
+ },
586
+ security: {
587
+ challenge: {
588
+ providerId: 'smartchallenge',
589
+ challengeType: 'wait',
590
+ settings: { waitSeconds: 5 },
591
+ applyTo: { methods: ['GET'], browserNavigationsOnly: true },
592
+ clearance: { ttlSeconds: 1800, bindToHost: true, bindToRoute: true },
593
+ },
594
+ },
595
+ }],
596
+ challenge: {
597
+ pendingTtlSeconds: 300,
598
+ relayTimeoutMs: 5000,
599
+ },
600
+ });
601
+
602
+ proxy.registerChallengeProvider('smartchallenge', provider);
603
+ await proxy.start();
604
+ ```
605
+
606
+ Challenge flow: the first matching protected request calls the provider `assess` operation, renders a provider response, and stores signed pending state in an HTTP-only cookie. Verification can happen by returning to the protected URL or by submitting to `/.well-known/smartproxy-challenge/*`; reserved verification requests pass query parameters plus JSON or `application/x-www-form-urlencoded` body fields as provider `payload`. Successful verification sets a signed clearance cookie and redirects to the original protected URL stored in the pending cookie.
607
+
608
+ Current challenge routes must set a stable `name` or `id`, use `action.type: 'forward'`, use `match.protocol: 'http'`, and use TCP HTTP-visible traffic. TLS passthrough, UDP/HTTP3, `match.transport: 'all'`, socket-handler routes, dynamic forwarding targets, and nftables forwarding are rejected for challenged routes.
609
+
559
610
  ### 📊 Runtime Management
560
611
 
561
612
  Control your proxy without restarts:
@@ -943,6 +994,7 @@ class SmartProxy extends EventEmitter {
943
994
  // Route Management (atomic, mutex-locked)
944
995
  updateRoutes(routes: IRouteConfig[]): Promise<void>;
945
996
  updateSecurityPolicy(policy: ISmartProxySecurityPolicy): Promise<void>;
997
+ registerChallengeProvider(providerId: string, provider: IChallengeProvider): void;
946
998
 
947
999
  // Port Management
948
1000
  addListeningPort(port: number): Promise<void>;
@@ -1029,6 +1081,17 @@ interface ISmartProxyOptions {
1029
1081
  blockedCidrs?: string[];
1030
1082
  }; // Global ingress block policy
1031
1083
 
1084
+ // Runtime-only challenge enforcement settings
1085
+ challenge?: {
1086
+ cookieSigningKey?: string; // Generated ephemerally when omitted
1087
+ pendingCookieName?: string; // Default: __smartproxy_challenge_pending
1088
+ clearanceCookieName?: string; // Default: __smartproxy_clearance
1089
+ reservedPathPrefix?: string; // Default: /.well-known/smartproxy-challenge
1090
+ relaySocketPath?: string; // Internal Unix socket path; normally generated by SmartProxy
1091
+ relayTimeoutMs?: number; // Provider relay timeout (default: 5000)
1092
+ pendingTtlSeconds?: number; // Pending challenge cookie TTL (default: 300)
1093
+ };
1094
+
1032
1095
  // Keep-alive
1033
1096
  keepAliveTreatment?: 'standard' | 'extended' | 'immortal';
1034
1097
  keepAliveInactivityMultiplier?: number; // (default: 4)
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartproxy',
6
- version: '27.12.8',
6
+ version: '27.13.0',
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
  }
package/ts/index.ts CHANGED
@@ -7,7 +7,7 @@ export { SmartProxy } from './proxies/smart-proxy/index.js';
7
7
  export { SharedRouteManager as RouteManager } from './core/routing/route-manager.js';
8
8
 
9
9
  // Export smart-proxy models
10
- export type { ISmartProxyOptions, ISmartProxySecurityPolicy, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext, TDatagramHandler, IDatagramInfo, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions } from './proxies/smart-proxy/models/index.js';
10
+ export type { ISmartProxyOptions, ISmartProxySecurityPolicy, ISmartProxyChallengeOptions, IConnectionRecord, IRouteConfig, IRouteMatch, IRouteAction, IRouteTls, IRouteContext, TDatagramHandler, IDatagramInfo, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions } from './proxies/smart-proxy/models/index.js';
11
11
  export type { TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent } from './proxies/smart-proxy/models/interfaces.js';
12
12
  export * from './proxies/smart-proxy/utils/index.js';
13
13
 
package/ts/plugins.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  // node native scope
2
2
  import { EventEmitter } from 'node:events';
3
+ import * as crypto from 'node:crypto';
3
4
  import * as fs from 'node:fs';
4
5
  import * as http from 'node:http';
5
6
  import * as net from 'node:net';
@@ -8,7 +9,7 @@ import * as tls from 'node:tls';
8
9
  import * as url from 'node:url';
9
10
  import * as http2 from 'node:http2';
10
11
 
11
- export { EventEmitter, fs, http, net, path, tls, url, http2 };
12
+ export { EventEmitter, crypto, fs, http, net, path, tls, url, http2 };
12
13
 
13
14
  // tsclass scope
14
15
  import * as tsclass from '@tsclass/tsclass';
@@ -19,11 +20,13 @@ export { tsclass };
19
20
  import * as smartcrypto from '@push.rocks/smartcrypto';
20
21
  import * as smartlog from '@push.rocks/smartlog';
21
22
  import * as smartlogDestinationLocal from '@push.rocks/smartlog/destination-local';
23
+ import * as smartchallenge from '@push.rocks/smartchallenge';
22
24
  import * as smartnftables from '@push.rocks/smartnftables';
23
25
  import * as smartrust from '@push.rocks/smartrust';
24
26
 
25
27
  export {
26
28
  smartcrypto,
29
+ smartchallenge,
27
30
  smartlog,
28
31
  smartlogDestinationLocal,
29
32
  smartnftables,
@@ -0,0 +1,338 @@
1
+ import * as plugins from '../../plugins.js';
2
+ import { logger } from '../../core/utils/logger.js';
3
+
4
+ type TChallengeRelayOperation = 'assess' | 'render' | 'verify';
5
+
6
+ interface IChallengeRelayRequest {
7
+ id?: string;
8
+ operation: TChallengeRelayOperation;
9
+ providerId: string;
10
+ request: unknown;
11
+ }
12
+
13
+ interface IChallengeRelayResponse {
14
+ id?: string;
15
+ success: boolean;
16
+ result?: unknown;
17
+ error?: string;
18
+ }
19
+
20
+ const maxChallengeRelayMessageBytes = 1024 * 1024;
21
+ const maxChallengeRelayIdBytes = 256;
22
+
23
+ export interface IChallengeProviderRelayServerOptions {
24
+ providerTimeoutMs?: number;
25
+ maxActiveProviderOperations?: number;
26
+ maxUnsettledProviderOperations?: number;
27
+ }
28
+
29
+ export class ChallengeProviderRelayServer {
30
+ private providerTimeoutMs: number;
31
+ private maxActiveProviderOperations: number;
32
+ private maxUnsettledProviderOperations: number;
33
+ private activeProviderOperations = 0;
34
+ private unsettledProviderOperations = 0;
35
+ private server: plugins.net.Server | null = null;
36
+ private socketPath: string;
37
+ private activeSockets = new Set<plugins.net.Socket>();
38
+ private startPromise?: Promise<void>;
39
+
40
+ constructor(
41
+ private providers: Map<string, plugins.smartchallenge.IChallengeProvider>,
42
+ optionsArg: IChallengeProviderRelayServerOptions = {},
43
+ ) {
44
+ this.socketPath = `/tmp/smartproxy-challenge-relay-${process.pid}-${plugins.crypto.randomBytes(8).toString('hex')}.sock`;
45
+ this.providerTimeoutMs = Math.max(1, optionsArg.providerTimeoutMs ?? 5_000);
46
+ this.maxActiveProviderOperations = Math.max(1, optionsArg.maxActiveProviderOperations ?? 1024);
47
+ this.maxUnsettledProviderOperations = Math.max(1, optionsArg.maxUnsettledProviderOperations ?? this.maxActiveProviderOperations);
48
+ }
49
+
50
+ public getSocketPath(): string {
51
+ return this.socketPath;
52
+ }
53
+
54
+ public async start(): Promise<void> {
55
+ if (this.server) return;
56
+ if (this.startPromise) return this.startPromise;
57
+ this.startPromise = this.startInternal();
58
+ try {
59
+ await this.startPromise;
60
+ } finally {
61
+ this.startPromise = undefined;
62
+ }
63
+ }
64
+
65
+ private async startInternal(): Promise<void> {
66
+ try {
67
+ await plugins.fs.promises.unlink(this.socketPath);
68
+ } catch {
69
+ // Ignore stale socket cleanup failures for missing files.
70
+ }
71
+
72
+ const server = plugins.net.createServer((socket) => {
73
+ this.activeSockets.add(socket);
74
+ socket.once('close', () => this.activeSockets.delete(socket));
75
+ this.handleConnection(socket);
76
+ });
77
+
78
+ server.on('error', (err) => {
79
+ logger.log('error', `ChallengeProviderRelayServer error: ${err.message}`, { component: 'challenge-provider-relay-server' });
80
+ });
81
+
82
+ await new Promise<void>((resolve, reject) => {
83
+ const handleError = (err: Error) => reject(err);
84
+ server.once('error', handleError);
85
+ server.listen(this.socketPath, () => {
86
+ server.off('error', handleError);
87
+ this.server = server;
88
+ logger.log('info', `ChallengeProviderRelayServer listening on ${this.socketPath}`, { component: 'challenge-provider-relay-server' });
89
+ resolve();
90
+ });
91
+ }).catch((err) => {
92
+ server.close();
93
+ throw err;
94
+ });
95
+ }
96
+
97
+ public async stop(): Promise<void> {
98
+ if (this.startPromise && !this.server) {
99
+ await this.startPromise.catch(() => undefined);
100
+ }
101
+
102
+ for (const socket of this.activeSockets) {
103
+ socket.destroy();
104
+ }
105
+ this.activeSockets.clear();
106
+
107
+ const server = this.server;
108
+ if (!server) return;
109
+ this.server = null;
110
+ await new Promise<void>((resolve) => {
111
+ server.close(() => {
112
+ plugins.fs.unlink(this.socketPath, () => resolve());
113
+ });
114
+ });
115
+ }
116
+
117
+ private handleConnection(socket: plugins.net.Socket): void {
118
+ let buffer: Buffer<ArrayBufferLike> = Buffer.alloc(0);
119
+ socket.setTimeout(Math.max(10_000, this.providerTimeoutMs + 1_000));
120
+ socket.on('timeout', () => socket.destroy());
121
+ socket.on('error', (err) => {
122
+ logger.log('warn', `Challenge relay socket error: ${err.message}`, { component: 'challenge-provider-relay-server' });
123
+ });
124
+
125
+ socket.on('data', (chunk: Buffer) => {
126
+ buffer = buffer.length === 0 ? chunk : Buffer.concat([buffer, chunk], buffer.length + chunk.length);
127
+ if (buffer.length > maxChallengeRelayMessageBytes) {
128
+ socket.removeAllListeners('data');
129
+ this.writeResponse(socket, { success: false, error: 'Challenge relay request exceeds maximum size' });
130
+ buffer = Buffer.alloc(0);
131
+ socket.end();
132
+ return;
133
+ }
134
+
135
+ const newlineIndex = buffer.indexOf(0x0a);
136
+ if (newlineIndex === -1) return;
137
+
138
+ const line = buffer.subarray(0, newlineIndex).toString('utf8');
139
+ socket.removeAllListeners('data');
140
+ this.dispatchLine(line)
141
+ .then((response) => this.writeResponse(socket, response))
142
+ .catch((err) => this.writeResponse(socket, { success: false, error: (err as Error).message }))
143
+ .finally(() => socket.end());
144
+ });
145
+ }
146
+
147
+ private async dispatchLine(lineArg: string): Promise<IChallengeRelayResponse> {
148
+ let request: IChallengeRelayRequest;
149
+ try {
150
+ request = JSON.parse(lineArg) as IChallengeRelayRequest;
151
+ } catch {
152
+ return { success: false, error: 'Invalid challenge relay JSON' };
153
+ }
154
+
155
+ const provider = this.providers.get(request.providerId);
156
+ if (!provider) {
157
+ return {
158
+ id: request.id,
159
+ success: false,
160
+ error: `Challenge provider '${request.providerId}' is not registered`,
161
+ };
162
+ }
163
+
164
+ switch (request.operation) {
165
+ case 'assess':
166
+ return { id: request.id, success: true, result: await this.withProviderTimeout(() => provider.assess(request.request as plugins.smartchallenge.IChallengeAssessRequest), 'assess') };
167
+ case 'render':
168
+ return { id: request.id, success: true, result: await this.withProviderTimeout(() => provider.render(request.request as plugins.smartchallenge.IChallengeRenderRequest), 'render') };
169
+ case 'verify':
170
+ return { id: request.id, success: true, result: await this.withProviderTimeout(() => provider.verify(request.request as plugins.smartchallenge.IChallengeVerifyRequest), 'verify') };
171
+ default:
172
+ const operationValue = (request as { operation?: unknown }).operation;
173
+ return { id: request.id, success: false, error: `Unsupported challenge relay operation '${String(operationValue)}'` };
174
+ }
175
+ }
176
+
177
+ private async withProviderTimeout<T>(operationArg: () => Promise<T>, operationNameArg: TChallengeRelayOperation): Promise<T> {
178
+ if (this.activeProviderOperations >= this.maxActiveProviderOperations) {
179
+ throw new Error(`Challenge provider ${operationNameArg} rejected because too many operations are active`);
180
+ }
181
+ if (this.unsettledProviderOperations >= this.maxUnsettledProviderOperations) {
182
+ throw new Error(`Challenge provider ${operationNameArg} rejected because too many timed-out operations are still settling`);
183
+ }
184
+
185
+ let timer: ReturnType<typeof setTimeout> | undefined;
186
+ let activeReleased = false;
187
+ let unsettledReleased = false;
188
+ const releaseActive = () => {
189
+ if (!activeReleased) {
190
+ activeReleased = true;
191
+ this.activeProviderOperations -= 1;
192
+ }
193
+ };
194
+ const releaseUnsettled = () => {
195
+ if (!unsettledReleased) {
196
+ unsettledReleased = true;
197
+ this.unsettledProviderOperations -= 1;
198
+ }
199
+ };
200
+ this.activeProviderOperations += 1;
201
+ this.unsettledProviderOperations += 1;
202
+ const providerPromise = Promise.resolve().then(operationArg);
203
+ const trackedProviderPromise = providerPromise.then(
204
+ (result) => {
205
+ releaseActive();
206
+ releaseUnsettled();
207
+ return result;
208
+ },
209
+ (err) => {
210
+ releaseActive();
211
+ releaseUnsettled();
212
+ throw err;
213
+ },
214
+ );
215
+ trackedProviderPromise.catch(() => undefined);
216
+ try {
217
+ return await Promise.race([
218
+ trackedProviderPromise,
219
+ new Promise<T>((_resolve, reject) => {
220
+ timer = setTimeout(
221
+ () => {
222
+ releaseActive();
223
+ reject(new Error(`Challenge provider ${operationNameArg} timed out after ${this.providerTimeoutMs}ms`));
224
+ },
225
+ this.providerTimeoutMs,
226
+ );
227
+ }),
228
+ ]);
229
+ } finally {
230
+ if (timer) clearTimeout(timer);
231
+ }
232
+ }
233
+
234
+ private writeResponse(socket: plugins.net.Socket, responseArg: IChallengeRelayResponse): void {
235
+ let response = responseArg;
236
+ if (this.estimateJsonByteLength(response, maxChallengeRelayMessageBytes) === undefined) {
237
+ response = this.createErrorResponse(responseArg.id, 'Challenge relay response exceeds maximum size');
238
+ }
239
+
240
+ let serializedResponse: string;
241
+ try {
242
+ serializedResponse = JSON.stringify(response);
243
+ } catch {
244
+ serializedResponse = JSON.stringify(this.createErrorResponse(responseArg.id, 'Challenge relay response is not serializable'));
245
+ }
246
+
247
+ if (Buffer.byteLength(serializedResponse, 'utf8') > maxChallengeRelayMessageBytes) {
248
+ serializedResponse = JSON.stringify({
249
+ success: false,
250
+ error: 'Challenge relay response exceeds maximum size',
251
+ });
252
+ }
253
+
254
+ socket.write(`${serializedResponse}\n`);
255
+ }
256
+
257
+ private createErrorResponse(idArg: string | undefined, errorArg: string): IChallengeRelayResponse {
258
+ const response: IChallengeRelayResponse = {
259
+ success: false,
260
+ error: errorArg,
261
+ };
262
+ if (idArg && Buffer.byteLength(idArg, 'utf8') <= maxChallengeRelayIdBytes) {
263
+ response.id = idArg;
264
+ }
265
+ return response;
266
+ }
267
+
268
+ private estimateJsonByteLength(
269
+ valueArg: unknown,
270
+ maxBytesArg: number,
271
+ seenArg = new Set<object>(),
272
+ ): number | undefined {
273
+ const bytes = this.estimateJsonByteLengthInternal(valueArg, maxBytesArg, seenArg);
274
+ return bytes !== undefined && bytes <= maxBytesArg ? bytes : undefined;
275
+ }
276
+
277
+ private estimateJsonByteLengthInternal(
278
+ valueArg: unknown,
279
+ maxBytesArg: number,
280
+ seenArg: Set<object>,
281
+ ): number | undefined {
282
+ if (valueArg === null) return 4;
283
+ if (typeof valueArg === 'string') return this.estimateJsonStringByteLength(valueArg, maxBytesArg);
284
+ if (typeof valueArg === 'number') return Number.isFinite(valueArg) ? String(valueArg).length : 4;
285
+ if (typeof valueArg === 'boolean') return valueArg ? 4 : 5;
286
+ if (typeof valueArg === 'undefined') return 4;
287
+ if (typeof valueArg === 'bigint' || typeof valueArg === 'symbol' || typeof valueArg === 'function') return undefined;
288
+ if (typeof valueArg !== 'object') return undefined;
289
+ if (seenArg.has(valueArg)) return undefined;
290
+
291
+ seenArg.add(valueArg);
292
+ try {
293
+ let totalBytes = 2;
294
+ if (Array.isArray(valueArg)) {
295
+ for (let i = 0; i < valueArg.length; i++) {
296
+ const childBytes = this.estimateJsonByteLengthInternal(valueArg[i], maxBytesArg, seenArg);
297
+ if (childBytes === undefined) return undefined;
298
+ totalBytes += childBytes + (i > 0 ? 1 : 0);
299
+ if (totalBytes > maxBytesArg) return undefined;
300
+ }
301
+ return totalBytes;
302
+ }
303
+
304
+ let propertyCount = 0;
305
+ for (const [key, value] of Object.entries(valueArg as Record<string, unknown>)) {
306
+ if (typeof value === 'undefined' || typeof value === 'function' || typeof value === 'symbol') continue;
307
+ const keyBytes = this.estimateJsonStringByteLength(key, maxBytesArg);
308
+ const valueBytes = this.estimateJsonByteLengthInternal(value, maxBytesArg, seenArg);
309
+ if (keyBytes === undefined || valueBytes === undefined) return undefined;
310
+ totalBytes += keyBytes + valueBytes + 1 + (propertyCount > 0 ? 1 : 0);
311
+ propertyCount += 1;
312
+ if (totalBytes > maxBytesArg) return undefined;
313
+ }
314
+ return totalBytes;
315
+ } finally {
316
+ seenArg.delete(valueArg);
317
+ }
318
+ }
319
+
320
+ private estimateJsonStringByteLength(valueArg: string, maxBytesArg: number): number | undefined {
321
+ let bytes = Buffer.byteLength(valueArg, 'utf8') + 2;
322
+ if (bytes > maxBytesArg) return undefined;
323
+
324
+ for (let i = 0; i < valueArg.length; i++) {
325
+ const code = valueArg.charCodeAt(i);
326
+ if (code === 0x22 || code === 0x5c) {
327
+ bytes += 1;
328
+ } else if (code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d) {
329
+ bytes += 1;
330
+ } else if (code < 0x20) {
331
+ bytes += 5;
332
+ }
333
+ if (bytes > maxBytesArg) return undefined;
334
+ }
335
+
336
+ return bytes;
337
+ }
338
+ }
@@ -2,6 +2,6 @@
2
2
  * SmartProxy models
3
3
  */
4
4
  // Export everything except IAcmeOptions from interfaces
5
- export type { ISmartProxyOptions, ISmartProxySecurityPolicy, ISmartProxyCertStore, IConnectionRecord, TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions } from './interfaces.js';
5
+ export type { ISmartProxyOptions, ISmartProxySecurityPolicy, ISmartProxyChallengeOptions, ISmartProxyCertStore, IConnectionRecord, TChallengeProviderRegistry, TSmartProxyCertProvisionObject, ICertProvisionEventComms, ICertificateIssuedEvent, ICertificateFailedEvent, IActiveConnectionSnapshot, IActiveConnectionSnapshotOptions } from './interfaces.js';
6
6
  export * from './route-types.js';
7
7
  export * from './metrics-types.js';
@@ -34,6 +34,17 @@ export interface ISmartProxySecurityPolicy {
34
34
  blockedCidrs?: string[];
35
35
  }
36
36
 
37
+ export interface ISmartProxyChallengeOptions {
38
+ /** Runtime-only cookie signing key. If omitted, SmartProxy generates an ephemeral key. */
39
+ cookieSigningKey?: string;
40
+ pendingCookieName?: string;
41
+ clearanceCookieName?: string;
42
+ reservedPathPrefix?: string;
43
+ relaySocketPath?: string;
44
+ relayTimeoutMs?: number;
45
+ pendingTtlSeconds?: number;
46
+ }
47
+
37
48
  /**
38
49
  * Provision object for static or HTTP-01 certificate
39
50
  */
@@ -171,6 +182,9 @@ export interface ISmartProxyOptions {
171
182
  connectionRateLimitPerMinute?: number; // Max new connections per minute from a single IP
172
183
  securityPolicy?: ISmartProxySecurityPolicy; // Global ingress block policy, enforced before routing
173
184
 
185
+ /** Runtime-only challenge enforcement settings. Never store this in route configs. */
186
+ challenge?: ISmartProxyChallengeOptions;
187
+
174
188
  // Enhanced keep-alive settings
175
189
  keepAliveTreatment?: 'standard' | 'extended' | 'immortal'; // How to treat keep-alive connections
176
190
  keepAliveInactivityMultiplier?: number; // Multiplier for inactivity timeout for keep-alive connections
@@ -249,6 +263,8 @@ export interface ISmartProxyOptions {
249
263
  rustBinaryPath?: string;
250
264
  }
251
265
 
266
+ export type TChallengeProviderRegistry = Map<string, plugins.smartchallenge.IChallengeProvider>;
267
+
252
268
  /**
253
269
  * Enhanced connection record
254
270
  */
@@ -196,6 +196,9 @@ export interface IRouteSecurity {
196
196
  /** Allowed VPN tunnel IPs, kept for compatibility. Prefer allowedClients. */
197
197
  allowedAssignedIps?: string[];
198
198
  };
199
+
200
+ /** Browser-facing challenge policy. SmartProxy enforces this in the HTTP path. */
201
+ challenge?: plugins.smartchallenge.IRouteChallengeConfig;
199
202
  }
200
203
 
201
204
  /**
@@ -1,5 +1,5 @@
1
1
  import type { IProtocolCacheEntry, IProtocolDistribution } from './metrics-types.js';
2
- import type { IAcmeOptions, ISmartProxyOptions, ISmartProxySecurityPolicy } from './interfaces.js';
2
+ import type { IAcmeOptions, ISmartProxyChallengeOptions, ISmartProxyOptions, ISmartProxySecurityPolicy } from './interfaces.js';
3
3
  import type {
4
4
  IRouteAction,
5
5
  IRouteConfig,
@@ -46,6 +46,11 @@ export interface IRustRouteConfig extends Omit<IRouteConfig, 'match' | 'action'>
46
46
 
47
47
  export interface IRustAcmeOptions extends Omit<IAcmeOptions, 'routeForwards'> {}
48
48
 
49
+ export interface IRustChallengeOptions extends Required<Pick<
50
+ ISmartProxyChallengeOptions,
51
+ 'cookieSigningKey' | 'pendingCookieName' | 'clearanceCookieName' | 'reservedPathPrefix' | 'relaySocketPath' | 'relayTimeoutMs' | 'pendingTtlSeconds'
52
+ >> {}
53
+
49
54
  export interface IRustProxyOptions {
50
55
  routes: IRustRouteConfig[];
51
56
  preserveSourceIp?: boolean;
@@ -75,6 +80,7 @@ export interface IRustProxyOptions {
75
80
  extendedKeepAliveLifetime?: number;
76
81
  metrics?: ISmartProxyOptions['metrics'];
77
82
  securityPolicy?: ISmartProxySecurityPolicy;
83
+ challenge?: IRustChallengeOptions;
78
84
  acme?: IRustAcmeOptions;
79
85
  }
80
86