phone-remote/app/security.py(86 行)+ app/actions.py(554 行)或手机丢失/二维码泄露导致的风险。
闸① 常量时间令牌比较 + FastAPI 依赖注入(security.py 第 24-38 行):
def verify_token(authorization: str | None = Header(default=None)) -> None:
if not authorization or not authorization.startswith("Bearer "):
raise HTTPException(status_code=401, detail="missing or invalid authorization header")
presented = authorization[len("Bearer "):].strip()
if not hmac.compare_digest(presented, settings.token): # 防时序侧信道
raise HTTPException(status_code=401, detail="invalid token")
require_token = Depends(verify_token)
闸② 网段中间件(第 70-86 行):只保护 /api,ipaddress 判断 CIDR 白名单;
解析不出 IP 直接 403。
闸③ 动作白名单(actions.py 第 537-554 行):
def execute(action_id: str, params: dict | None = None) -> dict:
"""执行白名单动作。任何不在白名单里的请求都会在这里被挡下。"""
action = ACTIONS.get(action_id)
if action is None:
raise KeyError(f"action not allowed: {action_id}")
unknown = set(params) - set(action.params)
if unknown: # 传了没声明的参数 → 拒绝
raise ValueError(f"unexpected params: {sorted(unknown)}")
return {"action_id": action.id, "result": action.run(params), ...}
闸④ 参数白名单(第 42-63、100-128 行):
PROCESS_NAME_RE = re.compile(r"^[\w.\- ]{1,64}$") # 挡掉路径/引号/分号
PRESET_RE = re.compile(r"^(daily|mirror|full|reward)$") # 只允许预设档位
REGISTERED_SCRIPTS: dict[str, list[str]] = {} # 脚本登记制,值为参数列表
def _script_run(params: dict) -> dict:
key = str(params.get("key", "")).strip()
if key not in REGISTERED_SCRIPTS:
raise ValueError(f"unknown script key: {key!r}(可用:{list(REGISTERED_SCRIPTS) or '无'})")
completed = subprocess.run(REGISTERED_SCRIPTS[key], shell=False, timeout=SUBPROCESS_TIMEOUT, ...)
hmac.compare_digest 防时序攻击(面试高频)默认列表为空——宁可不启用,也不留宽松口子
shell=False + 超时:不经过 shell,超时强杀script.run 对模型永不开放,解析阶段零副作用
tests/test_safety.py + test_gates.py + test_signed_file.py(189 项测试里的一部分)localguard:令牌中间件 + 网段白名单 + 白名单动作注册器第 16 课(安全设计)