diff --git a/confs/README.md b/confs/README.md index 755fe34..9d20c2f 100644 --- a/confs/README.md +++ b/confs/README.md @@ -181,6 +181,7 @@ port = resp["data"]["value"] - **扩展名白名单**:仅允许 `.ini/.conf/.cfg/.json/.yaml/.yml/.toml/.txt`,其余一律拒绝 - **路径遍历防护**:配置名必须等于 `os.path.basename(name)`,且 realpath 必须位于 `configs/` 目录前缀之下 +- **文件大小限制**:单配置文件上限 1 MiB(`MAX_CONFIG_SIZE`),超过则拒绝读取并返回错误,防止超大文件导致内存溢出 - **仅监听本机**:默认 `127.0.0.1`,不对外暴露,仅供内部服务调用 - **无鉴权**:当前无身份校验,依赖"仅本机 + 内部网络"的信任边界;如需承载敏感配置(如 API 密钥),应额外评估鉴权方案 @@ -224,5 +225,6 @@ confs/ - 配置文件需手动放入 `configs/` 目录(或用 `--dir` 指定其他目录),服务不会自动生成业务配置 - 文件以 UTF-8 读取,非 UTF-8 文件会返回 `配置读取失败` 错误 - 配置名区分大小写(与文件系统一致) +- 单配置文件大小上限为 1 MiB(`MAX_CONFIG_SIZE`),超过此限制会返回 `配置文件过大` 错误,防止超大文件读入内存导致 OOM - 缓存基于文件 mtime 判断变化,若同秒内修改且 mtime 精度不足(如某些 FAT 文件系统),可能无法及时重载——可手动调 `reload` 路由强制刷新 - 当前接入情况见上文"集成关系 → 已接入的服务";新服务接入可参考 static 的三层 fail-safe 模式(内置默认 → confs 优先 → 本地回退) diff --git a/confs/confs.py b/confs/confs.py index 4e066cf..590c88d 100644 --- a/confs/confs.py +++ b/confs/confs.py @@ -42,6 +42,8 @@ from log_client import logs # noqa: E402 CONFIG_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "configs") # 允许分发的配置文件类型(白名单) ALLOWED_EXT = {".ini", ".conf", ".cfg", ".json", ".yaml", ".yml", ".toml", ".txt"} +# 单配置文件大小上限(1 MiB),防止超大文件读入内存导致 OOM +MAX_CONFIG_SIZE = 1 * 1024 * 1024 # ── 异常 ──────────────────────────────────────────────────────────────── @@ -103,13 +105,19 @@ class ConfigStore: path = self._resolve(name) with self._lock: try: - mtime = os.path.getmtime(path) + st = os.stat(path) + mtime = st.st_mtime + size = st.st_size except OSError: self._cache.pop(name, None) raise ConfigNotFound(f"配置不存在或不可读: {name}") from None entry = self._cache.get(name) if entry is not None and entry["mtime"] == mtime: return entry + if size > MAX_CONFIG_SIZE: + raise ConfigError( + f"配置文件过大: {name}({size} 字节,上限 {MAX_CONFIG_SIZE} 字节)" + ) try: with open(path, "r", encoding="utf-8") as f: content = f.read()