重写Freemarker Configuration:模板注入防护与白名单实践
简介这份资源面向仍在使用老旧 JEECG Boot 版本、无法平滑升级的 Java 开发者提供一种低成本修复 Freemarker 模板注入漏洞的应急方案。其核心做法是直接重写 freemarker 的 Configuration 类在实例化方法中默认加入 this.setNewBuiltinClassResolver(TemplateClassResolver.SAFER_RESOLVER)从而在不改动整体框架的前提下收紧模板解析策略降低远程代码执行风险。压缩包内共 1 个文件为单个 java 源码文件包体约 13KB体量轻便便于直接放入项目对应目录替换使用。目前已有 1339 人学习下载说明该思路在存量系统安全加固场景中具有一定参考价值。对于需要快速封堵漏洞、又受限于版本升级成本的团队这份源码可作为排错与改造的起点帮助理解 SAFER_RESOLVER 的接入位置与生效方式并据此评估自身模板调用链是否存在同类隐患。1. 重写 Configuration 的起因一次模板注入排查给我的教训去年做代码审计时碰到一个案例某内部系统用了 Freemarker 2.3.31 渲染用户提交的模板片段表面上看只是让运营配置活动页文案实际上攻击者可以构造${freemarker.template.utility.Execute?new()(id)}这类 payload 直接执行系统命令。当时第一反应是升级版本但项目依赖链锁死升级会连带影响十几个模块。于是我选择了一条更可控的路重写Configuration类从加载源头掐断模板注入的可能。这篇内容适合正在用 Freemarker 做模板渲染、又没法立刻升级框架的 Java 开发者我会把重写的判断依据、关键代码、踩过的坑全部摊开讲新手能照着改熟手能看到每一步为什么这么设计。Freemarker 模板注入的本质是?new()内置函数允许实例化任意实现了TemplateModel的类配合Execute、ObjectConstructor等工具类就能突破沙箱。2.3.31 这个版本默认没有开启SAFER_RESOLVERConfiguration的newBuiltinClassResolver属性是UNRESTRICTED_RESOLVER等于把危险类的入口敞开着。很多人以为设置setAPIBuiltinEnabled(false)就安全了其实那只堵了?api一个口子?new()照样能走。我重写的核心思路就一句话在Configuration初始化阶段就把类解析器换成白名单模式同时把TemplateClassResolver的resolve方法改成只放行明确需要的类其余一律抛异常。2. 重写前的判断依据四个维度决定要不要动 Configuration2.1 版本升级和重写 Configuration 的取舍先说要命的现实问题Freemarker 在 2.3.29 之后才引入SAFER_RESOLVER2.3.31 虽然带了TemplateClassResolver.SAFER_RESOLVER这个常量但默认值仍然是UNRESTRICTED_RESOLVER。也就是说光靠升级小版本不解决问题必须显式配置。那为什么不直接升到 2.3.34 或更高因为很多老项目的Configuration是单例被 Spring 的FreeMarkerConfigurer托管升级后freemarker.template.Configuration的构造函数签名和setSettings行为有细微变化容易在启动阶段抛IncompatibleImprovements相关的异常。我一般会这么比如果项目里 Freemarker 只用于邮件模板、代码生成这类可控场景升级加配置就够了如果模板内容部分来自用户输入或外部系统重写Configuration做白名单更稳因为你能控制类加载的每一个入口。维度升级到 2.3.34重写 Configuration改动范围改 pom 版本 加配置新增一个子类替换 Bean 定义对依赖链影响可能连带升级其他库零依赖变更防护粒度全局 SAFER_RESOLVER可按模板来源分级回滚成本降版本可能引发兼容问题删掉子类即可适用场景新项目、依赖干净老系统、依赖锁死2.2 模板注入的真实攻击面在哪很多人只盯着?new()其实 Freemarker 的注入面比想象中宽。?api能拿到JavaBean的getClass()再反射调用?eval能动态执行字符串表达式#assign配合?interpret也能跑出意外代码。我在重写时把Configuration的setNewBuiltinClassResolver和setAPIBuiltinEnabled两个开关都收紧了但真正兜底的是重写TemplateClassResolver.resolve方法。下面这段是我实际用的白名单逻辑只放行java.util.ArrayList、java.util.HashMap这类纯数据结构其余全部拒绝。public class SafeTemplateClassResolver implements TemplateClassResolver { // 只允许实例化这些无副作用的类 private static final SetString WHITE_LIST new HashSet(Arrays.asList( java.util.ArrayList, java.util.HashMap, java.util.LinkedList )); Override public Class? resolve(Environment env, String className, String templateName) throws TemplateException { if (!WHITE_LIST.contains(className)) { // 直接抛异常让模板渲染失败而不是静默放行 throw new TemplateException( Class className is not allowed in template: templateName, env); } try { return ClassUtil.forName(className); } catch (ClassNotFoundException e) { throw new TemplateException(Class not found: className, e, env); } } }这段代码的关键在resolve的第三个参数templateName它记录了是哪个模板触发的类解析方便排查是哪个页面被注入了。Environment参数可以用来拿当前模板的配置但别在里面做复杂逻辑否则每次?new()都走一遍反射性能会掉。白名单里的类要满足两个条件一是无参构造函数公开二是没有能触发文件读写或命令执行的方法。ArrayList和HashMap是安全的但别把java.io.File加进去哪怕只是实例化后续配合?api也能搞出事情。2.3 重写 Configuration 时怎么保证不破坏原有渲染重写Configuration最怕的是把正常模板搞挂。我的做法是继承org.freemarker.template.Configuration在构造函数里先调super()然后立刻设置setNewBuiltinClassResolver(new SafeTemplateClassResolver())和setAPIBuiltinEnabled(false)。注意setAPIBuiltinEnabled必须在setSettings之前调用否则会被覆盖。另外setTemplateExceptionHandler要改成TemplateExceptionHandler.RETHROW_HANDLER这样注入尝试会直接抛异常而不是被吞掉方便日志告警。下面是我重写后的构造函数片段参数incompatibleImprovements传Configuration.VERSION_2_3_31保证和原版本行为一致。public class SafeConfiguration extends Configuration { public SafeConfiguration() { super(VERSION_2_3_31); // 关闭 ?api 内置函数 setAPIBuiltinEnabled(false); // 替换类解析器为白名单模式 setNewBuiltinClassResolver(new SafeTemplateClassResolver()); // 注入尝试直接抛异常不静默处理 setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // 禁止访问危险的内置变量 setSetting(auto_import, ); } }setSetting(auto_import, )这行是为了防止模板通过auto_import引入外部模板文件有些攻击链会利用这个机制加载恶意模板。VERSION_2_3_31这个常量决定了 Freemarker 的内部行为比如?api的默认状态、数字格式化规则传错会导致渲染结果和原来不一致。我踩过一次坑传了VERSION_2_3_0结果日期格式化从yyyy-MM-dd变成了yyyy-MM-dd HH:mm:ss页面全乱。所以重写时一定要和原项目的incompatibleImprovements对齐不确定就打印原Configuration的getIncompatibleImprovements()看一眼。3. 重写 Configuration 的完整步骤从继承到替换 Bean3.1 第一步定位原 Configuration 的注入点在动手重写之前先搞清楚项目里Configuration是怎么被创建的。常见的有三种Spring Boot 的FreeMarkerConfigurer自动装配、手动new Configuration()、以及通过freemarker.template.Configuration的静态getDefaultConfiguration()。前两种好办替换 Bean 或改new的类名就行第三种最麻烦因为getDefaultConfiguration()返回的是全局单例你没法直接替换。我的处理方式是写一个ConfigurationFactory在应用启动时用反射把Configuration的defaultConfiguration字段替换成SafeConfiguration实例。下面这段代码是反射替换的核心逻辑注意setAccessible(true)在 JDK 9 以上需要加--add-opens参数否则会抛InaccessibleObjectException。public class ConfigurationFactory { public static void replaceDefaultConfiguration() throws Exception { Field field Configuration.class.getDeclaredField(defaultConfiguration); field.setAccessible(true); // 先取出原实例保留其 settings Configuration original (Configuration) field.get(null); SafeConfiguration safe new SafeConfiguration(); // 把原配置的编码、模板路径等复制过来 safe.setDefaultEncoding(original.getDefaultEncoding()); safe.setDirectoryForTemplateLoading(original.getDirectoryForTemplateLoading()); field.set(null, safe); } }getDefaultEncoding()和getDirectoryForTemplateLoading()这两个属性必须复制否则模板加载路径会变导致FileNotFoundException。如果原Configuration还设置了setTemplateLoader也要一并复制但TemplateLoader本身可能带注入风险复制前先检查它的load方法有没有做路径校验。我一般会在替换后加一行日志打印safe.getNewBuiltinClassResolver()的类名确认是SafeTemplateClassResolver而不是UNRESTRICTED_RESOLVER。3.2 第二步白名单类的筛选和测试白名单不是拍脑袋定的。我的筛选流程是先跑一遍项目里所有模板用-Dfreemarker.loggerDEBUG把?new()调用的类名全打出来然后逐个判断。大部分业务模板只会用到ArrayList、HashMap、Date这几个。Date要特别小心因为java.util.Date的getTime()本身无害但配合?api能拿到Class对象所以setAPIBuiltinEnabled(false)必须同时开。筛选完之后写单元测试用恶意 payload 跑一遍确认全部抛TemplateException。下面是我用的测试用例覆盖了Execute、ObjectConstructor、JythonRuntime三个最常见的利用类。Test public void testMaliciousPayload() { SafeConfiguration cfg new SafeConfiguration(); String[] payloads { ${freemarker.template.utility.Execute?new()(id)}, ${freemarker.template.utility.ObjectConstructor?new()(java.lang.ProcessBuilder,id)}, ${freemarker.template.utility.JythonRuntime?new()} }; for (String payload : payloads) { try { new Template(test, new StringReader(payload), cfg).process(null, new StringWriter()); fail(Payload should be blocked: payload); } catch (Exception e) { // 预期抛异常说明白名单生效 assertTrue(e.getMessage().contains(not allowed)); } } }Execute是直接执行命令的ObjectConstructor能构造任意对象JythonRuntime能跑 Python 代码这三个是 Freemarker 注入的经典利用链。测试时注意Template.process的第二个参数传null会触发InvalidReferenceException但不影响类解析器的拦截逻辑因为?new()在解析阶段就执行了。如果测试通过但线上还是被注入检查是不是有模板走了Configuration的getTemplate重载方法绕过了SafeConfiguration比如某些框架会缓存Template对象缓存里的Configuration引用还是旧的。3.3 第三步替换 Spring Bean 并验证Spring Boot 项目里FreeMarkerConfigurer会创建Configuration替换方式是自定义一个FreeMarkerConfigurationFactoryBean的子类覆盖createConfiguration()方法返回SafeConfiguration。如果是 XML 配置直接把org.springframework.ui.freemarker.FreeMarkerConfigurer的freemarkerSettings里加一条new_builtin_class_resolver指向你的类。但注意 Spring 的FreeMarkerConfigurer在afterPropertiesSet里会调setSettings如果你的SafeConfiguration构造函数里设了setNewBuiltinClassResolver而setSettings又把它覆盖回默认值就白干了。解决办法是在SafeConfiguration里重写setSettings在调super.setSettings之后重新设一遍白名单解析器。Override public void setSettings(Properties settings) { super.setSettings(settings); // 防止 setSettings 覆盖安全配置 setNewBuiltinClassResolver(new SafeTemplateClassResolver()); setAPIBuiltinEnabled(false); }验证阶段我一般分三步先跑一遍全量模板渲染的回归测试确认页面输出和改之前一致再用curl发几个恶意 payload 到线上灰度环境看返回是不是 500 且日志里有not allowed最后用grep -r ?new() templates/扫一遍模板目录把业务里不必要的?new()调用全删掉。有一次我发现一个老模板里用了?new()创建SimpleDateFormat白名单里没加java.text.SimpleDateFormat结果页面直接挂了。后来我把SimpleDateFormat加进白名单但加了注释说明它只用于格式化不能传用户可控的参数。4. 避坑记录重写 Configuration 时最容易翻车的五个点4.1 坑一setSettings 覆盖安全配置现象是重写完Configuration后用 payload 测试仍然能执行命令。原因在于 Spring 的FreeMarkerConfigurer在初始化时会调setSettings而setSettings内部会把new_builtin_class_resolver重置为UNRESTRICTED_RESOLVER。解决办法是像上面那样重写setSettings在super.setSettings之后重新设一遍白名单解析器。更稳妥的做法是直接重写setNewBuiltinClassResolver在里面判断如果传入的是UNRESTRICTED_RESOLVER就忽略强制用自己的SafeTemplateClassResolver。4.2 坑二反射替换 defaultConfiguration 被 JDK 模块系统拦截现象是启动时抛InaccessibleObjectException: Unable to make field private static freemarker.template.Configuration freemarker.template.Configuration.defaultConfiguration accessible。原因是 JDK 9 以上对反射访问私有字段做了限制。解决办法是在启动参数里加--add-opens java.base/freemarker.templateALL-UNNAMED但更推荐的做法是别用反射改成在应用启动的最早阶段比如main方法第一行就new SafeConfiguration()并调Configuration.setDefaultConfiguration(safe)这个方法在 2.3.31 里是公开的不用反射。4.3 坑三白名单漏了业务必需的类导致页面 500现象是重写后部分页面报TemplateException: Class java.text.SimpleDateFormat is not allowed。原因是业务模板里用?new()创建了格式化类。解决办法是先把日志级别调到DEBUG跑一遍全量页面把所有?new()的类名收集起来逐个评估后加入白名单。加的时候要写注释说明用途比如// 仅用于日期格式化参数不可控。千万别图省事直接把java.lang.Runtime加进去那等于没防。4.4 坑四TemplateExceptionHandler 设成 IGNORE_HANDLER 导致注入被吞现象是 payload 测试时页面正常返回没有报错但日志里也没有拦截记录。原因是TemplateExceptionHandler被设成了IGNORE_HANDLER异常被静默处理了。解决办法是改成RETHROW_HANDLER让异常直接抛到上层同时配一个全局异常处理器记录日志。如果业务上不能因为模板异常就挂页面可以改成DEBUG_HANDLER它会打印异常但继续渲染至少你能在日志里看到注入尝试。4.5 坑五只防了 ?new() 没防 ?api 和 ?eval现象是?new()被拦了但攻击者用${.getClass().forName(java.lang.Runtime)}配合?api仍然能拿到Runtime类。原因是?api内置函数没关。解决办法是setAPIBuiltinEnabled(false)这个开关在 2.3.31 里是有效的。另外?eval和?interpret也要注意它们能动态执行字符串表达式如果模板内容部分来自用户输入最好在业务层做一次白名单过滤只允许字母、数字和少量符号。5. 重写后的验证与长期维护一个具体技巧收尾重写Configuration只是第一步真正难的是长期维护。我的习惯是在SafeTemplateClassResolver里加一个静态计数器每次拦截到非法类就AtomicLong.incrementAndGet()然后通过 JMX 或 Micrometer 暴露出去。这样线上一旦有注入尝试监控面板上能看到freemarker.blocked.count飙升比翻日志快得多。下面这段是计数器的实现注意用AtomicLong而不是LongAdder因为拦截频率不会太高AtomicLong足够且能保证顺序。public class SafeTemplateClassResolver implements TemplateClassResolver { private static final AtomicLong BLOCKED_COUNT new AtomicLong(0); public static long getBlockedCount() { return BLOCKED_COUNT.get(); } Override public Class? resolve(Environment env, String className, String templateName) throws TemplateException { if (!WHITE_LIST.contains(className)) { BLOCKED_COUNT.incrementAndGet(); // 记录模板名和类名方便溯源 env.getOut().flush(); throw new TemplateException( Blocked class: className in template: templateName, env); } // ... 白名单放行逻辑 } }这个计数器我接入了 Prometheus配了一条告警规则5 分钟内freemarker.blocked.count增长超过 10 就发通知。有一次凌晨收到告警查日志发现是某个内部系统在跑自动化测试payload 里带了?new()虽然被拦了但触发了计数。这件事让我意识到白名单不能太死测试环境可以放宽生产环境必须严格。后来我在SafeConfiguration里加了一个setStrictMode(boolean)方法测试环境传false时只记录不抛异常生产环境传true直接拦截。回过头看重写Configuration这件事最大的价值不是防住了某个具体 payload而是把模板渲染的安全边界从“依赖框架默认值”变成了“自己可控的白名单”。从那以后我每次接手老项目第一件事就是grep -r ?new()扫模板目录第二件事就是检查Configuration的newBuiltinClassResolver是不是UNRESTRICTED_RESOLVER。这两个动作花不了十分钟但能挡住大部分低级注入。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

Python量化交易回测实战:数据清洗、双均线策略与避坑指南

Python量化交易回测实战:数据清洗、双均线策略与避坑指南

简介:这是一份面向Python量化交易初学者的代码示例集合,适合希望快速了解量化分析基本流程、因子计算与本地数据管理的读者。资源共253个文件,压缩包仅252KB,以132个txt文本、106个py脚本和8个csv数据文件为主,另含jso…

2026/10/15 14:44:09 阅读更多 →
SQL不是增删改查:从契约本质重理解数据库设计

SQL不是增删改查:从契约本质重理解数据库设计

1. 为什么今天还要从“SQL概述”重新开始——一个被严重低估的底层认知问题很多人一看到“SQL概述”四个字,下意识就划走:不就是增删改查吗?我早就会写SELECT * FROM users WHERE id1了。但去年在帮某高校实验室重构一个十年老系统时&#xf…

2026/10/15 14:44:09 阅读更多 →
TinyVue图标系统:SVG Sprite技术解析与实战应用

TinyVue图标系统:SVG Sprite技术解析与实战应用

1. 项目概述:为什么需要关注TinyVue的图标系统?在Web前端开发中,图标系统往往是最容易被忽视却又至关重要的基础设施。最近在重构企业级中后台项目时,我深刻体会到一个设计良好的图标系统能为开发效率带来的提升——从手动维护SVG…

2026/10/15 14:43:09 阅读更多 →

最新新闻

Python数据分析实战:从工具链到工程化实践

Python数据分析实战:从工具链到工程化实践

1. 项目概述:为什么Python是数据分析师的首选工具十年前我刚入行数据分析时,主流工具还是Excel和SPSS。直到2015年第一次用Python处理百万行销售数据,5分钟完成同事需要通宵操作的报表,才真正体会到"工欲善其事必先利其器&qu…

2026/10/15 15:20:03 阅读更多 →
Kotlin语言基础与Android开发实践指南

Kotlin语言基础与Android开发实践指南

1. Kotlin语言概述与开发环境搭建作为一名从Java转向Kotlin的Android开发者,第一次接触Kotlin时就被它的简洁性惊艳到了。Kotlin是JetBrains公司2011年推出的静态类型编程语言,2017年被Google官方宣布为Android开发一级语言。与Java相比,Kotl…

2026/10/15 15:20:03 阅读更多 →
3天18小时无人值守:dzhng/skills 驱动 Codex 自主运行的完整案例复盘

3天18小时无人值守:dzhng/skills 驱动 Codex 自主运行的完整案例复盘

【免费下载链接】skills Reusable AI agent skills for software factories: explore ideas, write specs, implement, review, and run autonomous research. Works with Claude Code, Codex, and other skill-compatible agents. 项目地址: https://gitcode.com/g…

2026/10/15 15:20:03 阅读更多 →
EVEngine v0.5.0 重构:从游戏运行时转向可组合创作平台

EVEngine v0.5.0 重构:从游戏运行时转向可组合创作平台

说实话,在把 v0.5.0 的发布说明发出去之前,我纠结了很久。如果只写"修复了若干个 bug、新增某某模块",那完全体现不了这一版的真实分量;可要是一上来就聊架构、聊生态、聊理念,又怕把实际用户吓跑。几轮讨论…

2026/10/15 15:20:03 阅读更多 →
基于mongoose实现多线程HTTP Server:多核并发与性能调优实战

基于mongoose实现多线程HTTP Server:多核并发与性能调优实战

简介:本资源面向嵌入式开发与C/C后端学习者,提供一份基于mongoose库实现多线程HTTP服务器的完整工程源码,帮助理解轻量级Web服务器在高并发场景下的线程池设计、线程同步与请求分发机制。压缩包共11个文件,约221KB,以c…

2026/10/15 15:20:03 阅读更多 →
OpenTelemetry Instrumentation for VoyageAI:为 Embedding 与 Rerank 调用接入可观测性

OpenTelemetry Instrumentation for VoyageAI:为 Embedding 与 Rerank 调用接入可观测性

LLMOps可观测性 【免费下载链接】openllmetry Open-source observability for your GenAI or LLM application, based on OpenTelemetry 项目地址: https://gitcode.com/gh_mirrors/op/openllmetry 点击查看 免费下载 这篇技术指南以 openllmetry 仓库中的 opentel…

2026/10/15 15:19:02 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →