GitHub 扩大恶意依赖告警后,我给 npm 项目加了一套安装前安全检查
很多开发者对 npm 依赖安全的理解还停留在npm audit只要没有高危漏洞就认为依赖没有问题。但已知漏洞和恶意软件包并不是同一类风险。一个包可能没有公开 CVE却在安装阶段执行恶意脚本也可能刚发布几个小时还没有进入常规漏洞数据库。2026 年 7 月 28 日GitHub 扩大了 Dependabot 的恶意软件包告警数据来源将 OpenSSF malicious-packages 项目的恶意包信息自动引入 GitHub Advisory Database覆盖 npm、PyPI 等更多生态。开启 Malware alerts 的仓库会自动获得更广的检测范围。这次更新提醒了一个很现实的问题npm install不只是下载代码它还可能执行第三方代码。npm 依赖可以定义preinstall install postinstall prepare这些脚本会在依赖安装过程中运行。脚本不一定是 JavaScript也可以调用 Shell、Python、编译工具或其他系统命令。所以今天不谈抽象的供应链理论直接给 Node.js 项目建立一套可用的安全安装流程。一、为什么npm audit不够npm audit主要根据依赖树查询已知安全漏洞并返回修复建议。它很重要但检测范围主要是已经进入漏洞数据库的问题。下面这些情况单靠普通漏洞扫描不一定能及时发现刚发布不久的恶意包 名字与知名包非常相似的仿冒包 被接管后重新发布的正常包 利用 postinstall 脚本窃取环境变量 从非标准 Registry 下载的依赖 直接引用 Git 仓库的依赖 本地 file: 依赖被替换 锁文件被恶意篡改例如一个依赖可能包含{ scripts: { postinstall: node scripts/setup.js } }而setup.js中可以执行import fs from node:fs; const env process.env; fs.writeFileSync( /tmp/project-env.json, JSON.stringify(env), );这段代码并没有利用传统软件漏洞。它只是利用了安装脚本可以在开发者权限下运行这一事实。因此需要把依赖安全拆成几层第一层固定依赖版本 第二层检查依赖来源 第三层限制安装脚本 第四层验证签名和来源证明 第五层接入恶意包与漏洞告警二、第一层生产和 CI 使用npm ci开发环境里很多人习惯执行npm install但 CI 和生产构建更推荐npm cinpm ci要求项目已经存在package-lock.json如果package.json与锁文件中的依赖不一致它会直接失败而不是自动修改锁文件。这可以避免 CI 在不同时间解析出不同依赖版本。推荐流程git diff --exit-code package-lock.json npm ci更严格一点npm ci --ignore-scripts--ignore-scripts会阻止依赖安装过程中的生命周期脚本执行。需要注意显式执行的npm test、npm start和npm run仍然会运行对应脚本只是不会自动执行关联的 pre、post 脚本。这适合先完成“只下载、不执行”的第一阶段。三、不要忽略package-lock.json里的信息现代package-lock.json不只是版本列表。它还记录了resolved integrity hasInstallScript link inBundle其中resolved表示依赖实际下载地址integrity用于校验下载内容hasInstallScript表示该依赖包含安装脚本link表示本地链接依赖。npm 官方文档明确说明hasInstallScript用于标记依赖是否存在preinstall、install或postinstall脚本。因此我们可以在安装前直接扫描锁文件。创建scripts/check-dependency-lock.mjs写入#!/usr/bin/env node import fs from node:fs; import path from node:path; import process from node:process; const lockPath path.resolve( process.cwd(), package-lock.json, ); if (!fs.existsSync(lockPath)) { console.error(BLOCK项目缺少 package-lock.json); process.exit(1); } let lock; try { lock JSON.parse( fs.readFileSync(lockPath, utf8), ); } catch (error) { console.error( BLOCK无法解析 package-lock.json${error.message}, ); process.exit(1); } if (!lock.packages) { console.error( BLOCK当前锁文件不包含 packages 字段请先更新锁文件版本, ); process.exit(1); } const results []; function getPackageName(packagePath) { const marker node_modules/; const index packagePath.lastIndexOf(marker); if (index -1) { return packagePath; } return packagePath.slice( index marker.length, ); } function classifySource(resolved ) { if (!resolved) { return unknown; } if (resolved.startsWith(file:)) { return local-file; } if ( resolved.startsWith(git) || resolved.startsWith(github:) || resolved.startsWith(gitlab:) ) { return git; } if (resolved.startsWith(http://)) { return insecure-http; } if ( resolved.startsWith( https://registry.npmjs.org/, ) ) { return npm-registry; } if (resolved.startsWith(https://)) { return external-https; } return other; } for ( const [packagePath, metadata] of Object.entries(lock.packages) ) { if (!packagePath) { continue; } if ( !packagePath.includes( node_modules/, ) ) { continue; } const name getPackageName(packagePath); const source classifySource( metadata.resolved, ); const warnings []; if (metadata.hasInstallScript) { warnings.push(包含安装脚本); } if ( source git || source local-file || source external-https || source insecure-http ) { warnings.push( 非标准依赖来源${source}, ); } if ( source insecure-http ) { warnings.push(使用非加密 HTTP); } if ( metadata.resolved source ! local-file source ! git !metadata.integrity ) { warnings.push(缺少 integrity); } if (warnings.length 0) { results.push({ name, version: metadata.version || unknown, source, resolved: metadata.resolved || -, warnings, }); } } if (results.length 0) { console.log( PASS未发现安装脚本或异常依赖来源, ); process.exit(0); } console.log( 发现 ${results.length} 个需要检查的依赖\n, ); for (const item of results) { console.log( ${item.name}${item.version}, ); console.log( 来源${item.source}, ); console.log( 下载${item.resolved}, ); console.log( 问题${item.warnings.join(、)}, ); console.log(); } const blocked results.some( (item) item.source insecure-http || item.warnings.includes( 缺少 integrity, ), ); if (blocked) { console.error( BLOCK发现必须人工处理的依赖风险, ); process.exit(1); } console.warn( WARNING请检查以上依赖后再执行安装脚本, ); process.exit(0);运行node scripts/check-dependency-lock.mjs可能输出发现 3 个需要检查的依赖 esbuild0.25.8 来源npm-registry 问题包含安装脚本 sharp0.34.3 来源npm-registry 问题包含安装脚本 internal-utils1.0.0 来源git 问题非标准依赖来源git注意存在安装脚本不代表依赖有问题。例如某些原生模块需要下载二进制文件或执行编译。脚本的作用是提醒开发者这个依赖安装时会执行代码需要明确审核。四、用npm query找出安装脚本如果依赖已经安装也可以使用 npm 自带查询功能。查询包含postinstall的依赖npm query \ :attr(scripts, [postinstall])只输出名称npm query \ :attr(scripts, [postinstall]) \ | jq -r .[].namenpm 官方文档也给出了通过npm query查找包含postinstall脚本依赖的示例。还可以分别检查npm query \ :attr(scripts, [preinstall]) npm query \ :attr(scripts, [install]) npm query \ :attr(scripts, [postinstall])不过最好不要等安装完成后才检查。锁文件扫描更适合放在 CI 的安装前阶段。五、新版 npm 开始管理依赖安装脚本新版 npm 提供了npm approve-scripts npm deny-scripts它们用于管理项目package.json中的allowScripts字段。npm approve-scripts会记录哪些依赖被允许执行安装脚本并且默认可以将批准范围固定到具体版本。先查看仍未审批的依赖npm approve-scripts \ --allow-scripts-pending批准一个具体依赖npm approve-scripts esbuild批准后package.json中会出现类似配置{ allowScripts: { esbuild0.25.8: true } }默认固定具体版本比写成{ allowScripts: { esbuild: true } }更安全。因为后者意味着未来所有版本都自动继承批准。明确拒绝某个依赖npm deny-scripts suspicious-package对应配置类似{ allowScripts: { suspicious-package: false } }npm 12 的文档说明依赖安装脚本默认会被阻止未审批依赖需要明确允许拒绝项也可以写入allowScripts避免后续被批量批准。对于旧版 npm可以继续采用npm ci --ignore-scripts审核后再单独执行npm rebuild esbuild npm rebuild sharpnpm rebuild会重新执行指定依赖的安装生命周期脚本适合在最初使用--ignore-scripts后有选择地构建必要依赖。六、验证 Registry 签名和 Provenance完成依赖安装后再执行npm audit signatures该命令会验证 Registry 签名和包的 Provenance 证明。如果签名或证明缺失、无效命令会返回错误提示包可能存在来源或完整性问题。在 CI 中可以直接写npm audit signatures需要 JSON 结果npm audit signatures --json如果要包含完整的 Sigstore 证明数据npm audit signatures \ --json \ --include-attestations官方配置文档说明include-attestations会在 JSON 输出中包含 DSSE、验证材料和透明日志数据。但要注意签名验证主要证明下载内容没有被中途篡改 发布来源可以被验证 构建和发布链路具备证明它不能证明包的代码一定安全。一个作者主动发布的恶意包也可以拥有有效签名。所以签名验证必须和恶意包告警、代码审查一起使用。七、开启 GitHub Dependabot Malware Alerts在 GitHub 仓库中进入Settings → Code security → Dependabot → Malware alertsGitHub 这次更新会将 OpenSSF malicious-packages 数据引入 Advisory Database。已经开启 Malware alerts 的仓库无需额外配置就会自动获得扩展后的检测范围。还可以在 GitHub Advisory Database 中使用type:malware筛选恶意软件包公告。这里建议同时开启Dependabot alerts Malware alerts Dependabot security updates Dependency graph不要只开自动更新。自动更新解决的是版本升级问题恶意包检测解决的是依赖本身是否已被标记为恶意两者不是一回事。八、完整 CI 安全流程创建.github/workflows/dependency-security.yml写入name: Dependency Security on: pull_request: paths: - package.json - package-lock.json - scripts/check-dependency-lock.mjs push: branches: - main jobs: dependency-security: runs-on: ubuntu-latest permissions: contents: read steps: - name: Checkout uses: actions/checkoutv4 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 24 cache: npm - name: Check lockfile run: | node scripts/check-dependency-lock.mjs - name: Install without lifecycle scripts run: | npm ci --ignore-scripts - name: Audit known vulnerabilities run: | npm audit --audit-levelhigh - name: Verify signatures run: | npm audit signatures - name: Run project checks run: | npm run lint npm run typecheck npm test这个流程的重点是先扫描锁文件 再禁止脚本安装 然后检查漏洞和签名 最后运行项目测试如果项目确实依赖原生编译或安装脚本可以在审核后增加- name: Rebuild approved dependencies run: | npm rebuild esbuild npm rebuild sharp不要简单执行npm rebuild否则所有依赖的安装脚本可能重新运行。九、新增依赖时不要直接执行npm install xxx团队可以统一采用以下流程。第一步只更新锁文件npm install package-name \ --package-lock-only \ --ignore-scripts第二步查看 Diffgit diff -- package.json package-lock.json重点检查新增了多少间接依赖 是否出现 Git 或 file: 来源 是否出现非官方 Registry 是否出现缺少 integrity 的依赖 哪些依赖带有 hasInstallScript 锁文件是否出现异常大范围变化第三步执行扫描node scripts/check-dependency-lock.mjs第四步无脚本安装npm ci --ignore-scripts第五步漏洞与签名检查npm audit --audit-levelhigh npm audit signatures第六步审批必要脚本npm approve-scripts \ --allow-scripts-pending再批准确实需要的包。第七步运行测试npm run lint npm run typecheck npm test这套流程比npm install package-name git add . git commit慢几分钟。但它能让依赖变化从“黑盒下载”变成可审查的工程变更。十、不要把package-lock.json当成无意义的大文件不少团队 Review PR 时会认真看业务代码却直接跳过锁文件。实际上依赖攻击经常就藏在锁文件变化里。至少检查1. 依赖版本是否符合预期原计划增加一个包却升级了几十个无关依赖需要确认原因。2. 下载地址是否改变例如从https://registry.npmjs.org/变成https://example-cdn.invalid/ githttps://... file:../local-package3. integrity 是否改变package-lock.json中的integrity用于验证下载内容。Registry 依赖通常会记录对应完整性值。4. 是否新增安装脚本锁文件中的{ hasInstallScript: true }意味着这个依赖在安装阶段会执行代码。5. 是否出现 Git 分支依赖例如{ resolved: githttps://github.com/example/repo.git#main }使用可变分支比固定 Commit 风险更大。至少固定具体 Commitgithttps://github.com/example/repo.git#具体提交哈希十一、几个常见误区误区一有package-lock.json就绝对安全锁文件只能固定依赖和验证下载内容。如果锁定的版本本身就是恶意包锁文件不会自动保护你。误区二npm audit没报警就安全它主要检查已知漏洞。恶意脚本、可疑来源和刚发布的恶意版本需要额外检测。误区三所有安装脚本都危险不少正常依赖确实需要编译原生扩展或下载平台二进制文件。正确做法不是全部禁止而是默认阻止 逐个审核 固定版本批准 变更后重新审批误区四签名有效就代表代码安全签名证明来源和完整性不等于证明发布者没有恶意行为。误区五只检查直接依赖真正的风险可能来自第五层甚至第十层间接依赖。锁文件和 Dependabot 会比只看package.json更完整。十二、推荐落地清单可以先从下面这套最小方案开始[ ] 提交 package-lock.json [ ] CI 使用 npm ci [ ] 默认使用 --ignore-scripts [ ] 扫描 hasInstallScript [ ] 拦截 HTTP 和异常 Registry [ ] 检查缺失 integrity [ ] 运行 npm audit [ ] 运行 npm audit signatures [ ] 开启 Dependabot Malware alerts [ ] 安装脚本逐包审批 [ ] 依赖变更必须人工 Review对于高风险项目再增加私有 Registry 依赖代理缓存 允许依赖白名单 SBOM 许可证检查 构建环境网络隔离 短期凭据 可信发布与 OIDCnpm 的 Trusted Publishing 支持通过 CI/CD 和 OIDC 发布软件包减少长期 npm Token 的使用也可以进一步降低发布链路中的凭据风险。十三、AI 写代码越快依赖安全越不能省现在很多 AI 编程工具会在实现功能时自动建议npm install 某个包问题是Agent 的目标通常是尽快完成任务。它不一定会主动检查这个包是谁维护的 最近是否转移所有权 有没有安装脚本 是否存在仿冒名称 依赖树增加了多少包 是否支持签名验证 是否已被标记为恶意软件因此不能把依赖选择完全交给模型。更合理的要求是不要直接安装依赖。 先说明 1. 为什么需要新增依赖 2. 是否可以使用现有能力实现 3. 包的维护状态和替代方案 4. 是否包含安装脚本 5. 将增加多少间接依赖 6. 许可证和运行环境要求。 等待人工确认后只更新 package.json 和 package-lock.json。但最终的安全控制仍然应该由 CI、锁文件和脚本完成而不是依赖提示词。十四、工具订阅不能替代项目安全长期使用 ChatGPT Plus、Claude Pro、Cursor、Kiro 等 AI 编程工具时可以通过 gpt68.com 了解相关第三方 AI 会员充值服务。需要说明的是gpt68.com 解决的是订阅充值流程问题不是替代工具本身也不是相关产品的官方网站或授权合作方不提供共享账号。无论使用哪个 AI 编程工具依赖安全、密钥管理、测试和代码 Review 都需要项目自身负责。总结GitHub 扩大恶意依赖告警范围以后Node.js 项目可以立即补上几道防线用 package-lock.json 固定依赖 用 npm ci 保证可复现 用 --ignore-scripts 阻止自动执行 扫描 hasInstallScript 和异常来源 用 approve-scripts 逐包批准 用 npm audit signatures 验证来源 开启 Dependabot Malware alerts最重要的思路不是永远不要使用第三方依赖。而是任何新增依赖都必须被当成一段即将进入项目并可能执行的外部代码。业务代码需要 Review。依赖变化同样需要 Review。当 AI 和自动化工具让开发者添加依赖越来越容易时供应链检查反而应该成为默认流程而不是发生事故后的补救措施。

相关新闻

构建高质量Android APK样本库:从AndroZoo数据获取到自动化管理实践

构建高质量Android APK样本库:从AndroZoo数据获取到自动化管理实践

1. 项目概述:为什么我们需要一个高质量的APK样本库?在移动安全研究、恶意软件分析、应用行为检测模型训练,甚至是合规性测试等领域,一个高质量、标注清晰的Android应用(APK)样本库是至关重要的基础设施。无…

2026/10/11 20:40:01 阅读更多 →
GEE平台全球农田分布数据应用:从宏观统计到农业水资源压力评估

GEE平台全球农田分布数据应用:从宏观统计到农业水资源压力评估

1. 项目缘起:为什么我们需要一张全球农田地图?作为一名长期与遥感数据打交道的从业者,我经常被问到这样一个问题:“有没有一张现成的、能直接用的全球农田分布图?” 无论是做全球粮食安全评估、农业水资源管理&#xf…

2026/10/11 20:39:38 阅读更多 →
质量知识专题 | 质量管理体系

质量知识专题 | 质量管理体系

本文严格对标高校本科《质量管理学》课程大纲、ISO9001:2015国际标准,搭建一套可背诵、可落地、可数字化改造的完整体系知识框架,同时覆盖学生应试、工程师落地、管理者体系升级三类核心需求。 无论你是刚接触质量管理的学生,还是正在推动企业…

2026/10/2 7:48:21 阅读更多 →

最新新闻

scale_up协议光链路可靠性设计:从感知到自愈的全栈协同

scale_up协议光链路可靠性设计:从感知到自愈的全栈协同

1. 项目概述:光链路可靠性不是“加个备份”就能解决的事“scale_up协议中针对光链路的可靠性设计”——这个标题乍看是通信协议层的技术细节,但背后牵动的是整个高速互连系统的命脉。我接触过多个采用scale_up架构的高性能计算集群项目,其中超…

2026/10/11 20:39:26 阅读更多 →
融光 ai-fusion-video 安全与认证:Spring Security Token 鉴权、用户角色与团队权限完整指南

融光 ai-fusion-video 安全与认证:Spring Security Token 鉴权、用户角色与团队权限完整指南

人工智能AI 应用大模型媒体生成AI Agent后端前端 【免费下载链接】ai-fusion-video 【融光】 - 基于 Agent 的全流程AI短剧/漫剧/视频创作平台 - Java & agentscope2.0 | Agent-based end-to-end AI creation platform for short dramas, motion comics, and videos – bui…

2026/10/11 20:39:26 阅读更多 →
ASP.NET五套实战项目拆解:登录权限、CRUD、分页与文件上传一次讲透

ASP.NET五套实战项目拆解:登录权限、CRUD、分页与文件上传一次讲透

简介:面向ASP.NET初学者的完整项目案例合集,内含5个从设计、编码到部署全流程的实战项目,覆盖C#编程、数据库交互、MVC模式、Entity Framework、Ajax及UI设计等核心知识点。适合正在学习ASP.NET Web Forms或MVC开发的学生与转行开发者&#x…

2026/10/11 20:39:26 阅读更多 →
Android教务数据桥接系统:轻量级本地化课表管理方案

Android教务数据桥接系统:轻量级本地化课表管理方案

简介:本资源是一套面向计算机相关专业本科生的Android毕业设计实战项目,聚焦高校课程电子化管理场景,为正在完成期末大作业或毕业设计的学生提供可直接复用的完整解决方案。压缩包共含论文、开发文档、数据库设计说明与可运行源码四大类内容&…

2026/10/11 20:39:26 阅读更多 →
从「准」到「实」再到「快」:中文图像生成的三次叙事转向意味着什么

从「准」到「实」再到「快」:中文图像生成的三次叙事转向意味着什么

从「准」到「实」再到「快」:中文图像生成的三次叙事转向意味着什么 【免费下载链接】Qwen-Image-2.1-Turbo 项目地址: https://ai.gitcode.com/hf_mirrors/Qwen/Qwen-Image-2.1-Turbo 过去一年,中文图像生成赛道的公共话题经历了三次肉眼可见的…

2026/10/11 20:39:25 阅读更多 →
Java后端接入SignalR:从HTTP到HTTPS的完整落地与避坑指南

Java后端接入SignalR:从HTTP到HTTPS的完整落地与避坑指南

简介:面向Java与Android开发者的SignalR实时通信客户端库源码包,用于在非浏览器环境中接入ASP.NET SignalR服务,实现服务器向客户端实时推送。ASP.NET SignalR是一套成熟的实时Web通信库,而这份Java客户端扩展了它的使用边界&…

2026/10/11 20:38:24 阅读更多 →

日新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

周新闻

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

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

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

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

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

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

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

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

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

2026/10/11 0:00:27 阅读更多 →

月新闻

我发现了一个新思路:用 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/11 10:45:37 阅读更多 →
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/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练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/11 14:36:54 阅读更多 →