mihomo-cli 2.2.1 → 2.2.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.2.3] - 2026-05-01
4
+
5
+ ### 修复
6
+
7
+ - **非 TUN 模式误触 sudo**:修复 `start` 命令在 mixed 模式下停止旧进程时强制使用 sudo 的问题,改为自动检测是否存在 root 进程再决定是否提权
8
+
9
+ ---
10
+
11
+ ## [2.2.2] - 2026-05-01
12
+
13
+ ### 修复
14
+
15
+ - **文件描述符泄漏**:修复 `startMixedMode` 中 spawn 后未关闭 fd 的问题
16
+ - **forceSudo 参数失效**:修复 `cleanupAll` 忽略调用方传入的强制 sudo 参数
17
+ - **formatBytes 溢出**:修复超大字节值(>1PB)导致显示 `undefined` 单位
18
+ - **YAML 解析类型检查**:`parseYamlOrJson` 现在拒绝非对象类型的 YAML 内容
19
+ - **spawn 错误处理**:`openUrl` 添加 error 事件处理,防止未捕获异常
20
+ - **UserInfo 类型转换**:移除 `parseUserInfo` 中多余的 `as unknown` 双重转换
21
+
22
+ ### 安全
23
+
24
+ - **订阅名称校验**:新增文件名安全校验,防止路径穿越等不安全名称
25
+
26
+ ---
27
+
3
28
  ## [2.2.1] - 2026-05-01
4
29
 
5
30
  ### 修复
package/dist/index.js CHANGED
@@ -2808,7 +2808,14 @@ function getSubscriptionsWithCache() {
2808
2808
  ...cache[s.name] || {}
2809
2809
  }));
2810
2810
  }
2811
+ var SAFE_NAME_RE = /^[\w\-\p{Unified_Ideograph}]{1,64}$/u;
2812
+ function validateSubscriptionName(name) {
2813
+ if (!name || !SAFE_NAME_RE.test(name)) {
2814
+ throw new Error(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${name}"\uFF0C\u53EA\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u3001\u77ED\u6A2A\u7EBF\u548C\u4E2D\u6587\uFF08\u6700\u957F 64 \u5B57\u7B26\uFF09`);
2815
+ }
2816
+ }
2811
2817
  function addSubscription(url, name = "default") {
2818
+ validateSubscriptionName(name);
2812
2819
  const settings = readSettings();
2813
2820
  const subs = settings.subscriptions || [];
2814
2821
  const existingIndex = subs.findIndex((s) => s.name === name);
@@ -3006,7 +3013,7 @@ function parseYamlOrJson(content, errorMsg) {
3006
3013
  }
3007
3014
  try {
3008
3015
  const result = jsYaml.load(content);
3009
- if (result !== void 0) return result;
3016
+ if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
3010
3017
  } catch {
3011
3018
  }
3012
3019
  try {
@@ -3160,7 +3167,7 @@ function formatBytes(bytes) {
3160
3167
  if (num === 0) return "0 B";
3161
3168
  const k = 1024;
3162
3169
  const sizes = ["B", "KB", "MB", "GB", "TB"];
3163
- const i = Math.floor(Math.log(num) / Math.log(k));
3170
+ const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
3164
3171
  return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
3165
3172
  }
3166
3173
  function formatTimestamp(ts) {
@@ -3405,14 +3412,14 @@ function killAllMihomo(forceSudo = false) {
3405
3412
  }
3406
3413
  }
3407
3414
  }
3408
- function cleanupAll(_forceSudo = false) {
3415
+ function cleanupAll(forceSudo = false) {
3409
3416
  const pids = getAllMihomoPids();
3410
3417
  if (pids.length === 0) {
3411
3418
  clearPid();
3412
3419
  return { killed: 0, failed: 0, remaining: [] };
3413
3420
  }
3414
3421
  const hasRootProcess = pids.some((p) => isProcessRoot(p));
3415
- const needsSudo = hasRootProcess;
3422
+ const needsSudo = forceSudo || hasRootProcess;
3416
3423
  let killedCount = 0;
3417
3424
  const failedPids = [];
3418
3425
  if (needsSudo) {
@@ -3560,12 +3567,12 @@ async function startMixedMode(staleState) {
3560
3567
  const configFile = PATHS.configFile;
3561
3568
  const logFile = PATHS.logFile;
3562
3569
  const args = ["-d", DIRS.data, "-f", configFile];
3563
- const out = fs5.openSync(logFile, "a");
3564
- const err = fs5.openSync(logFile, "a");
3570
+ const logFd = fs5.openSync(logFile, "a");
3565
3571
  const child = spawn(PATHS.mihomoBinary, args, {
3566
3572
  detached: true,
3567
- stdio: ["ignore", out, err]
3573
+ stdio: ["ignore", logFd, logFd]
3568
3574
  });
3575
+ fs5.closeSync(logFd);
3569
3576
  child.unref();
3570
3577
  const pid = child.pid;
3571
3578
  savePid(pid);
@@ -3735,7 +3742,10 @@ function getLogPathByName(name) {
3735
3742
  }
3736
3743
  function openUrl(url) {
3737
3744
  try {
3738
- spawn("open", [url], { stdio: "ignore", detached: true });
3745
+ const child = spawn("open", [url], { stdio: "ignore", detached: true });
3746
+ child.unref();
3747
+ child.on("error", () => {
3748
+ });
3739
3749
  return true;
3740
3750
  } catch {
3741
3751
  return false;
@@ -4983,7 +4993,7 @@ async function cmdStart(args) {
4983
4993
  const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
4984
4994
  console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
4985
4995
  }
4986
- handleStopResult(stop(true));
4996
+ handleStopResult(stop());
4987
4997
  if (hasProcess) {
4988
4998
  console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
4989
4999
  `);
@@ -5018,7 +5028,7 @@ async function cmdStart(args) {
5018
5028
  console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
5019
5029
  console.log("");
5020
5030
  console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
5021
- handleStopResult(stop(true));
5031
+ handleStopResult(stop());
5022
5032
  try {
5023
5033
  configInfo = prepareConfigForStart(targetMode, sub.name);
5024
5034
  const result = await start(targetMode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mihomo-cli",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "type": "module",
5
5
  "description": "A terminal-based mihomo (Clash.Meta) client for macOS",
6
6
  "bin": {