blog-web/scripts/generator.js
灌糖包子 c4e47fe9fa
安全加固:升级依赖消除高危漏洞,修复构建兼容性问题
【主项目依赖升级】
- hexo 6.x → 8.1.2:移除 swig-templates、swig-extras、旧版 cheerio、
  hexo-fs/hexo-util 等大量漏洞依赖链,解决 critical/high 级别漏洞
- hexo-renderer-marked 5.x → 7.0.1:修复 dompurify XSS 漏洞(GHSA-hpcv)
- gulp 4.x → 5.0.1:修复 vinyl-fs、glob/glob-stream、chokidar、
  braces 等 high 级漏洞链
- hexo-generator-sitemap 2.2.0 → 3.0.1:同步升级

【替换高危依赖】
- gulp-htmlmin 替换为 gulp-html-minifier-terser@7.1.0:
  gulp-htmlmin 依赖的 html-minifier 存在 ReDoS 高危漏洞且无修复版本,
  改用其维护分支 html-minifier-terser,API 完全兼容
- optimist 替换为 minimist@1.2.8(devDependency):
  optimist 捆绑的 minimist@<=0.2.3 存在 prototype pollution critical 漏洞,
  直接使用修复版 minimist 替代

【移除无修复版本的高危包】
- 移除 hexo-generator-baidu-sitemap(无维护更新,内部捆绑 hexo@4.x
  导致一系列 critical 漏洞),在 scripts/generator.js 中实现等效的
  自定义 Baidu Sitemap 生成器替代,功能与原包完全一致

【npm overrides 修复传递依赖漏洞】
- 添加 overrides: { ejs: "^6.0.1" }:
  hexo-renderer-ejs@2.x 依赖 ejs@3.x,ejs@3.x 存在 SSTI critical 漏洞
  且引入 jake → filelist → minimatch → brace-expansion 漏洞链;
  ejs@6.x 已无 jake 依赖,critical 漏洞全部消除

【yilia 主题依赖升级】
- @babel/core 7.26.x → 7.29.7:修复 Arbitrary File Read 漏洞
- @babel/plugin-transform-runtime / @babel/preset-env 同步升级至 7.29.x
- postcss 8.5.3 → 8.5.25:修复 XSS 及路径遍历漏洞
- 移除 clean-webpack-plugin(依赖链含 brace-expansion 高危漏洞),
  改用 webpack 5 内置 output.clean 实现等效的增量清理功能
- 添加 overrides: { brace-expansion: "5.0.9" } 修复剩余传递依赖漏洞

【构建兼容性修复】
- scripts/generator.js 新增 jsfiddle tag 注册:
  hexo@8 移除了内置 {% jsfiddle %} tag 导致构建 FATAL,
  重新实现该 tag 保持原有 iframe 嵌入行为
- gulpfile.js generate 任务错误处理修正:
  原代码在 hexo FATAL 时调用 hexo.exit(err) 但不向 gulp 抛出异常,
  导致 series 继续执行后续任务(compressHtml 因 public/ 不存在而报错),
  修改为在 hexo 退出后重新 throw err 确保 gulp 正确感知失败

【修复结果】
- 主项目:56 个漏洞(8 低危、14 中危、25 高危、9 严重)→ 0 个漏洞
- yilia 主题:8 个漏洞 → 1 个低危
  (剩余 1 个为 vue@2.x ReDoS,仅影响构建时自身模板解析,
  修复需整体迁移到 Vue 3 重写主题,暂不处理)
2026-07-31 20:38:04 +08:00

81 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const nunjucks = require('nunjucks')
const path = require('path')
const fs = require('fs')
// hexo@8 移除了内置 jsfiddle tag在此重新注册
hexo.extend.tag.register('jsfiddle', function(args) {
const shortcode = args[0]
const tabs = args[1] || 'js,resources,html,css,result'
const skin = args[2] || 'light'
const width = args[3] || '100%'
const height = args[4] || '300'
return `<iframe style="width: ${width}; height: ${height}px" src="//jsfiddle.net/${shortcode}/embedded/${tabs}/?skin=${skin}" allowfullscreen="allowfullscreen" frameborder="0"></iframe>`
})
const env = new nunjucks.configure({ autoescape: false })
env.addFilter('noControlChars', function(str) {
return str && str.replace(/[\x00-\x1F\x7F]/g, '')
})
const searchTmplSrc = path.join(__dirname, '../templates/articles.xml')
hexo.extend.generator.register('xml', function(locals){
const searchTmpl = nunjucks.compile(fs.readFileSync(searchTmplSrc, 'utf8'), env)
const descCompare = function(value1, value2) {
if(value1 > value2) {
return -1
} else if(value1 < value2) {
return 1
} else {
return 0
}
}
const posts = locals.posts.toArray().sort(function(item1, item2){
return descCompare(item1.updateDate || item1.date, item2.updateDate || item2.date)
}).slice(0, 10)
const xmlData = searchTmpl.render({
posts,
root: this.config.root
})
return {
path: 'articles.xml',
data: xmlData
}
})
// 自定义 Baidu Sitemap 生成器(替代已废弃的 hexo-generator-baidu-sitemap
hexo.extend.generator.register('baidusitemap', function(locals) {
const config = hexo.config.baidusitemap || {}
const outputPath = config.path || 'baidusitemap.xml'
const posts = locals.posts.toArray().filter(function(post) { return !post.draft })
const pages = locals.pages.toArray()
const allItems = posts.concat(pages).sort(function(a, b) {
const dateA = (a.updated || a.date)
const dateB = (b.updated || b.date)
return dateB - dateA
})
const lines = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">'
]
allItems.forEach(function(item) {
const dateObj = item.updated || item.date
const dateStr = dateObj && dateObj.format
? dateObj.format('YYYY-MM-DD')
: new Date(dateObj).toISOString().split('T')[0]
lines.push(' <url>')
lines.push(' <loc>' + item.permalink + '</loc>')
lines.push(' <lastmod>' + dateStr + '</lastmod>')
lines.push(' </url>')
})
lines.push('</urlset>')
return {
path: outputPath,
data: lines.join('\n')
}
})