dsh-pocket 2.9.0 → 2.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client/client.js CHANGED
@@ -505,11 +505,16 @@ function startFileGuard(readFile) {
505
505
 
506
506
  // client/mobile/sessionGuard.ts
507
507
  var SESSION_META = "dsh-pocket-session";
508
+ var ACCESS_META = "dsh-pocket-access";
508
509
  function readFingerprint() {
509
510
  const meta = document.querySelector(`meta[name="${SESSION_META}"]`);
510
511
  const fp = meta?.content?.trim();
511
512
  return fp && fp.length > 0 ? fp : null;
512
513
  }
514
+ function readAccess() {
515
+ const meta = document.querySelector(`meta[name="${ACCESS_META}"]`);
516
+ return meta?.content?.trim() ?? null;
517
+ }
513
518
  function mountBadge(fp) {
514
519
  const badge = document.createElement("div");
515
520
  badge.setAttribute("data-mobile-nav", "session-fp");
@@ -556,6 +561,8 @@ function mountWarning() {
556
561
  return () => overlay.remove();
557
562
  }
558
563
  function startSessionGuard() {
564
+ if (readAccess() !== "public") return () => {
565
+ };
559
566
  let cleanupBadge = null;
560
567
  let cleanupWarn = null;
561
568
  const evaluate = () => {
@@ -12,6 +12,7 @@
12
12
  // 弹红色全屏警告并拦截交互,防止用户在被钓鱼页输入 8 位密码。
13
13
 
14
14
  const SESSION_META = 'dsh-pocket-session'
15
+ const ACCESS_META = 'dsh-pocket-access'
15
16
 
16
17
  function readFingerprint(): string | null {
17
18
  const meta = document.querySelector<HTMLMetaElement>(`meta[name="${SESSION_META}"]`)
@@ -19,6 +20,12 @@ function readFingerprint(): string | null {
19
20
  return fp && fp.length > 0 ? fp : null
20
21
  }
21
22
 
23
+ /** 读取代理注入的访问类型标记(public | lan)。 */
24
+ function readAccess(): string | null {
25
+ const meta = document.querySelector<HTMLMetaElement>(`meta[name="${ACCESS_META}"]`)
26
+ return meta?.content?.trim() ?? null
27
+ }
28
+
22
29
  function mountBadge(fp: string): () => void {
23
30
  const badge = document.createElement('div')
24
31
  badge.setAttribute('data-mobile-nav', 'session-fp')
@@ -52,6 +59,10 @@ function mountWarning(): () => void {
52
59
  }
53
60
 
54
61
  export function startSessionGuard(): () => void {
62
+ // 局域网访问不存在「快速隧道子域被 Cloudflare 回收复用」的风险(issue #83),
63
+ // 防钓鱼校验只在公网启用;局域网直接不挂,避免误报阻断用户使用。
64
+ if (readAccess() !== 'public') return () => {}
65
+
55
66
  let cleanupBadge: (() => void) | null = null
56
67
  let cleanupWarn: (() => void) | null = null
57
68
  const evaluate = (): void => {
package/lib/proxy.mjs CHANGED
@@ -494,6 +494,11 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
494
494
  const server = createServer((req, res) => {
495
495
  const host = String(req.headers.host ?? '');
496
496
  const isPublic = classifyHost(host) === 'public';
497
+ // 访问类型标记(issue #83):sessionGuard 只在公网启用——局域网 IP 是用户自己电脑的地址,
498
+ // 不存在「快速隧道子域被 Cloudflare 回收复用」的风险,不应弹防钓鱼警告。
499
+ const accessMeta = isPublic
500
+ ? '<meta name="dsh-pocket-access" content="public">'
501
+ : '<meta name="dsh-pocket-access" content="lan">';
497
502
  // 局域网访问关闭(issue #54):拦截经局域网 IP/主机名访问的请求;
498
503
  // 公网(含任意非内网 Host——issue #66 fail closed)与 loopback(本机/cloudflared 回连)放行,
499
504
  // 公网流量随后照常走访问密码认证。
@@ -526,7 +531,7 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
526
531
  'cache-control': 'no-store',
527
532
  'retry-after': String(rl.retryAfter),
528
533
  });
529
- res.end(loginPageHtml('locked', isPublic, rl.retryAfter, sessionFingerprint));
534
+ res.end(loginPageHtml('locked', isPublic, rl.retryAfter, isPublic ? sessionFingerprint : null));
530
535
  return;
531
536
  }
532
537
  let body = '';
@@ -547,7 +552,7 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
547
552
  limiter?.record(ip);
548
553
  log?.(`dsh-pocket: login failed from ${ip} | 登录失败 IP: ${ip}`);
549
554
  res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
550
- res.end(loginPageHtml(true, isPublic, 0, sessionFingerprint));
555
+ res.end(loginPageHtml(true, isPublic, 0, isPublic ? sessionFingerprint : null));
551
556
  }
552
557
  });
553
558
  return;
@@ -558,7 +563,7 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
558
563
  // 锁定期间打开登录页也给提示(HTTP 200 + 锁定文案;429 语义留给 POST 拒绝)
559
564
  const rl = limiter?.status(ip) ?? { locked: false, retryAfter: 0 };
560
565
  res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-store' });
561
- res.end(loginPageHtml(rl.locked ? 'locked' : false, isPublic, rl.retryAfter, sessionFingerprint));
566
+ res.end(loginPageHtml(rl.locked ? 'locked' : false, isPublic, rl.retryAfter, isPublic ? sessionFingerprint : null));
562
567
  } else {
563
568
  res.writeHead(401, { 'content-type': 'application/json', 'cache-control': 'no-store' });
564
569
  res.end('{"error":"unauthorized"}');
@@ -631,7 +636,7 @@ export function createPocketProxy({ port = 3081, host = '0.0.0.0', upstream = DE
631
636
  // 防密码被钓(issue #82):注入会话指纹 meta(与登录页同源)。
632
637
  // 仅当页面尚无该标记时注入,避免重复;不存在 <head> 时跳过(dsh web 必有)。
633
638
  if (!html.includes(SESSION_MARK)) {
634
- html = html.replace(/<head[^>]*>/i, (m) => `${m}${sessionMeta}`);
639
+ html = html.replace(/<head[^>]*>/i, (m) => `${m}${sessionMeta}${accessMeta}`);
635
640
  }
636
641
  if (!html.includes(INJECT_MARK)) {
637
642
  html = html.replace(/<head[^>]*>/i, (m) => `${m}${injectHtml}`);
package/lib/tunnel.mjs CHANGED
@@ -27,15 +27,15 @@ export const QUICK_TUNNEL_URL_RE = /https:\/\/(?!api\.)[a-z0-9-]+\.trycloudflare
27
27
  *
28
28
  * cloudflared 参数错误(如 "Incorrect Usage: flag provided but not defined")的
29
29
  * 关键信息在输出**开头**,尾部整段都是 usage 帮助文本(对用户没用);而运行期错误
30
- * (403 / 协议 / 网络)的关键信息在**尾部**。所以:参数错误取开头片段,其它仍取尾部。
30
+ * (403 / 协议 / 网络)的关键信息在**尾部**。所以:参数错误取该行,其它仍取尾部。
31
31
  * 最多 500 字符,与历史上限一致。
32
32
  */
33
33
  export function firstMeaningfulErrorLine(buf) {
34
- const text = String(buf ?? '').trim();
35
- if (/^(Incorrect Usage|flag provided but not defined|unknown flag|unknown command)/im.test(text)) {
36
- return text.split(/\r?\n/)[0].trim().slice(0, 500);
37
- }
38
- return text.split(/\r?\n/).slice(-4).join('\n').trim().slice(0, 500);
34
+ const lines = String(buf ?? '').trim().split(/\r?\n/);
35
+ // 命中哪行就返回哪行:cloudflared 先打版本横幅时,参数错误未必是首行
36
+ const usageIdx = lines.findIndex((l) => /^(?:Incorrect Usage|flag provided but not defined|unknown flag|unknown command)/i.test(l.trim()));
37
+ if (usageIdx >= 0) return lines[usageIdx].trim().slice(0, 500);
38
+ return lines.slice(-4).join('\n').trim().slice(0, 500);
39
39
  }
40
40
 
41
41
  function platformBinary() {
package/package.json CHANGED
@@ -80,5 +80,5 @@
80
80
  "access": "public",
81
81
  "registry": "https://registry.npmjs.org/"
82
82
  },
83
- "version": "2.9.0"
83
+ "version": "2.9.1"
84
84
  }