From 902d2782cf1555f9d24067de67a24ac2e2fa437b Mon Sep 17 00:00:00 2001 From: "qwen.ai[bot]" Date: Sat, 25 Apr 2026 14:14:33 +0000 Subject: [PATCH] =?UTF-8?q?Title:=20=E7=BB=A7=E7=BB=AD=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E6=89=80=E6=9C=89=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Key features implemented: - Updated .gitignore to include additional cache and coverage directories (.mypy_cache/, .pytest_cache/, coverage/, htmlcov/) - Enhanced XSS protection in pkg-manager plugin by adding HTML escaping for all user data in page rendering functions - Improved PL injection security in plugin-loader with enhanced static source code analysis including base64 decoding checks and string concatenation bypass detection - Strengthened configuration file loading security using ast.literal_eval for safe parsing and stricter code execution prevention - Added comprehensive dangerous pattern checks in PL injection static analysis to prevent various bypass techniques The updates focus on security hardening across the plugin system, particularly addressing input sanitization and code injection vulnerabilities while expanding the project's ignore patterns for better repository cleanliness. --- .gitignore | 29 +++- oss/shared/__pycache__/router.cpython-312.pyc | Bin 6779 -> 6768 bytes .../__pycache__/main.cpython-312.pyc | Bin 7485 -> 7474 bytes .../__pycache__/main.cpython-312.pyc | Bin 39530 -> 39519 bytes .../__pycache__/main.cpython-312.pyc | Bin 34183 -> 35043 bytes store/@{FutureOSS}/pkg-manager/main.py | 33 ++-- .../__pycache__/main.cpython-312.pyc | Bin 49372 -> 51122 bytes store/@{FutureOSS}/plugin-loader/main.py | 144 +++++++++++++----- 8 files changed, 156 insertions(+), 50 deletions(-) diff --git a/.gitignore b/.gitignore index 726fabc..859e3fa 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,6 @@ __pycache__/ *.pyo *.pyd -# Dependencies -node_modules/ - # Logs and temp files *.log *.tmp @@ -17,12 +14,32 @@ node_modules/ .env.local *.env.* -# Editors -.vscode/ -.idea/ +# Dependencies +.venv/ +venv/ +node_modules/ # Build artifacts dist/ build/ target/ + +# Coverage +.coverage +coverage/ +htmlcov/ + +# Editors +.vscode/ +.idea/ + +# System +.DS_Store +Thumbs.db + +# MyPy +.mypy_cache/ + +# Pytest +.pytest_cache/ ``` \ No newline at end of file diff --git a/oss/shared/__pycache__/router.cpython-312.pyc b/oss/shared/__pycache__/router.cpython-312.pyc index c332a7038f62a836f03d14f98fee09d33f2d04b5..d7976bba300cb260af8da9901e014905835cfc9f 100644 GIT binary patch delta 26 gcmexu^1+1bG%qg~0}$*zypih@6Qjsx4(4KU0Cs!_*#H0l delta 37 rcmexh^4o;#G%qg~0}wP!+{pEbiAP?)JijQrxF9h(Re$qerebjb=rs(X diff --git a/store/@{Falck}/html-render/__pycache__/main.cpython-312.pyc b/store/@{Falck}/html-render/__pycache__/main.cpython-312.pyc index 353bd944b0a58375dfa1798486c5d80bba223f3d..6e7f3395c31e8ef8a36283ef357ec59e5aaaa71e 100644 GIT binary patch delta 26 gcmdmMwaJR>G%qg~0}$*zypii66Qk1R?@Vq|0B)TKDgXcg delta 37 rcmdmFwbzR4G%qg~0}wP!+{pEiiAPtzJijQrxF9h(Re$pvCO0Vn*Jum# diff --git a/store/@{FutureOSS}/log-terminal/__pycache__/main.cpython-312.pyc b/store/@{FutureOSS}/log-terminal/__pycache__/main.cpython-312.pyc index a28856538322eae6f05970834f286b3c0549c873..9cd9557667ff174c40e229fd9ad2d83df62b0e2a 100644 GIT binary patch delta 28 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 delta 3529 zcmaKudvKK16~OQPzJ0&)-iILxkmr{N2`@t+A$EljEd(vJCOcx>ip+2Xbj$JDLQ`@oU+yxUlwL6*L zo_p@O=bm%Vxp(u?lr(w2i?q&Z zV<1~n!_NGKP>KmPYBfU*Ol6#6$jhE#&)7?Cnzm7Fl9hwqVY5)|iu!gmhxiK-Ap01oL^*a>5)p z?;xclX3C^TNEu;{ghkEomAW9Ygyo=R$JK9sn>!(mt>AAh&3kB>hr2vw9##j-zQnbk z{^Z%uo_vO_0CB95PkG&Zsn@7)_td+nfgdU_cUU)%_G&}1P-IZC@>j~YnyD7Gf;Dfc zXms^~5VcT34wJND7ay&*-`b@2M$k5{nqReXruZD2t(h$XFWc!I^wJ&O$sgPelR?@==evT*&K{WQ zrS5c&ZK>}vvE}%t>}fM8U6n4b_ZINU%H}10ufLF-FSPm_E%(ms+f#Z6I@D#ZlGlEvv@3kZYrGS4>y+ImV$Ip7aI;+W|Mpg@} z`H|Yyytu|~84kdxxqgV#Wo~}BE{}gwyP`mJ1iKq%hU>*NT!lffDCA9bE@!4c!p-$p zwQl};od-MRq1s%X*Uj3gk?(9OwRgZ$)(cQ<{Nbi*xDT^U?!uHU*ozI+-4h#RAv6(v zgD(kRv3{-VH4vgDSG(wTzIT1?p?8+Ao^qYEJX$tiQhB;5S+eG&) zf@IIWw~9v6gnUoBT|Q}>&t8%ya=9;UA~zI^eyccLHRoHM^sa&CSz9|_+iTi_KucRn98vV_BQ*$k)D&O7G3=hfE zEkSCJV)`+}FwbiBn)abM%IjLUm=Y)+tODm_5;f! z{ma+K*szvhqoDcA6*xcE1(IT8=qY6kM}lgU9RQh)^JV_h1}qFah;wo@#uO7PLq#qs zEPYX^&p<@Y*dN1*K0dW2pC9*E(^b0epP@z9$I!+yv4bhN8s~>MDV|IUs4*-!XUF*L zrp=}n2r#;xUu-SbmCcu^vLE*l(FwoDaduznKx9k14-X#g&DJjy=rf_N@4XdaHyAQPo_~%9eRpC(b;Z)4yWb8 z$Ou#U<<2tN$K74E_FtjLhY`p4wyt&b5x#4ihfjCq(|hz^cD-+)$MlnT{m!uZ88pF{ zo=qdN5lTAqcOHpQE%2!_B)(AGH6j}>_y6<)+Q z8L$^{cos3kzv{hthd6ZX0+03Yn~(8u_fszP7ZOSA_hIu zq}lHgqCkH@>1lxCWFpc3pWLfxE?!7{@Yw5!HxL&9iVJdThk?lFT~yB_QV8MvEtFnC z^dSC-m_xjccn9$(K-2}`d!d{cc)GumeyDrH*+%*^uIw$LPw-pzu03=bZN)yTP+EgH zgBSuRc2O_(CdessD5fzyNmD2)E|kdo7icTO#~P*#_|UL(sO$hJ=1g_)lEBM99NQCN zVlDPu$~h3s9SrT(qOtCwU~G>Fnf-=mu>u;0cs;zCWlR62dIx)x-uOPclNRE+_Nput z9(_g)fTi$Z9uU||{H9wyyf2mq|H2)Nb<>-K7sG-V_7R$W47kqUr>OlO{x~n%S3wK) zP5X}0-Vf2}B0^k+OCWV9M#fQtC$?}b>=PKmR2gbTz#@Npw0Dm-5L`$;Y6Js}{%ZI~ zdG)A5EA*YCf2H&&Zyl>QiPeXBY-~H7fY~Md(wLL3)IT3hNT~%(SN0w=@HQ_r+D>|$eM3UIMd&v7mCo& zR>XG^UlIPsk>$r5@wI#SVmkx#1StcL<$%Kn?T%; zco1-gFdxdInF>)tEaj17P0PEfaT9%N^%q14X}`QfDC94Q5dJYF>8~DZqtwcCCi9GT z_{EEHWwOvc4IY$2W&#ELN<^VD`=czuyC=s?;w^ZdU!3&Za00ASa%eD?c_78!=!02# z_Hl2a8$|dc(Vqj-GNtrg()u2;zDI1l?f7VUhn3n+?z=+p`*Ke%p#{l;%B0ftHd+6L KiPB<`)_(xa$%O#` diff --git a/store/@{FutureOSS}/pkg-manager/main.py b/store/@{FutureOSS}/pkg-manager/main.py index 70f3022..15b0b08 100644 --- a/store/@{FutureOSS}/pkg-manager/main.py +++ b/store/@{FutureOSS}/pkg-manager/main.py @@ -2,6 +2,7 @@ import os import sys import json +import html import urllib.request from pathlib import Path from oss.logger.logger import Log @@ -112,15 +113,19 @@ class PkgManagerPlugin(Plugin): for pkg_name, info in plugins.items(): status_class = "success" if info.get('enabled', False) else "secondary" status_text = "已启用" if info.get('enabled', False) else "已禁用" + # XSS 防护:对所有用户数据进行 HTML 转义 + safe_pkg_name = html.escape(pkg_name) + safe_version = html.escape(str(info.get('version', '未知'))) + safe_author = html.escape(str(info.get('author', '未知'))) plugin_rows += f""" - {pkg_name} - {info.get('version', '未知')} - {info.get('author', '未知')} + {safe_pkg_name} + {safe_version} + {safe_author} {status_text} - - + + """ @@ -209,15 +214,23 @@ class PkgManagerPlugin(Plugin): plugin_cards = "" for pkg_name, info in available.items(): is_installed = pkg_name in installed - action_btn = f'' if not is_installed else '' + # XSS 防护:对所有用户数据进行 HTML 转义 + safe_pkg_name = html.escape(pkg_name) + safe_name = html.escape(str(info.get('name', pkg_name))) + safe_desc = html.escape(str(info.get('description', '暂无描述'))) + safe_version = html.escape(str(info.get('version', '未知'))) + safe_author = html.escape(str(info.get('author', '未知'))) + # JavaScript 中的字符串也需要转义 + js_safe_pkg_name = pkg_name.replace('\\', '\\\\').replace("'", "\\'").replace('"', '\\"') + action_btn = f'' if not is_installed else '' plugin_cards += f"""
-

{info.get('name', pkg_name)}

-

{info.get('description', '暂无描述')}

+

{safe_name}

+

{safe_desc}

- 版本:{info.get('version', '未知')} - 作者:{info.get('author', '未知')} + 版本:{safe_version} + 作者:{safe_author}
{action_btn} diff --git a/store/@{FutureOSS}/plugin-loader/__pycache__/main.cpython-312.pyc b/store/@{FutureOSS}/plugin-loader/__pycache__/main.cpython-312.pyc index d140b0562b32eb90edc2e389cfac74bb6e539b82..82e2187c5fb847f6c4d80f4b9b239b3e30ebfe42 100644 GIT binary patch delta 7122 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 diff --git a/store/@{FutureOSS}/plugin-loader/main.py b/store/@{FutureOSS}/plugin-loader/main.py index 2ac5ece..ca7600d 100644 --- a/store/@{FutureOSS}/plugin-loader/main.py +++ b/store/@{FutureOSS}/plugin-loader/main.py @@ -213,16 +213,48 @@ class PLInjector: return False def _static_source_check(self, source: str, file_path: str): - """静态源码安全检查""" + """静态源码安全检查 - 增强版,防止字符串拼接/编码绕过""" + import base64 + + # 首先检查是否有 base64 编码的恶意代码 + try: + # 查找所有字符串字面量 + string_pattern = r'([A-Za-z0-9+/=]{20,})' + for match in re.finditer(string_pattern, source): + try: + decoded = base64.b64decode(match.group(1)).decode('utf-8', errors='ignore') + # 检查解码后的内容 + for dangerous in ['import ', 'exec(', 'eval(', 'compile(', 'os.', 'sys.', 'subprocess']: + if dangerous in decoded: + raise PLValidationError(f"{file_path} - 检测到 base64 编码的恶意代码") + except: + pass + except: + pass + + # 检查字符串拼接绕过 (如 'ex' + 'ec') + concat_patterns = [ + r"""['"]ex['"]\s*\+\s*['"]ec['"]""", + r"""['"]impor['"]\s*\+\s*['"]t['"]""", + r"""['"]eva['"]\s*\+\s*['"]l['"]""", + r"""['"]compil['"]\s*\+\s*['"]e['"]""", + ] + for pattern in concat_patterns: + if re.search(pattern, source): + raise PLValidationError(f"{file_path} - 检测到字符串拼接绕过尝试") + forbidden = [ (r'^\s*import\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)', '禁止导入系统级模块'), (r'^\s*from\s+(os|sys|subprocess|shutil|socket|ctypes|cffi|multiprocessing|threading)\s+import', '禁止导入系统级模块'), (r'__import__\s*\(', '禁止使用 __import__'), - (r'exec\s*\(', '禁止使用 exec'), - (r'eval\s*\(', '禁止使用 eval'), - (r'compile\s*\(', '禁止使用 compile'), - (r'open\s*\(', '禁止直接操作文件'), + (r'(? dict: - """加载插件配置文件""" + """加载插件配置文件 - 使用 ast.literal_eval 安全解析""" + import ast cf = plugin_dir / "config.py" if not cf.exists(): return {} @@ -396,43 +429,86 @@ class PluginManager: Log.error("plugin-loader", f"配置文件编码错误:{cf} - {e}") return {} - # 安全检查 - for p in ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess']: + # 严格检查:不允许任何代码执行 + for p in ['import ', 'from ', 'open(', 'exec(', 'eval(', 'compile(', 'os.', 'sys.', 'subprocess', 'lambda', 'def ', 'class ']: if p in content: Log.warn("plugin-loader", f"{cf} 包含危险代码:{p}") return {} - sg = {"__builtins__": {"True": True, "False": False, "None": None, "dict": dict, "list": list, "str": str, "int": int, "float": float, "bool": bool}} - lv = {} + # 尝试使用 ast.literal_eval 安全解析 try: - code = compile(content, str(cf), "exec") - exec(code, sg, lv) - except SyntaxError as e: - Log.error("plugin-loader", f"配置文件语法错误:{cf} - {e}") - return {} - except NameError as e: - Log.error("plugin-loader", f"配置文件名称错误:{cf} - {e}") - return {} - except TypeError as e: - Log.error("plugin-loader", f"配置文件类型错误:{cf} - {e}") - return {} - except Exception as e: - Log.error("plugin-loader", f"配置文件解析失败:{cf} - {type(e).__name__}: {e}") - return {} + result = ast.literal_eval(content) + if isinstance(result, dict): + return {k: v for k, v in result.items() if not k.startswith("_")} + except (ValueError, SyntaxError): + pass - return {k: v for k, v in lv.items() if not k.startswith("_") and not callable(v)} + # 如果失败,尝试提取简单的键值对 + config = {} + for line in content.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', line) + if match: + key, value_str = match.groups() + if key.startswith('_'): + continue + try: + value = ast.literal_eval(value_str) + config[key] = value + except (ValueError, SyntaxError): + Log.warn("plugin-loader", f"{cf} 跳过无效的值:{line}") + continue + return config + def _load_extensions(self, plugin_dir: Path) -> dict: + """加载插件扩展配置 - 使用 ast.literal_eval 安全解析""" + import ast ef = plugin_dir / "extensions.py" - if not ef.exists(): return {} - with open(ef, "r", encoding="utf-8") as f: content = f.read() - for p in ['import ', 'open(', 'exec(', 'eval(', 'os.', 'sys.', 'subprocess']: - if p in content: Log.warn("plugin-loader", f"{ef} 包含危险代码: {p}"); return {} - sg = {"__builtins__": {"True": True, "False": False, "None": None, "dict": dict, "list": list, "str": str, "int": int, "float": float, "bool": bool}} - lv = {} - try: code = compile(content, str(ef), "exec"); exec(code, sg, lv) - except Exception as e: Log.error("plugin-loader", f"扩展文件解析失败: {e}"); return {} - return {k: v for k, v in lv.items() if not k.startswith("_") and not callable(v)} + if not ef.exists(): + return {} + try: + with open(ef, "r", encoding="utf-8") as f: + content = f.read() + except Exception as e: + Log.error("plugin-loader", f"扩展文件读取失败:{e}") + return {} + + # 严格检查:不允许任何代码执行 + for p in ['import ', 'from ', 'open(', 'exec(', 'eval(', 'compile(', 'os.', 'sys.', 'subprocess', 'lambda', 'def ', 'class ']: + if p in content: + Log.warn("plugin-loader", f"{ef} 包含危险代码:{p}") + return {} + + # 尝试使用 ast.literal_eval 安全解析 + try: + result = ast.literal_eval(content) + if isinstance(result, dict): + return {k: v for k, v in result.items() if not k.startswith("_")} + except (ValueError, SyntaxError): + pass + + # 如果失败,尝试提取简单的键值对 + extensions = {} + for line in content.split('\n'): + line = line.strip() + if not line or line.startswith('#'): + continue + match = re.match(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.+)$', line) + if match: + key, value_str = match.groups() + if key.startswith('_'): + continue + try: + value = ast.literal_eval(value_str) + extensions[key] = value + except (ValueError, SyntaxError): + Log.warn("plugin-loader", f"{ef} 跳过无效的值:{line}") + continue + return extensions + def load(self, plugin_dir: Path, use_sandbox: bool = True) -> Optional[Any]: """加载单个插件"""