跳到主要内容

我把这个博客当成一个安全项目来做

2026-10-10工程实践 4 分钟阅读 1912 字静态托管、零第三方脚本、源站不暴露——这些选择看起来是「技术选型」,其实每一条都是在做攻击面收敛。

起点:一个博客有什么可"安全"的?

大多数个人博客的安全讨论到"上个 HTTPS"就结束了。但如果你打算把简历链接交给面试官,这个站就同时承担了三件事:

  1. 它是我的门面——被别人看到的那一刻,它就在替我表达"这个人懂不懂安全"
  2. 它上面挂着在线 Demo 的入口——一旦被篡改,跳转目标就可能被替换
  3. 它与我的私有服务共用同一套基础设施思路——边界划错了,问题会扩散

所以这个站从第一天起,选型的标准不是"哪个框架流行",而是哪条路留下的攻击面最小。


决策一:纯静态,不要 CMS、不要数据库

选择:Hugo 生成静态 HTML,托管在 GitHub Pages。

换来了什么:

常见攻击面静态站是否还有
SQL 注入❌ 没有数据库
后台弱口令❌ 没有后台
任意文件上传 getshell❌ 没有上传接口
反序列化 / RCE❌ 没有服务端运行时
依赖组件漏洞⚠️ 只剩构建期依赖,不进入线上

代价:动态功能(评论、搜索、访问统计)都不能直接做。

怎么补:

  • 搜索 → 用 Pagefind,在构建时生成索引,浏览器本地检索,不需要后端
  • 评论 → 用 Giscus,数据存在 GitHub Discussions,我不维护任何存储
  • 统计 → 用 Cloudflare Web Analytics,不引入 Cookie、不追踪个人

共同点:这三样都把"数据处理"外包给了别人已经维护好的基础设施,我这边零服务端。


决策二:不引入任何第三方脚本

打开很多博客,<head> 里躺着五六个外部域名的脚本:统计、字体、CDN、客服、广告。

对我这个站来说,每一个都是把访问者的浏览器控制权分一份给别人:

  • 第三方脚本被投毒 → 你的站跟着中招(供应链攻击最常见的入口)
  • 第三方域名被劫持 → 你的访问者被重定向
  • 第三方能看到访问者的 Referer 和 IP

所以本站的原则是:

<!-- 一个都不引 -->
<script src="https://third-party.example.com/analytics.js"></script>  ❌

字体用系统字体栈,图标用内联 SVG,代码高亮在构建时完成。

唯一的例外是评论组件——它本来就依赖 GitHub 登录,属于功能必要,且我会明确标注出来。


决策三:源站 IP 不暴露

后面我会把 SecAgent 的在线 Demo 部署到云服务器上。如果直接把服务器 IP 写进前端代码,那么所有 CDN、WAF、限流都形同虚设——攻击者绕过它们直连源站就行。

做法:

访问者 → 域名 → Cloudflare(CDN / WAF / 限流)
                      ↓ 加密隧道回源
                 源站(不对外暴露端口)

源站的业务端口只绑定回环地址(127.0.0.1),由隧道进程转发进来。外网扫不到,也连不上。

附带好处:换服务器时,只要重建隧道,域名和 DNS 记录一个字都不用改。


决策四:把信任边界画清楚

这是我认为最重要的一条。个人项目最常见的错误是:把公开的东西和私密的东西放在同一个地方。

本站的分层:

层内容谁能访问
公开层这个博客、在线 Demo任何人
私有层知识库、笔记服务只有我,需登录,走内网
本地层含敏感信息的实验记录、靶机不出本机

红线:任何包含真实凭据、客户信息、未脱敏日志的内容,永远不进公开仓库——哪怕它现在看起来"删掉了"。Git 历史是不会忘的,一旦推上去就永远在里面。

(这一点我踩过:曾经把内部准备的文档和公开仓库放在同一个目录树下。现在内部资料统一放在 docs/internal/,并且从第一次提交起就被 .gitignore 排除——它从来没有进入过 git 历史,这是关键区别。)


决策五:默认凭据一律 fail-closed

这条是从部署 Demo 的过程中逼出来的。

后端服务最初对默认口令只做了"启动时打印一条警告"。这在本地无妨,但一旦暴露到公网,等于把门开着挂了个"请随手关门"的牌子。

改成:

检测到默认/弱口令
    ├─ 本地开发(开关 = false)→ 打印告警,照常启动
    └─ 对外部署(开关 = true)→ 抛异常,拒绝启动

宁可服务起不来,也不带着公开的口令对外提供服务。

这就是 fail-closed(失败关闭) 与 fail-open(失败放行) 的区别。安全组件的默认行为应该是前者:

出问题的时候,系统应该拒绝服务,而不是降级到不安全的状态继续跑。


还没解决的

诚实记录一下当前的问题:

  • 评论组件是唯一的第三方依赖——虽然只加载在文章页,但仍是一个外部请求
  • GitHub Pages 在国内访问不稳定——后续考虑加一层 CDN,或做国内镜像
  • 没有做子资源完整性(SRI)校验——因为目前没有外部脚本,等真引入了再补
  • 没有正式的威胁建模——上面的决策是工程直觉,不是系统化的 STRIDE 分析

小结

把一个"写文章的地方"当成"一个有攻击面的系统"来看,选型标准会完全不一样:

通常的选型标准这里的选型标准
功能多不多攻击面小不小
好不好看依赖少不少
社区大不大能不能自己说清楚它怎么工作

这个习惯带到工作里,就是在做任何一个功能之前,先问一句:这块新增了什么可以被攻击的东西?


这个站的全部源码在 GitHub 上。