proxyctl 0.5.0__tar.gz → 0.5.1__tar.gz

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 (28) hide show
  1. {proxyctl-0.5.0 → proxyctl-0.5.1}/PKG-INFO +1 -1
  2. {proxyctl-0.5.0 → proxyctl-0.5.1}/pyproject.toml +1 -1
  3. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/explain.py +20 -11
  4. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/suggest_rules.py +48 -20
  5. {proxyctl-0.5.0 → proxyctl-0.5.1}/.gitignore +0 -0
  6. {proxyctl-0.5.0 → proxyctl-0.5.1}/LICENSE +0 -0
  7. {proxyctl-0.5.0 → proxyctl-0.5.1}/README.md +0 -0
  8. {proxyctl-0.5.0 → proxyctl-0.5.1}/man/proxyctl.1 +0 -0
  9. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/__init__.py +0 -0
  10. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/_io.py +0 -0
  11. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/audit.py +0 -0
  12. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/autostart.py +0 -0
  13. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/builtin_plugins/__init__.py +0 -0
  14. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/builtin_plugins/connectivity_basic.py +0 -0
  15. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/builtin_plugins/corp_network.py +0 -0
  16. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/check.py +0 -0
  17. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/cli.py +0 -0
  18. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/completion.py +0 -0
  19. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/core/__init__.py +0 -0
  20. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/core/plugin.py +0 -0
  21. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/engine/__init__.py +0 -0
  22. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/engine/base.py +0 -0
  23. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/engine/mihomo.py +0 -0
  24. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/engine/singbox.py +0 -0
  25. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/status.py +0 -0
  26. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/subscription.py +0 -0
  27. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/suggest.py +0 -0
  28. {proxyctl-0.5.0 → proxyctl-0.5.1}/src/proxyctl/trace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proxyctl
3
- Version: 0.5.0
3
+ Version: 0.5.1
4
4
  Summary: Proxy configuration lifecycle management for macOS and Linux
5
5
  Project-URL: Homepage, https://github.com/crhan/proxyctl
6
6
  Project-URL: Issues, https://github.com/crhan/proxyctl/issues
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "proxyctl"
3
- version = "0.5.0"
3
+ version = "0.5.1"
4
4
  description = "Proxy configuration lifecycle management for macOS and Linux"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1949,24 +1949,33 @@ _SUGGESTION_DOCS: dict[str, dict] = {
1949
1949
  # ── Controller / Engine / Data 5 条 ────────────────────────────────
1950
1950
  "controller.empty_secret": {
1951
1951
  "summary": (
1952
- "Clash API secret 为空字符串。本地连接没影响,但若 controller bind "
1953
- "到非环回地址(见 controller.public_bind),任何人都能调你的 API。"
1952
+ "Clash API bind 公网(0.0.0.0 或局域网 IP)且 secret 为空——"
1953
+ "**任何人都能调你的 API**:切换节点、读取节点列表、改路由。"
1954
+ "v0.5.1+ 此规则仅在公网 bind 时触发;127.0.0.1 + 空 secret 不报。"
1954
1955
  ),
1955
1956
  "edit": (
1956
- " # 在 mihomo config.yaml 加:\n"
1957
- " secret: <一段 >= 16 字符的随机字符串,用 openssl rand -hex 16 生成>"
1957
+ " # 优先:把 bind 改回环回(推荐,本机用够了)\n"
1958
+ " # external-controller: 127.0.0.1:9090\n"
1959
+ " # 或:配强 secret\n"
1960
+ " # secret: <用 openssl rand -hex 24 生成>"
1958
1961
  ),
1959
- "verify": "grep -E '^secret:' ~/.config/mihomo/config.yaml",
1960
- "next_commands": ["explain subscription"],
1962
+ "verify": "grep -E '^secret:|^external-controller:' ~/.config/mihomo/config.yaml",
1963
+ "next_commands": ["explain suggestion:controller.public_bind"],
1961
1964
  },
1962
1965
  "controller.weak_secret": {
1963
1966
  "summary": (
1964
- "Clash API secret 长度 < 16。本地用没事,但若不慎暴露到局域网,"
1965
- "短 secret 容易爆破。"
1967
+ "Clash API bind 公网(0.0.0.0 或局域网 IP)且 secret < 16 字符 → "
1968
+ "暴露面 + 弱认证 = 易爆破。**v0.5.1+ 此规则仅在公网 bind 时触发**;"
1969
+ "bind 127.0.0.1 时 secret 强度无意义(attack surface 不存在),不报。"
1966
1970
  ),
1967
- "edit": " # 用 openssl rand -hex 24 重新生成一段强 secret",
1968
- "verify": "grep -E '^secret:' ~/.config/mihomo/config.yaml",
1969
- "next_commands": ["explain suggestion:controller.empty_secret"],
1971
+ "edit": (
1972
+ " # 优先:bind 改回环回\n"
1973
+ " # external-controller: 127.0.0.1:9090\n"
1974
+ " # 或:升级 secret\n"
1975
+ " # secret: <用 openssl rand -hex 24 生成的强 secret>"
1976
+ ),
1977
+ "verify": "grep -E '^secret:|^external-controller:' ~/.config/mihomo/config.yaml",
1978
+ "next_commands": ["explain suggestion:controller.public_bind"],
1970
1979
  },
1971
1980
  "controller.public_bind": {
1972
1981
  "summary": (
@@ -109,7 +109,18 @@ def _is_public_bind(host: str | None) -> bool:
109
109
 
110
110
 
111
111
  def controller_rules(cfg: dict[str, Any]) -> list[dict[str, Any]]:
112
- """3 条 controller 规则。cfg 来自 inspect_engine_config。"""
112
+ """controller 规则 — v0.5.1 修复复合判定 bug。
113
+
114
+ 哥 2026-05-19 指出的设计问题:"只有本机才能访问为什么还要看密钥复杂度?"
115
+
116
+ 旧逻辑(v0.5.0)独立判断 empty_secret / weak_secret / public_bind,
117
+ 导致 bind=127.0.0.1 + secret 短也会触发 weak_secret advisory ——
118
+ 但本地 attack surface 不存在,规则属于误报。
119
+
120
+ 新逻辑(v0.5.1):以 attack surface 为单一判定轴。
121
+ bind 127.0.0.1 → 任何 secret(含空)都不报
122
+ bind public → 视 secret 强度报 warn / advisory,public_bind 永远 warn
123
+ """
113
124
  if not cfg.get("config_exists"):
114
125
  return []
115
126
  out: list[dict[str, Any]] = []
@@ -117,44 +128,61 @@ def controller_rules(cfg: dict[str, Any]) -> list[dict[str, Any]]:
117
128
  port = cfg.get("controller_port")
118
129
  secret = cfg.get("controller_secret")
119
130
 
120
- # empty_secret —— 空字符串或字段不存在但 controller 已配置
121
- if (host or port) and (secret == "" or secret is None):
122
- # 仅在 controller 已经配置时报告(避免完全没用 API 的用户被打扰)
131
+ # controller 未配置时整组规则跳过
132
+ if not (host or port):
133
+ return out
134
+
135
+ public = _is_public_bind(host)
136
+
137
+ # 127.0.0.1 / ::1 → attack surface 不存在,secret 强度不评估
138
+ if not public:
139
+ return out
140
+
141
+ # ── 以下分支仅当 bind 暴露给非本机时触发 ─────────────────────────
142
+
143
+ # public_bind 本身永远报 warn(明确告诉用户"你的 API 暴露给了外面")
144
+ out.append({
145
+ "id": "controller.public_bind",
146
+ "severity": "warn",
147
+ "actor": "user",
148
+ "title": f"Clash API bind 到非环回地址:{host}",
149
+ "evidence": {"controller_host": host, "controller_port": port},
150
+ "doc": "suggestion:controller.public_bind",
151
+ "since": "0.5.0",
152
+ })
153
+
154
+ if secret == "" or secret is None:
155
+ # 公网 bind + 无认证 = 任何人可控制代理 → warn
123
156
  out.append({
124
157
  "id": "controller.empty_secret",
125
158
  "severity": "warn",
126
159
  "actor": "user",
127
- "title": "Clash API secret 为空",
160
+ "title": f"Clash API bind 公网({host})但 secret 为空",
128
161
  "evidence": {
129
162
  "controller_host": host,
130
163
  "controller_port": port,
131
164
  "secret_set": False,
132
165
  },
133
- "fix_command": "proxyctl explain subscription",
166
+ "fix_command": "proxyctl explain suggestion:controller.empty_secret",
134
167
  "doc": "suggestion:controller.empty_secret",
135
168
  "since": "0.5.0",
136
169
  })
137
- elif secret and len(secret) < 16:
170
+ elif len(secret) < 16:
171
+ # 公网 bind + 弱 secret = 易爆破 → advisory(有 secret 总比无好,
172
+ # 但应该升级)
138
173
  out.append({
139
174
  "id": "controller.weak_secret",
140
175
  "severity": "advisory",
141
176
  "actor": "user",
142
- "title": f"Clash API secret 过短(长度 {len(secret)} < 16)",
143
- "evidence": {"secret_length": len(secret)},
177
+ "title": (f"Clash API bind 公网({host})且 secret 仅 "
178
+ f"{len(secret)} 字符(< 16)"),
179
+ "evidence": {
180
+ "controller_host": host,
181
+ "secret_length": len(secret),
182
+ },
144
183
  "doc": "suggestion:controller.weak_secret",
145
184
  "since": "0.5.0",
146
185
  })
147
-
148
- if _is_public_bind(host):
149
- out.append({
150
- "id": "controller.public_bind",
151
- "severity": "warn",
152
- "actor": "user",
153
- "title": f"Clash API bind 到非环回地址:{host}",
154
- "evidence": {"controller_host": host, "controller_port": port},
155
- "doc": "suggestion:controller.public_bind",
156
- "since": "0.5.0",
157
- })
158
186
  return out
159
187
 
160
188
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes