Skip to content

Commit 3068ae7

Browse files
committed
fix(trae): 回调基址判定用 ipaddress,避开 bandit B104 误报
'0.0.0.0' 字面量在 bandit 眼里是「绑定全网卡」(B104),这里只是主机 字符串比对。改为 ipaddress 判定回环 / 未指定地址,同时把 ::(IPv6 通配) 也纳入,测试补 localhost 与非回环 IP 两条分支。
1 parent c3b295d commit 3068ae7

2 files changed

Lines changed: 25 additions & 3 deletions

File tree

‎src/api/admin_auth.py‎

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from __future__ import annotations
88

99
import inspect
10+
import ipaddress
1011
import time
1112

1213
from fastapi import APIRouter, Depends, Request
@@ -39,7 +40,7 @@
3940
# 浏览器打不开的基址主机:回环与通配监听。PUBLIC_BASE_URL 默认值就是
4041
# 127.0.0.1:8000,远程/容器部署忘了改时,回调会跳到**用户自己这台机器**,
4142
# 浏览器既回不到服务也不报错(issue #21)。
42-
_UNREACHABLE_BASE_HOSTS = frozenset({"127.0.0.1", "localhost", "::1", "0.0.0.0"})
43+
_LOCALHOST_NAMES = frozenset({"localhost"})
4344

4445

4546
def _host_of(authority: str) -> str:
@@ -50,6 +51,17 @@ def _host_of(authority: str) -> str:
5051
return text.split(":", 1)[0].lower()
5152

5253

54+
def _browser_cannot_reach(host: str) -> bool:
55+
"""回环 / 通配监听:浏览器打开它到不了本服务(或到不了发请求的那个人)。"""
56+
if host in _LOCALHOST_NAMES:
57+
return True
58+
try:
59+
address = ipaddress.ip_address(host)
60+
except ValueError: # 普通域名 / 无效字面量:按可达处理
61+
return False
62+
return address.is_loopback or address.is_unspecified
63+
64+
5365
def _request_origin(request: Request) -> str:
5466
"""当前请求的浏览器 origin(采信反代/隧道的 X-Forwarded-Proto)。"""
5567
proto = request.headers.get("x-forwarded-proto", "").split(",", 1)[0].strip().lower()
@@ -73,9 +85,9 @@ def resolve_public_callback_url(settings: Settings,
7385
`host_allowed` 白名单,故这里采信 Host 头不会放大攻击面。
7486
"""
7587
base = settings.public_base_url.rstrip("/")
76-
if request is not None and _host_of(base) in _UNREACHABLE_BASE_HOSTS:
88+
if request is not None and _browser_cannot_reach(_host_of(base)):
7789
request_host = _host_of(request.headers.get("host", "") or request.url.netloc)
78-
if request_host and request_host not in _UNREACHABLE_BASE_HOSTS:
90+
if request_host and not _browser_cannot_reach(request_host):
7991
base = _request_origin(request).rstrip("/")
8092
return base + "/authorize"
8193

‎tests/api/test_authorize.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -400,6 +400,11 @@ def test_resolve_callback_url_uses_request_origin_for_unreachable_base():
400400
PUBLIC_BASE_URL="http://0.0.0.0:8000")
401401
assert resolve_public_callback_url(wildcard, _request("10.0.0.2:8000")) == \
402402
"http://10.0.0.2:8000/authorize"
403+
# localhost 字面量同理
404+
named = Settings(_env_file=None, APP_SECRET=SECRET,
405+
PUBLIC_BASE_URL="http://localhost:8000")
406+
assert resolve_public_callback_url(named, _request("10.0.0.2:8000")) == \
407+
"http://10.0.0.2:8000/authorize"
403408

404409

405410
def test_resolve_callback_url_keeps_configured_base():
@@ -416,6 +421,11 @@ def test_resolve_callback_url_keeps_configured_base():
416421
PUBLIC_BASE_URL="https://gw.example")
417422
assert resolve_public_callback_url(explicit, _request("192.168.1.5:8000")) == \
418423
"https://gw.example/authorize"
424+
# 非回环 IP 基址:本来就是浏览器可达的,不为请求 origin 让路
425+
lan_base = Settings(_env_file=None, APP_SECRET=SECRET,
426+
PUBLIC_BASE_URL="http://10.0.0.9:8000")
427+
assert resolve_public_callback_url(lan_base, _request("192.168.1.5:8000")) == \
428+
"http://10.0.0.9:8000/authorize"
419429

420430

421431
def _mock_trae() -> TraeProvider:

0 commit comments

Comments
 (0)