@linxin666/dsh-remote-web-ui 0.3.23 → 0.4.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 (116) hide show
  1. package/README.i18n.yaml +2 -2
  2. package/README.md +13 -10
  3. package/README.zh.md +13 -10
  4. package/icon.svg +3 -0
  5. package/lib/client.js +396 -132
  6. package/lib/client.js.map +1 -1
  7. package/lib/index.js +604 -262
  8. package/lib/types/client/FenceNotice.d.ts.map +1 -1
  9. package/lib/types/client/FenceNotice.js +0 -3
  10. package/lib/types/client/PluginSettingsCard.d.ts +4 -0
  11. package/lib/types/client/PluginSettingsCard.d.ts.map +1 -1
  12. package/lib/types/client/PluginSettingsCard.js +3 -2
  13. package/lib/types/client/RemoteEntry.d.ts.map +1 -1
  14. package/lib/types/client/RemoteEntry.js +14 -9
  15. package/lib/types/client/RemotePanel.d.ts.map +1 -1
  16. package/lib/types/client/RemotePanel.js +13 -4
  17. package/lib/types/client/RemoteSettingsCard.d.ts +6 -6
  18. package/lib/types/client/RemoteSettingsCard.d.ts.map +1 -1
  19. package/lib/types/client/RemoteSettingsCard.js +14 -8
  20. package/lib/types/client/UpdateEntry.js +2 -2
  21. package/lib/types/client/UpdatePanel.js +2 -2
  22. package/lib/types/client/deep-link.js +2 -2
  23. package/lib/types/client/index.d.ts +16 -8
  24. package/lib/types/client/index.d.ts.map +1 -1
  25. package/lib/types/client/index.js +56 -32
  26. package/lib/types/client/locales.d.ts +0 -2
  27. package/lib/types/client/locales.d.ts.map +1 -1
  28. package/lib/types/client/locales.js +6 -8
  29. package/lib/types/client/mobile-adapt.d.ts.map +1 -1
  30. package/lib/types/client/mobile-adapt.js +143 -28
  31. package/lib/types/client/pair-api.d.ts +32 -3
  32. package/lib/types/client/pair-api.d.ts.map +1 -1
  33. package/lib/types/client/pair-api.js +44 -6
  34. package/lib/types/client/plugin-card-seat.d.ts +104 -0
  35. package/lib/types/client/plugin-card-seat.d.ts.map +1 -0
  36. package/lib/types/client/plugin-card-seat.js +143 -0
  37. package/lib/types/client/remote-channel.d.ts.map +1 -1
  38. package/lib/types/client/remote-channel.js +18 -5
  39. package/lib/types/client/settings-form.d.ts +17 -17
  40. package/lib/types/client/settings-form.d.ts.map +1 -1
  41. package/lib/types/client/settings-form.js +30 -27
  42. package/lib/types/console-output.d.ts +41 -0
  43. package/lib/types/console-output.d.ts.map +1 -0
  44. package/lib/types/console-output.js +89 -0
  45. package/lib/types/http.d.ts +18 -1
  46. package/lib/types/http.d.ts.map +1 -1
  47. package/lib/types/http.js +22 -1
  48. package/lib/types/index.d.ts +19 -11
  49. package/lib/types/index.d.ts.map +1 -1
  50. package/lib/types/index.js +91 -76
  51. package/lib/types/inner-auth.d.ts +3 -2
  52. package/lib/types/inner-auth.d.ts.map +1 -1
  53. package/lib/types/inner-auth.js +3 -2
  54. package/lib/types/pair-grant.d.ts +66 -0
  55. package/lib/types/pair-grant.d.ts.map +1 -0
  56. package/lib/types/pair-grant.js +95 -0
  57. package/lib/types/pairing.d.ts +44 -12
  58. package/lib/types/pairing.d.ts.map +1 -1
  59. package/lib/types/pairing.js +69 -20
  60. package/lib/types/pairing.persist.test.js +26 -0
  61. package/lib/types/posture.d.ts +11 -3
  62. package/lib/types/posture.d.ts.map +1 -1
  63. package/lib/types/posture.js +8 -3
  64. package/lib/types/remote-api.d.ts +10 -13
  65. package/lib/types/remote-api.d.ts.map +1 -1
  66. package/lib/types/remote-api.js +34 -34
  67. package/lib/types/remote-channel-boot.d.ts +6 -1
  68. package/lib/types/remote-channel-boot.d.ts.map +1 -1
  69. package/lib/types/remote-channel-boot.js +16 -8
  70. package/lib/types/remote-channel-rules.d.ts +14 -0
  71. package/lib/types/remote-channel-rules.d.ts.map +1 -1
  72. package/lib/types/remote-channel-rules.js +8 -0
  73. package/lib/types/remote-methods.d.ts +1 -1
  74. package/lib/types/remote-methods.d.ts.map +1 -1
  75. package/lib/types/remote-methods.js +1 -0
  76. package/lib/types/routes.d.ts +47 -5
  77. package/lib/types/routes.d.ts.map +1 -1
  78. package/lib/types/routes.js +160 -58
  79. package/lib/types/tunnel.d.ts +21 -6
  80. package/lib/types/tunnel.d.ts.map +1 -1
  81. package/lib/types/tunnel.js +58 -11
  82. package/lib/types/update.d.ts +4 -0
  83. package/lib/types/update.d.ts.map +1 -1
  84. package/lib/types/update.js +74 -33
  85. package/package.json +23 -20
  86. package/src/client/FenceNotice.tsx +0 -2
  87. package/src/client/PluginSettingsCard.tsx +7 -2
  88. package/src/client/RemoteEntry.tsx +14 -9
  89. package/src/client/RemotePanel.tsx +17 -7
  90. package/src/client/RemoteSettingsCard.tsx +16 -9
  91. package/src/client/UpdateEntry.tsx +2 -2
  92. package/src/client/UpdatePanel.tsx +3 -3
  93. package/src/client/deep-link.ts +2 -2
  94. package/src/client/index.ts +70 -40
  95. package/src/client/locales.ts +6 -8
  96. package/src/client/mobile-adapt.ts +136 -25
  97. package/src/client/pair-api.ts +47 -7
  98. package/src/client/plugin-card-seat.ts +192 -0
  99. package/src/client/remote-channel.ts +19 -4
  100. package/src/client/remote.module.css +38 -0
  101. package/src/client/settings-form.ts +34 -31
  102. package/src/console-output.ts +97 -0
  103. package/src/http.ts +23 -1
  104. package/src/index.ts +102 -79
  105. package/src/inner-auth.ts +3 -2
  106. package/src/pair-grant.ts +131 -0
  107. package/src/pairing.persist.test.ts +36 -0
  108. package/src/pairing.ts +75 -24
  109. package/src/posture.ts +12 -4
  110. package/src/remote-api.ts +34 -41
  111. package/src/remote-channel-boot.ts +16 -8
  112. package/src/remote-channel-rules.ts +17 -0
  113. package/src/remote-methods.ts +1 -0
  114. package/src/routes.ts +165 -57
  115. package/src/tunnel.ts +56 -11
  116. package/src/update.ts +74 -29
@@ -11,12 +11,21 @@
11
11
  * on the 0.1.2-alpha.2 cohort nothing emits api/gate — direct /api is
12
12
  * governed by the harness fence + browser auth — while the /remote channel
13
13
  * always enforces the pairing cookie itself.
14
+ *
15
+ * The app landing is reached with a one-time grant, never with the device id
16
+ * itself: /pair-accept mints a short-lived, single-consume grant
17
+ * (pair-grant.ts) and redirects to /pair-app?grant=<g>, and only the landing's
18
+ * own response carries the device credential into the shell. The device cookie
19
+ * stays the primary credential; the grant only keeps a live session credential
20
+ * out of the URL, the address bar, and every log on the redirect path.
14
21
  */
15
22
  import { z } from 'zod';
16
23
  import { UnknownLanAddressError } from "./pairing.js";
17
24
  import { isLoopbackAddress } from "./loopback.js";
18
25
  import { isLoopbackClient, readCookie } from "./gate.js";
19
26
  import { readJsonBody, writeJson } from "./http.js";
27
+ import { createPairGrantStore } from "./pair-grant.js";
28
+ import { REMOTE_HOST_GRANT_GLOBAL } from "./remote-channel-rules.js";
20
29
  /**
21
30
  * Browser-trust fence for the /api/pair routes, mirroring the connection
22
31
  * package's internal fence semantics (Host/Origin based, DNS-rebinding and
@@ -75,7 +84,17 @@ export function isTrustedHost(request, trustedHosts) {
75
84
  return isLoopbackClient(request) || trustedHosts.some(entry => {
76
85
  // A port-less entry matches the hostname on any port; an exact host:port
77
86
  // entry matches that authority verbatim (WHATWG normalization both sides).
78
- const entryUrl = new URL(`http://${entry}`);
87
+ // A malformed entry (a config or DSH_REMOTE_TRUSTED_HOSTS typo) contributes
88
+ // no trust instead of throwing out of the fence: every pairing request from
89
+ // a non-loopback origin would otherwise answer 400 with only a generic
90
+ // webserver warning, and the valid entries would never be consulted.
91
+ let entryUrl;
92
+ try {
93
+ entryUrl = new URL(`http://${entry}`);
94
+ }
95
+ catch {
96
+ return false;
97
+ }
79
98
  return entryUrl.port === '' ? entryUrl.hostname === hostname : entryUrl.host === hostUrl.host;
80
99
  });
81
100
  }
@@ -183,19 +202,31 @@ function privateLanHostOf(request, navigation = false) {
183
202
  const MAX_BODY_BYTES = 4096;
184
203
  /**
185
204
  * The rate-limit bucket key for one accept attempt (pure; unit-tested).
186
- * The first client-visible XFF hop separates buckets behind the auto-tunnel
187
- * (every internet client arrives from 127.0.0.1 there) — but only for
188
- * loopback peers, since a direct LAN client can rotate the header freely.
205
+ * A forwarded hop separates buckets behind the auto-tunnel (every internet
206
+ * client arrives from 127.0.0.1 there) — but only for loopback peers, since a
207
+ * direct LAN client can rotate the header freely. The caller passes the hop it
208
+ * trusts (the edge-appended LAST one; see rateLimitAccept), and the hop is
209
+ * truncated so one oversized header cannot mint a huge key.
189
210
  * @param socketIp - the socket peer address.
190
- * @param forwarded - the first XFF hop, already trimmed, if any.
211
+ * @param forwarded - the trusted XFF hop, already trimmed, if any.
191
212
  * @param bucket - page (GET /pair-accept) vs api (POST /api/pair/accept).
192
213
  */
193
214
  export function acceptLimitKey(socketIp, forwarded, bucket) {
194
215
  if (isLoopbackAddress(socketIp) && forwarded !== undefined && forwarded !== '') {
195
- return `${bucket}|${socketIp}|${forwarded}`;
216
+ return `${bucket}|${socketIp}|${forwarded.slice(0, MAX_FORWARDED_KEY_CHARS)}`;
196
217
  }
197
218
  return `${bucket}|${socketIp}`;
198
219
  }
220
+ /** Longest forwarded hop kept in a bucket key (the header is caller-sized). */
221
+ const MAX_FORWARDED_KEY_CHARS = 64;
222
+ /**
223
+ * Hard cap on live accept buckets. The key is caller-influenced (a forwarded
224
+ * hop), so a rotating flood must not grow the table without bound: past the
225
+ * cap the oldest bucket is evicted, the same FIFO stance as
226
+ * {@link addBounded}. A legitimate client that loses its bucket merely starts
227
+ * a fresh window.
228
+ */
229
+ export const MAX_ACCEPT_BUCKETS = 1024;
199
230
  /**
200
231
  * Cap on the dynamically trusted hosts (see {@link addBounded}): room for
201
232
  * every legitimate router/proxy aliasing shape, small enough that a flood of
@@ -277,17 +308,25 @@ const COOKIE_MAX_AGE_SEC = 365 * 24 * 60 * 60;
277
308
  export const APP_DEVICE_STORAGE_KEY = 'dsh-remote-device';
278
309
  export function appShellCaptureScript(deviceId) {
279
310
  const safeId = JSON.stringify(deviceId);
311
+ const grantGlobal = JSON.stringify(REMOTE_HOST_GRANT_GLOBAL);
280
312
  const register = `try{if('serviceWorker' in navigator){navigator.serviceWorker.register(${JSON.stringify(PAIR_PATHS.appServiceWorker)},{scope:'/'}).catch(function(e){})}}catch(e){}`;
281
- return `<script>(function(){try{sessionStorage.setItem(${JSON.stringify(APP_DEVICE_STORAGE_KEY)},${safeId});localStorage.setItem(${JSON.stringify(APP_DEVICE_STORAGE_KEY)},${safeId});}catch(e){}try{history.replaceState(null,'','/')}catch(e){}${register}})()</script>`;
313
+ return `<script>(function(){try{sessionStorage.setItem(${JSON.stringify(APP_DEVICE_STORAGE_KEY)},${safeId});}catch(e){}try{history.replaceState(null,'','/')}catch(e){}try{window[${grantGlobal}]=true}catch(e){}${register}})()</script>`;
282
314
  }
283
- /** Patch the official index document with the device-capture script. */
315
+ /**
316
+ * Patch the official index document with the device-capture script. The script
317
+ * goes immediately after the opening <head> tag, ahead of the harness-injected
318
+ * parse-time channel boot patch: that patch installs the transport host-mode
319
+ * hook only when this server-served marker is already set (see
320
+ * remote-channel-boot.ts), so host mode is granted by the device-gated landing
321
+ * instead of being asserted from the origin.
322
+ */
284
323
  export function patchAppShell(html, deviceId) {
285
324
  const script = appShellCaptureScript(deviceId);
286
- const marker = '</head>';
287
- const at = html.indexOf(marker);
288
- if (at === -1)
325
+ const head = /<head[^>]*>/i.exec(html);
326
+ if (head === null)
289
327
  return script + html;
290
- return html.slice(0, at) + script + html.slice(at);
328
+ const end = head.index + head[0].length;
329
+ return html.slice(0, end) + script + html.slice(end);
291
330
  }
292
331
  /**
293
332
  * The reopen service worker served at PAIR_PATHS.appServiceWorker. A paired
@@ -488,6 +527,7 @@ export function makeRoutes(deps) {
488
527
  const pairingRequired = () => typeof requirePairingForLan === 'function'
489
528
  ? requirePairingForLan()
490
529
  : requirePairingForLan;
530
+ const grants = deps.grants ?? createPairGrantStore();
491
531
  const events = new PairingEventsStream(service);
492
532
  const dynamicTrustedHosts = new Set();
493
533
  /** Loopback-only fence: the desktop panel's control endpoints. */
@@ -517,11 +557,18 @@ export function makeRoutes(deps) {
517
557
  if (privateLanHost !== undefined) {
518
558
  const cookieDeviceId = readCookie(req.headers.cookie, service.config.cookieName);
519
559
  let queryDeviceId = null;
560
+ let queryGrant;
520
561
  try {
521
- queryDeviceId = new URL(req.url ?? '/', 'http://pair.invalid').searchParams.get('device');
562
+ const params = new URL(req.url ?? '/', 'http://pair.invalid').searchParams;
563
+ queryDeviceId = params.get('device');
564
+ queryGrant = params.get('grant') ?? undefined;
522
565
  }
523
566
  catch { }
524
- const deviceId = cookieDeviceId ?? queryDeviceId ?? undefined;
567
+ // The app landing carries a one-time grant instead of the device id;
568
+ // peek (never spend) so the fence can trust a container/NAT authority
569
+ // whose Host is not among the advertised ones.
570
+ const granted = grants.peek(queryGrant);
571
+ const deviceId = cookieDeviceId ?? queryDeviceId ?? granted ?? undefined;
525
572
  if (deviceId !== undefined && service.hasDevice(deviceId)) {
526
573
  addBounded(dynamicTrustedHosts, privateLanHost, MAX_DYNAMIC_TRUSTED_HOSTS);
527
574
  return true;
@@ -545,27 +592,50 @@ export function makeRoutes(deps) {
545
592
  * count separately: a QR re-scan (page navigation) must not consume a
546
593
  * brute-force budget that belongs to token guessing (and vice versa).
547
594
  */
595
+ /**
596
+ * Drop expired buckets from the FRONT of the insertion-ordered table, then
597
+ * FIFO-evict past the hard cap. The front always holds the oldest window
598
+ * because a re-armed bucket is re-inserted at the tail, so this is amortized
599
+ * O(1) per request: every iteration deletes an entry that was inserted once.
600
+ * It replaces a full-table scan that ran on every pairing request once the
601
+ * table passed 256 entries (and deleted nothing while all windows were
602
+ * fresh).
603
+ */
604
+ const pruneAcceptAttempts = (nowMs) => {
605
+ while (acceptAttempts.size > 0) {
606
+ const oldest = acceptAttempts.keys().next().value;
607
+ if (oldest === undefined)
608
+ break;
609
+ const attempt = acceptAttempts.get(oldest);
610
+ if (attempt !== undefined && nowMs - attempt.windowStart <= ACCEPT_WINDOW_MS)
611
+ break;
612
+ acceptAttempts.delete(oldest);
613
+ }
614
+ while (acceptAttempts.size > MAX_ACCEPT_BUCKETS) {
615
+ const oldest = acceptAttempts.keys().next().value;
616
+ if (oldest === undefined)
617
+ break;
618
+ acceptAttempts.delete(oldest);
619
+ }
620
+ };
548
621
  const rateLimitAccept = (req, bucket) => {
549
622
  const socketIp = req.socket?.remoteAddress ?? 'unknown';
550
623
  // XFF is honored only for loopback peers (the tunnel edge); see
551
624
  // acceptLimitKey. It is untrusted for authentication and never grants
552
- // access.
553
- const forwarded = typeof req.headers['x-forwarded-for'] === 'string'
554
- ? (req.headers['x-forwarded-for'].split(',')[0] ?? '').trim()
555
- : undefined;
625
+ // access. Only the LAST hop is used: an edge APPENDS the address it saw, so
626
+ // any earlier hop is whatever the client sent — keying on the first one let
627
+ // an attacker rotate the header for a fresh brute-force budget per request.
628
+ const hops = typeof req.headers['x-forwarded-for'] === 'string'
629
+ ? req.headers['x-forwarded-for'].split(',')
630
+ : [];
631
+ const forwarded = (hops[hops.length - 1] ?? '').trim();
556
632
  const ip = acceptLimitKey(socketIp, forwarded, bucket);
557
633
  const nowMs = Date.now();
558
- // The map lives as long as the plugin: prune expired windows once the
559
- // table grows past a modest size so distinct source IPs (LAN clients,
560
- // brute-force scans) cannot accumulate forever.
561
- if (acceptAttempts.size > 256) {
562
- for (const [key, attempt] of acceptAttempts) {
563
- if (nowMs - attempt.windowStart > ACCEPT_WINDOW_MS)
564
- acceptAttempts.delete(key);
565
- }
566
- }
634
+ pruneAcceptAttempts(nowMs);
567
635
  const entry = acceptAttempts.get(ip);
568
636
  if (entry === undefined || nowMs - entry.windowStart > ACCEPT_WINDOW_MS) {
637
+ // Re-insert at the tail so insertion order stays window order.
638
+ acceptAttempts.delete(ip);
569
639
  acceptAttempts.set(ip, { count: 1, windowStart: nowMs });
570
640
  return false;
571
641
  }
@@ -646,19 +716,18 @@ export function makeRoutes(deps) {
646
716
  writeJson(res, 403, { ok: false, code: 'forbidden' });
647
717
  return;
648
718
  }
649
- writeJson(res, result.code === 'used' ? 409 : 404, { ok: false, code: result.code });
719
+ writeJson(res, 404, { ok: false, code: result.code });
650
720
  return;
651
721
  }
652
722
  if (privateLanHost !== undefined) {
653
723
  addBounded(dynamicTrustedHosts, privateLanHost, MAX_DYNAMIC_TRUSTED_HOSTS);
654
724
  }
655
- // No Secure attribute: LAN pairing runs over plain HTTP (the cookie must
656
- // work there), and the same cookie rides HTTPS on the tunnel. Lax keeps
657
- // top-level navigations working while blocking cross-site subrequests.
725
+ // The Secure attribute follows the transport (deviceCookie): off on
726
+ // plain-HTTP LAN, where a Secure cookie is dropped by the browser, and on
727
+ // for a tunneled request the edge stamped as https. Lax keeps top-level
728
+ // navigations working while blocking cross-site subrequests.
658
729
  writeJson(res, 200, { ok: true, deviceId: result.deviceId }, {
659
- 'set-cookie': [
660
- `${service.config.cookieName}=${result.deviceId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${String(COOKIE_MAX_AGE_SEC)}`,
661
- ],
730
+ 'set-cookie': [deviceCookie(req, service.config.cookieName, result.deviceId)],
662
731
  });
663
732
  };
664
733
  const handleStop = async (req, res) => {
@@ -724,6 +793,11 @@ export function makeRoutes(deps) {
724
793
  }
725
794
  const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
726
795
  const paired = deviceId !== undefined && service.hasDevice(deviceId);
796
+ // The desktop (loopback) keeps the full pairing-relevant view; the LAN IP
797
+ // literals stay behind a live pairing or loopback so an unauthenticated
798
+ // caller on a tunnel cannot map the internal network. A phone already knows
799
+ // its usable literal from the URL it opened.
800
+ const local = isLoopbackClient(req);
727
801
  const snapshot = service.snapshot();
728
802
  // Unpaired LAN/tunnel clients get only the pairing-relevant fields; the
729
803
  // token expiry, public tunnel URL, and counts are an oracle for targeting
@@ -731,9 +805,9 @@ export function makeRoutes(deps) {
731
805
  // session credentials) is never returned here — only the loopback events
732
806
  // stream carries it to the desktop panel.
733
807
  const { devices: _devices, ...rest } = snapshot;
734
- const visible = paired
808
+ const visible = paired || local
735
809
  ? rest
736
- : { phase: snapshot.phase, lanAvailable: snapshot.lanAvailable, lanAddresses: snapshot.lanAddresses };
810
+ : { phase: snapshot.phase, lanAvailable: snapshot.lanAvailable };
737
811
  writeJson(res, 200, { ok: true, paired, requirePairingForLan: pairingRequired(), ...visible });
738
812
  };
739
813
  const handleEvents = (req, res) => {
@@ -800,9 +874,10 @@ export function makeRoutes(deps) {
800
874
  // instead of the harness 401 dead end.
801
875
  const deviceId = readCookie(req.headers.cookie, service.config.cookieName);
802
876
  if (deviceId !== undefined && service.hasDevice(deviceId)) {
803
- // An already-paired device re-opening a dead link goes straight to
804
- // the cookieless app landing with its live device credential.
805
- res.writeHead(303, { location: `${appOrigin(req)}/pair-app?device=${encodeURIComponent(deviceId)}`, 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
877
+ // An already-paired device re-opening a dead link goes straight to the
878
+ // cookieless app landing, again on a fresh one-time grant.
879
+ const { grant } = grants.issue(deviceId);
880
+ res.writeHead(303, { location: `${appOrigin(req)}/pair-app?grant=${encodeURIComponent(grant)}`, 'cache-control': 'no-store', 'referrer-policy': 'no-referrer' });
806
881
  res.end();
807
882
  return;
808
883
  }
@@ -814,24 +889,27 @@ export function makeRoutes(deps) {
814
889
  addBounded(dynamicTrustedHosts, privateLanHost, MAX_DYNAMIC_TRUSTED_HOSTS);
815
890
  }
816
891
  // Land the paired device on the cookieless app page: the official shell
817
- // served by this plugin, with the device id in the URL. No harness index
818
- // gate, no browser-auth cookie hop - the channel gate accepts the device
819
- // credential whether or not the browser stores cookies.
892
+ // served by this plugin. The URL carries a one-time grant, never the
893
+ // device id - a session credential must not travel in a URL (history, the
894
+ // address bar, every log on the redirect path) - and the landing's own
895
+ // response hands the device id to the shell. No harness index gate, no
896
+ // browser-auth cookie hop: the channel gate accepts the device credential
897
+ // whether or not the browser stores cookies.
898
+ const { grant } = grants.issue(result.deviceId);
820
899
  res.writeHead(303, {
821
- location: `${appOrigin(req)}/pair-app?device=${encodeURIComponent(result.deviceId)}`,
900
+ location: `${appOrigin(req)}/pair-app?grant=${encodeURIComponent(grant)}`,
822
901
  'cache-control': 'no-store',
823
902
  'referrer-policy': 'no-referrer',
824
- 'set-cookie': [
825
- `${service.config.cookieName}=${result.deviceId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${String(COOKIE_MAX_AGE_SEC)}`,
826
- ],
903
+ 'set-cookie': [deviceCookie(req, service.config.cookieName, result.deviceId)],
827
904
  });
828
905
  res.end();
829
906
  };
830
907
  /**
831
- * The cookieless app landing. A paired device (device query or live
832
- * pairing cookie) receives the official index shell patched with the
833
- * device-capture script; the shell itself is the official document and
834
- * carries no data, so serving it needs only the device credential.
908
+ * The cookieless app landing. A paired device (a one-time grant minted by
909
+ * /pair-accept, or a live pairing cookie on a reopen) receives the official
910
+ * index shell patched with the device-capture script; the shell itself is
911
+ * the official document and carries no data, so serving it needs only the
912
+ * device credential.
835
913
  */
836
914
  const handleAppPage = async (req, res) => {
837
915
  if (!requireMethod(req, res, 'GET'))
@@ -848,10 +926,14 @@ export function makeRoutes(deps) {
848
926
  return;
849
927
  }
850
928
  const url = new URL(req.url ?? '/', 'http://pair.invalid');
851
- const device = url.searchParams.get('device') ?? '';
929
+ // The one-time grant is spent before anything else: a replayed or expired
930
+ // grant resolves to nothing (pair-grant.ts), so a leaked landing URL can
931
+ // never be re-run. The cookie stays the primary credential for a reopen
932
+ // (the service worker's network-first navigation carries it).
933
+ const grantedDevice = grants.consume(url.searchParams.get('grant') ?? undefined);
852
934
  const cookieDevice = readCookie(req.headers.cookie, service.config.cookieName);
853
- const id = (device !== '' && service.touchDevice(device))
854
- ? device
935
+ const id = (grantedDevice !== undefined && service.touchDevice(grantedDevice))
936
+ ? grantedDevice
855
937
  : (cookieDevice !== undefined && service.touchDevice(cookieDevice))
856
938
  ? cookieDevice
857
939
  : undefined;
@@ -870,9 +952,7 @@ export function makeRoutes(deps) {
870
952
  'content-type': 'text/html; charset=utf-8',
871
953
  'cache-control': 'no-store',
872
954
  'referrer-policy': 'no-referrer',
873
- 'set-cookie': [
874
- `${service.config.cookieName}=${id}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${String(COOKIE_MAX_AGE_SEC)}`,
875
- ],
955
+ 'set-cookie': [deviceCookie(req, service.config.cookieName, id)],
876
956
  });
877
957
  res.end(patchAppShell(html, id));
878
958
  };
@@ -921,9 +1001,31 @@ export function makeRoutes(deps) {
921
1001
  }
922
1002
  return routes;
923
1003
  }
1004
+ /**
1005
+ * Whether the request reached this process over TLS. The harness itself serves
1006
+ * plain HTTP; a tunnel edge terminates TLS and stamps `x-forwarded-proto`
1007
+ * (the same signal {@link appOrigin} trusts when it rebuilds a redirect).
1008
+ */
1009
+ export function isHttpsRequest(req) {
1010
+ const forwarded = req.headers['x-forwarded-proto'];
1011
+ if (typeof forwarded === 'string') {
1012
+ const first = forwarded.split(',')[0]?.trim().toLowerCase();
1013
+ if (first === 'https')
1014
+ return true;
1015
+ }
1016
+ return req.socket.encrypted === true;
1017
+ }
1018
+ /**
1019
+ * The device cookie for one request. `Secure` is added only when the request
1020
+ * arrived over TLS: LAN pairing runs on plain HTTP, where a Secure cookie is
1021
+ * simply dropped by the browser and the phone would silently lose its session.
1022
+ */
1023
+ export function deviceCookie(req, cookieName, deviceId) {
1024
+ const secure = isHttpsRequest(req) ? '; Secure' : '';
1025
+ return `${cookieName}=${deviceId}; Path=/; HttpOnly; SameSite=Lax; Max-Age=${String(COOKIE_MAX_AGE_SEC)}${secure}`;
1026
+ }
924
1027
  /** The request origin (https when a tunnel edge says so). */
925
1028
  function appOrigin(req) {
926
- const proto = req.headers['x-forwarded-proto'] === 'https' ? 'https' : 'http';
927
1029
  const origin = `http://${req.headers.host ?? '127.0.0.1'}`;
928
- return proto === 'https' ? origin.replace('http://', 'https://') : origin;
1030
+ return isHttpsRequest(req) ? origin.replace('http://', 'https://') : origin;
929
1031
  }
@@ -107,6 +107,8 @@ export interface BinaryReadinessSeams {
107
107
  * the reinstall), and re-fetch the current platform binary otherwise.
108
108
  */
109
109
  export declare function createBinaryReadiness(executable: string, seams?: BinaryReadinessSeams): () => Promise<void>;
110
+ /** How many archive downloads one process may attempt before giving up. */
111
+ export declare const MAX_BINARY_INSTALL_ATTEMPTS = 3;
110
112
  /**
111
113
  * Spawn flags for one quick target. `originHostHeader` stamps the Host the
112
114
  * local webserver sees: the plugin's phone-facing fence trusts the configured
@@ -117,13 +119,26 @@ export declare function createBinaryReadiness(executable: string, seams?: Binary
117
119
  */
118
120
  export declare function quickTunnelFlags(originHostHeader?: string): Record<string, string | boolean>;
119
121
  /**
120
- * Command-line arguments for a named tunnel with a persistent Cloudflare token.
121
- * Note: `--no-autoupdate` and `--protocol` are flags for the `tunnel` command
122
- * and must appear BEFORE the `run` subcommand. Putting them after `run` causes
123
- * cloudflared to exit immediately with "flag provided but not defined: -no-autoupdate"
124
- * (issues #1432, #1433).
122
+ * Command-line arguments for a named tunnel. The account token is deliberately
123
+ * NOT part of the argv: `--token <t>` would expose an account credential to any
124
+ * local user through ps/proc for the whole tunnel lifetime, while the flag's own
125
+ * environment variable (TUNNEL_TOKEN, see cloudflared's tunnel run flags) is not
126
+ * readable that way. Note: `--no-autoupdate` and `--protocol` are flags for the
127
+ * `tunnel` command and must appear BEFORE the `run` subcommand. Putting them
128
+ * after `run` causes cloudflared to exit immediately with "flag provided but not
129
+ * defined: -no-autoupdate" (issues #1432, #1433).
125
130
  */
126
- export declare function namedTunnelArgs(token: string): string[];
131
+ export declare function namedTunnelArgs(): string[];
132
+ /**
133
+ * Run one spawn call with TUNNEL_TOKEN set, restoring the previous value
134
+ * afterwards. The cloudflared package's Tunnel spawns with the inherited
135
+ * environment inside its constructor, so the variable only has to exist for
136
+ * that call; the child keeps its own copy of the environment.
137
+ * @param token - the named-tunnel token.
138
+ * @param spawnTunnel - the call that creates the child process.
139
+ * @returns whatever the spawn call returns.
140
+ */
141
+ export declare function withTunnelTokenEnv<T>(token: string, spawnTunnel: () => T): T;
127
142
  /**
128
143
  * Own the lifecycle of one auto-tunnel: start/stop, URL surfacing, and
129
144
  * crash-restart backoff.
@@ -1 +1 @@
1
- {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../../src/tunnel.ts"],"names":[],"mappings":"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"}
@@ -96,20 +96,38 @@ export function createBinaryReadiness(executable, seams = {}) {
96
96
  const runs = seams.runs ?? binaryRuns;
97
97
  const installBinary = seams.install ?? install;
98
98
  const validated = new Set();
99
+ let installs = 0;
100
+ let lastFailure;
99
101
  return async () => {
100
102
  if (validated.has(executable))
101
103
  return;
104
+ // The manager retries forever with backoff, so a permanently unrunnable
105
+ // binary must not re-download the whole platform archive on every attempt
106
+ // (a wrong-arch payload would otherwise fetch it once per restart round).
107
+ // A transient install failure still gets its retries; past the cap the
108
+ // remembered error is rethrown until the process restarts.
109
+ if (installs >= MAX_BINARY_INSTALL_ATTEMPTS && lastFailure !== undefined)
110
+ throw lastFailure;
102
111
  if (exists(executable) && await runs(executable)) {
103
112
  validated.add(executable);
104
113
  return;
105
114
  }
106
- await installBinary(executable);
107
- if (!(await runs(executable))) {
108
- throw new Error(`the cloudflared binary at ${executable} still does not run after the reinstall`);
115
+ installs += 1;
116
+ try {
117
+ await installBinary(executable);
118
+ if (!(await runs(executable))) {
119
+ throw new Error(`the cloudflared binary at ${executable} still does not run after the reinstall`);
120
+ }
121
+ }
122
+ catch (error) {
123
+ lastFailure = error instanceof Error ? error : new Error(String(error));
124
+ throw lastFailure;
109
125
  }
110
126
  validated.add(executable);
111
127
  };
112
128
  }
129
+ /** How many archive downloads one process may attempt before giving up. */
130
+ export const MAX_BINARY_INSTALL_ATTEMPTS = 3;
113
131
  /** The readiness policy the manager uses by default: the package's real binary path. */
114
132
  const defaultBinaryReadiness = createBinaryReadiness(bin);
115
133
  /** Default binary readiness: keep a working platform binary at `bin`. */
@@ -132,14 +150,43 @@ export function quickTunnelFlags(originHostHeader) {
132
150
  : { ...SHARED_TUNNEL_FLAGS, '--http-host-header': originHostHeader };
133
151
  }
134
152
  /**
135
- * Command-line arguments for a named tunnel with a persistent Cloudflare token.
136
- * Note: `--no-autoupdate` and `--protocol` are flags for the `tunnel` command
137
- * and must appear BEFORE the `run` subcommand. Putting them after `run` causes
138
- * cloudflared to exit immediately with "flag provided but not defined: -no-autoupdate"
139
- * (issues #1432, #1433).
153
+ * Command-line arguments for a named tunnel. The account token is deliberately
154
+ * NOT part of the argv: `--token <t>` would expose an account credential to any
155
+ * local user through ps/proc for the whole tunnel lifetime, while the flag's own
156
+ * environment variable (TUNNEL_TOKEN, see cloudflared's tunnel run flags) is not
157
+ * readable that way. Note: `--no-autoupdate` and `--protocol` are flags for the
158
+ * `tunnel` command and must appear BEFORE the `run` subcommand. Putting them
159
+ * after `run` causes cloudflared to exit immediately with "flag provided but not
160
+ * defined: -no-autoupdate" (issues #1432, #1433).
161
+ */
162
+ export function namedTunnelArgs() {
163
+ return ['tunnel', '--no-autoupdate', '--protocol', 'http2', 'run'];
164
+ }
165
+ /**
166
+ * Run one spawn call with TUNNEL_TOKEN set, restoring the previous value
167
+ * afterwards. The cloudflared package's Tunnel spawns with the inherited
168
+ * environment inside its constructor, so the variable only has to exist for
169
+ * that call; the child keeps its own copy of the environment.
170
+ * @param token - the named-tunnel token.
171
+ * @param spawnTunnel - the call that creates the child process.
172
+ * @returns whatever the spawn call returns.
140
173
  */
141
- export function namedTunnelArgs(token) {
142
- return ['tunnel', '--no-autoupdate', '--protocol', 'http2', 'run', '--token', token];
174
+ export function withTunnelTokenEnv(token, spawnTunnel) {
175
+ const previous = process.env.TUNNEL_TOKEN;
176
+ process.env.TUNNEL_TOKEN = token;
177
+ try {
178
+ return spawnTunnel();
179
+ }
180
+ finally {
181
+ if (previous === undefined)
182
+ delete process.env.TUNNEL_TOKEN;
183
+ else
184
+ process.env.TUNNEL_TOKEN = previous;
185
+ }
186
+ }
187
+ /** Spawn one named tunnel without putting its token in the child's argv. */
188
+ function spawnNamedTunnel(token) {
189
+ return withTunnelTokenEnv(token, () => new Tunnel(namedTunnelArgs()));
143
190
  }
144
191
  /** Default factory: the cloudflared package's quick and named tunnels. */
145
192
  function defaultFactory(target) {
@@ -154,7 +201,7 @@ function defaultFactory(target) {
154
201
  if (target.kind === 'quick') {
155
202
  return Tunnel.quick(target.targetUrl, quickTunnelFlags(target.originHostHeader));
156
203
  }
157
- return namedTunnelHandle(new Tunnel(namedTunnelArgs(target.token)), target.publicUrl);
204
+ return namedTunnelHandle(spawnNamedTunnel(target.token), target.publicUrl);
158
205
  }
159
206
  /** Node timers. */
160
207
  const nodeTimer = { setTimeout, clearTimeout };
@@ -219,7 +219,11 @@ export interface UpdateRunDeps {
219
219
  timeoutMs?: number;
220
220
  /** Platform override (defaults to process.platform; test seam). */
221
221
  platform?: NodeJS.Platform;
222
+ /** Process-group kill seam (defaults to process.kill; test seam). */
223
+ killImpl?: KillImpl;
222
224
  }
225
+ /** Seam for the process-group kill (tests inject a recorder). */
226
+ export type KillImpl = (pid: number, signal: NodeJS.Signals) => void;
223
227
  /**
224
228
  * Run the update inside the profile directory. Tries pnpm first, falls back
225
229
  * to corepack and then npx when the previous command is missing (ENOENT);
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../src/update.ts"],"names":[],"mappings":"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"}