公司动态
【Bug已解决】[Security] Incomplete Fix for CVE-2026-44513: community Pipeline Branch Bypasses trust_remo…
【Bug已解决】[Security] Incomplete Fix for CVE-2026-44513: community Pipeline Branch Bypasses trust_remote_code Check 解决方案一、现象长什么样diffusers 的trust_remote_code机制是用来防止加载不可信自定义流水线community pipeline时执行任意代码的。按设计只要你没有显式写trust_remote_codeTrue加载任何需要远程代码的 pipeline 都必须被拦下来并抛错# 期望行为trust_remote_code 没开应该被拦 ValueError: Loading ... requires you to execute the modeling file in the repo. Make sure you have read the code and are confident it is safe to run. You can avoid this by passing trust_remote_codeTrue.但 CVE-2026-44513 的修复是不完整的当加载路径走某个特定分支比如本地已经有缓存、或走custom_pipeline字符串而非从 hub 拉取时trust_remote_code的检查被跳过了于是即使trust_remote_codeFalse远程代码仍然被执行了。现象有两种# 症状 A本该抛错却静默成功远程代码已经跑完 pipeline DiffusionPipeline.from_pretrained( some-community/pipeline, trust_remote_codeFalse) # 竟然加载成功 # 症状 B能加载但根本没问过用户同不同意等于安全开关形同虚设这是一个典型的安全回归它不是功能崩了而是安全闸门在某个分支上没关紧用户以为关着的门其实开着。二、背景diffusers 在DiffusionPipeline.from_pretrained里有两条加载自定义流水线的路径标准 hub 路径从模型仓库拉取modeling_*.py这种会走完整的trust_remote_code校验community pipeline 分支通过custom_pipelineorg/name或在缓存已存在时走本地加载这条分支本应同样校验但 CVE 修复只在路径 1 上加了检查路径 2 漏了。trust_remote_code的语义很明确远程代码 会exec仓库里的 Python 文件有执行任意代码的能力。所以加载前必须“要么用户显式同意True要么拒绝加载”。这是个授权检查authorization不是可选项。一旦某个分支绕过它就等于任何能诱导用户加载特定 pipeline 的人都能在他机器上跑代码。为什么是“incomplete fix”因为修复只堵了“从网络拉取时”的那一处没堵“本地缓存命中时”或“custom_pipeline 字符串解析时”的那一处——安全修复最忌讳的就是只补一个入口留下并行入口。三、根因根因是授权检查没有收敛到单一入口存在并行加载分支检查只加在一个分支CVE 补丁在get_class/ hub 下载逻辑里加了if not trust_remote_code: raise但custom_pipeline的处理函数里有另一条直接importlib加载本地文件的路径没加同样的守卫。缓存命中短路了检查当本地snapshots/缓存里已经有这份远端代码时加载逻辑走“直接读本地文件”分支跳过了“是否信任远程代码”的判断因为代码已经“在本地了”。但本地这份代码正是之前从远程来的信任状态不该因为“已经下载过”就失效。trust_remote_code默认语义被分支忽略主路径读kwargs.get(trust_remote_code, False)但 community 分支用了另一个局部变量或默认值导致False没传进去。本质这是授权检查分散在多个加载入口、且缓存状态被错误当作信任状态导致的绕过。和所有“安全修复只补一半”的问题一样根因是缺少一个所有加载路径都必须经过的“信任闸门”。四、最小可运行复现下面用最小代码模拟“两个加载入口只有一个做信任检查”的绕过不真联网用本地文件模拟远程代码importosimportimportlib.utilimporttempfile# 模拟一份“远程”自定义 pipeline 代码危险会执行任意语句remote_code print([evil] remote code executed!) class DummyPipeline: pass tmptempfile.mkdtemp()mod_pathos.path.join(tmp,modeling_dummy.py)withopen(mod_path,w)asf:f.write(remote_code)defload_via_hub_check(mod_path,trust_remote_code):标准 hub 路径做了信任检查。ifnottrust_remote_code:raiseValueError(trust_remote_code must be True to load remote code)specimportlib.util.spec_from_file_location(dummy,mod_path)modimportlib.util.module_from_spec(spec)spec.loader.exec_module(mod)# 执行远程代码returnmoddefload_via_community_branch(mod_path,trust_remote_code):community 分支CVE 修复漏掉的入口没做检查。# 注意这里根本没有读 trust_remote_code直接加载specimportlib.util.spec_from_file_location(dummy,mod_path)modimportlib.util.module_from_spec(spec)spec.loader.exec_module(mod)# 远程代码被执行了returnmod# 用户明确关掉信任trustFalsetry:load_via_hub_check(mod_path,trust)# 正确被拦下exceptValueErrorase:print(hub path blocked:,e)load_via_community_branch(mod_path,trust)# 绕过直接执行了 [evil]运行后会看到[evil] remote code executed!—— 即便trustFalsecommunity 分支依然执行了远程代码。这就是 incomplete fix 的精确缩影。五、解决方案第一层最小直接修复最小修复把信任检查加进那个漏掉的分支并在缓存命中时也重新校验而不是因为“已经在本地”就跳过。importimportlib.utilimportosdef_assert_trusted(trust_remote_code:bool,name:str):所有加载入口都必须先过的信任闸门。ifnottrust_remote_code:raiseValueError(fLoading{name}requires executing remote modeling code. fPass trust_remote_codeTrue only if you trust its source.)defload_via_community_branch_fixed(mod_path,trust_remote_code):_assert_trusted(trust_remote_code,os.path.basename(mod_path))# ← 补上specimportlib.util.spec_from_file_location(dummy,mod_path)modimportlib.util.module_from_spec(spec)spec.loader.exec_module(mod)returnmoddefload_from_cache_fixed(mod_path,trust_remote_code):# 缓存命中不等于信任仍然先过闸门_assert_trusted(trust_remote_code,os.path.basename(mod_path))# ... 再读本地文件这一层改动最小在漏掉的分支和缓存分支各加一行_assert_trusted就能堵住绕过。但它依赖“每个新分支都记得加”下看第二层怎么把闸门收口。六、解决方案第二层结构性改进把“任何加载远程/自定义代码的入口都必须先过信任闸门”固化成单一事实来源。下面这个 dataclass 是信任策略的集中地所有加载函数只通过它的require_trusted方法执行加载从结构上保证不存在能绕过闸门的并行入口。fromdataclassesimportdataclass,fieldfromtypingimportCallable,Dict,Tupleimportimportlib.utilimportosdataclassclassTrustRemoteCodeGuardPolicy:单一事实来源集中管理 trust_remote_code 授权闸门。_loaders:Dict[str,Callable[[str],object]]field(default_factorydict)defregister_loader(self,name:str,loader:Callable[[str],object])-None:self._loaders[name]loaderdefrequire_trusted(self,trust_remote_code:bool,label:str)-None:唯一授权点信任未开启则一律拒绝。ifnottrust_remote_code:raiseValueError(fLoading{label}requires executing remote code. fSet trust_remote_codeTrue only if you trust the source.)defload(self,name:str,mod_path:str,trust_remote_code:bool)-object:# 无论走 hub / community / cache 哪个分支这里都是唯一入口self.require_trusted(trust_remote_code,name)ifnamenotinself._loaders:raiseKeyError(fno loader registered for{name})returnself._loaders[name](mod_path)# 注册各分支的“纯加载器”不含信任逻辑逻辑全在 policy.load 里policyTrustRemoteCodeGuardPolicy()policy.register_loader(hub,lambdap:_exec_module(p))policy.register_loader(community,lambdap:_exec_module(p))policy.register_loader(cache,lambdap:_exec_module(p))def_exec_module(mod_path:str):specimportlib.util.spec_from_file_location(m,mod_path)modimportlib.util.module_from_spec(spec)spec.loader.exec_module(mod)returnmod这一层的关键收益单点授权require_trusted是唯一闸门新增 hub/community/cache 任何分支都只调policy.load不可能绕过信任状态与缓存解耦缓存命中也走load所以“已经下载过”不再等于“自动信任”单一事实来源所有“什么情况下能执行远程代码”的约定都收口在TrustRemoteCodeGuardPolicy安全审计只盯它。七、解决方案第三层断言 / CI 守护把第二层的闸门钉成 pytest挂进 CI确保任何分支都不可能在trust_remote_codeFalse时执行远程代码importosimporttempfileimportpytestfromyour_package.trust_guardimportTrustRemoteCodeGuardPolicy,_exec_moduledef_make_remote_file(tmp_path):ptmp_path/modeling_dummy.pyp.write_text(class DummyPipeline:\n pass\n)returnstr(p)deftest_all_branches_blocked_when_untrusted(tmp_path):# 断言 1hub / community / cache 三个分支在 trustFalse 时全部被拦policyTrustRemoteCodeGuardPolicy()policy.register_loader(hub,lambdap:_exec_module(p))policy.register_loader(community,lambdap:_exec_module(p))policy.register_loader(cache,lambdap:_exec_module(p))path_make_remote_file(tmp_path)forbranchin(hub,community,cache):withpytest.raises(ValueError):policy.load(branch,path,trust_remote_codeFalse)deftest_branch_allowed_when_trusted(tmp_path):# 断言 2trustTrue 时三个分支都能正常加载policyTrustRemoteCodeGuardPolicy()policy.register_loader(hub,lambdap:_exec_module(p))policy.register_loader(community,lambdap:_exec_module(p))policy.register_loader(cache,lambdap:_exec_module(p))path_make_remote_file(tmp_path)forbranchin(hub,community,cache):assertpolicy.load(branch,path,trust_remote_codeTrue)isnotNonedeftest_cache_hit_still_requires_trust(tmp_path):# 断言 3即便“已经在本地缓存”未信任也必须拒绝缓存≠信任policyTrustRemoteCodeGuardPolicy()policy.register_loader(cache,lambdap:_exec_module(p))path_make_remote_file(tmp_path)withpytest.raises(ValueError):policy.load(cache,path,trust_remote_codeFalse)三条断言从“三分支全拦”“信任时放行”“缓存仍须信任”三面把绕过钉死确保 CVE 修复不再“补一半”。八、排查清单遇到trust_remote_code形同虚设、或自定义 pipeline 在False时仍加载时先确认是哪个加载分支是custom_pipeline字符串还是本地缓存命中还是从 hub 拉取找到对应代码路径。在该分支里搜importlib/exec_module/from_pretrained的加载点确认前面有没有trust_remote_code判断。没有就漏了。缓存命中分支最容易漏检查它是否因为“文件已在本地”就跳过了信任判断。缓存状态 ≠ 信任状态。把所有加载入口都改为只经过第二层的policy.load单一闸门删掉散落的if trust_remote_code判断避免多入口不一致。跑第三层 pytest断言hub/community/cache三个分支在False时全抛ValueError。安全修复的原则闸门只允许有一个且必须每个入口都过。任何“只在某一处加检查”的改法都是 incomplete fix。九、小结CVE-2026-44513 的 incomplete fix本质是**trust_remote_code这个授权闸门只加在了 hub 下载分支而 community pipeline / 本地缓存命中分支绕过了它**导致用户明明设了trust_remote_codeFalse远程代码仍被执行。根因是授权检查分散在多个加载入口、且缓存状态被错误当成信任状态。修复分三层——第一层在漏掉的分支和缓存分支各补一行信任断言打通最小闭环第二层用TrustRemoteCodeGuardPolicy这个 dataclass 把所有加载入口收口到唯一的require_trusted闸门从结构上消灭并行绕过第三层用三条 pytest 把“三分支全拦、信任时放行、缓存仍须信任”钉死在 CI。安全心法一句话授权检查只能有一个入口且必须每个加载路径都经过它少一个就是 incomplete fix。