从零开始构建一个现代化前端项目:一个运维老狗的安全视角

编译通过了吗
2025-12-15 08:13
阅读 686

“你一个搞 DevOps 的,怎么突然写起前端来了?”
—— 上周五深夜 2 点,我刚在 CI pipeline 里加完 SAST 扫描,测试同事发来微信。

说实话,作为团队里干了快两年的 DevOps 工程师,我本不该碰前端代码。但去年双11前,我们组接了个“轻量级运营活动页”需求——产品经理拍着胸脯说:“就几个按钮、一张图,三天上线!” 结果 UI 给了 Figma 文件,交互细节写了 87 条注释,还要求支持 IE11(别问,问就是“某些用户还在用”)。

更离谱的是,这页面要直接挂到主站域名下,涉及用户登录态和优惠券发放。安全红线立马拉满。我一看 Git 提交记录,前端同事用 create-react-app 起了个项目,.env 里明文写着测试密钥,package.json 依赖里混着三年没更新的 lodash@4.17.15……当时真的想砸电脑。

于是,我咬牙接手了这个“前端项目基建”的活儿。毕竟,在我们公司文化里,谁让线上出事,谁请全组喝一个月瑞幸。今天这篇博客,就是我在凌晨三点调试完 CSP 策略后,吐血整理的“现代化前端项目安全搭建指南”。


别再裸奔了!你的前端项目缺一套“安全盔甲”

很多前端同学觉得:“我又不写后端,能有啥安全问题?” 呵,天真。XSS、CSRF、敏感信息泄露、供应链投毒……前端恰恰是攻击者的第一入口。尤其当项目涉及运营活动——这类页面往往生命周期短、迭代快、权限高,最容易被忽视安全加固。

我的目标很明确:从第一天起,就把安全左移到开发流程中。下面这套方案,已经跑在我们最近三个运营项目上,零安全事故(暂时)。

第一步:GitHub 仓库初始化 —— 安全是默认选项

新建仓库时,我绝不点那个“Initialize this repository with a README”。为啥?因为我要确保 .gitignore.editorconfig、安全策略文件 先于任何业务代码存在

mkdir fancy-campaign-2024
cd fancy-campaign-2024
git init
# 先配好忽略规则
echo "node_modules/\n.env\n.DS_Store\nbuild/" > .gitignore

接着,我会立刻创建几个关键文件:

  • .github/dependabot.yml:自动扫描依赖漏洞
  • .eslintrc.js:加入安全规则(比如禁用 innerHTML
  • SECURITY.md:明确漏洞披露流程(虽然小项目用不上,但仪式感要有)

重点说下 Dependabot。它不只是升级依赖,更是供应链安全的第一道防线。配置如下:

# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    open-pull-requests-limit: 5
    # 关键:只接受 security updates 的自动合并
    labels:
      - "security"
    commit-message:
      prefix: "chore(deps)"

这样,一旦 axiosreact 出现 CVE,GitHub 会自动提 PR,并打上 security 标签。我们的 CI 流水线看到这个标签,会跳过人工审核直接合入——毕竟,安全补丁等不起 PM 的排期

第二步:脚手架选型 —— 别再用 CRA 了!

我知道很多人爱 create-react-app,简单无脑。但它最大的问题是:配置黑盒 + 更新滞后。你想加个 CSP nonce?得 eject,然后维护地狱开启。

我现在的标准方案是 Vite + TypeScript + ESLint (带安全插件)。启动快、HMR 丝滑,关键是配置透明。

npm create vite@latest . -- --template react-ts
npm install -D eslint-plugin-security eslint-plugin-react-hooks

ESLint 配置里,我强制开启这些规则:

// .eslintrc.js
rules: {
  'security/detect-object-injection': 'error',
  'security/detect-non-literal-fs-filename': 'error',
  // 禁止危险的 DOM 操作
  'no-restricted-properties': [
    'error',
    { property: 'innerHTML', message: 'Use JSX or dangerouslySetInnerHTML with caution' }
  ]
}

别笑,就因为这条规则,我们拦截了实习生想用 div.innerHTML = userContent 的骚操作。安全不是功能,是约束

第三步:环境变量与密钥 —— 别把密码提交到 GitHub!

运营活动常需要调用内部 API,比如发放优惠券。很多前端会把测试 token 写进 .env,然后 git add .……兄弟,GitHub 的 commit history 是公开的(即使私有仓库,离职员工也能看啊)!

正确姿势:

  1. 所有密钥通过 CI 注入,本地开发用 mock。
  2. 前端绝不存储敏感密钥——真需要,走后端代理。

我们的 Vite 配置:

// vite.config.ts
export default defineConfig({
  define: {
    // 只暴露非敏感配置
    __APP_VERSION__: JSON.stringify(process.env.npm_package_version),
    __API_BASE__: JSON.stringify(
      import.meta.env.DEV 
        ? 'http://localhost:3001/mock-api' 
        : 'https://api.our-company.com'
    )
  }
})

注意:import.meta.env 在构建时会被静态替换,所以绝不能传入运行时动态值(比如用户 ID)。这也是为什么优惠券发放必须由后端完成——前端只负责展示按钮,点击后请求 /api/coupon/grant,由后端校验权限。

第四步:CSP —— 防 XSS 的终极武器

上周有个运营页被钓鱼,黑客注入了 <script src="evil.com/steal-cookies.js">。根因?页面用了第三方统计脚本,但没设 CSP。

现代浏览器都支持 Content Security Policy,它像一道防火墙,只允许加载白名单内的资源。

我们在 index.html 里加了这段:

<meta 
  http-equiv="Content-Security-Policy" 
  content="default-src 'self'; 
           script-src 'self' 'unsafe-inline' https://analytics.trusted.com; 
           style-src 'self' 'unsafe-inline'; 
           img-src 'self' data: https:;"
>

解释一下:

  • default-src 'self':默认只加载同源资源
  • script-src 放行了自家域名和可信的统计域名
  • 严禁 'unsafe-eval'——Webpack 的 HMR 开发时会用到,但生产必须关掉!

如何验证?打开 DevTools → Console,如果有 CSP 违规,会清晰报错:

Refused to load the script 'https://hack.com/xss.js' because it violates the following Content Security Policy directive...

第五步:自动化安全扫描 —— 让 CI 成为守门员

光靠人工 review 不现实。我在 GitHub Actions 里加了三道关卡:

# .github/workflows/security.yml
name: Security Scan
on: [push, pull_request]

jobs:
  sast:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run ESLint Security
        run: npx eslint . --ext .ts,.tsx --rulesdir ./eslint-rules
      - name: Run npm audit
        run: npm audit --audit-level high
      - name: Check for secrets (gitleaks)
        uses: gitleaks/gitleaks-action@v2
  • eslint-plugin-security:查代码中的危险模式
  • npm audit:扫依赖漏洞(high 以上直接失败)
  • gitleaks:防止不小心提交了 AWS_KEY 之类

效果立竿见影。上周,一个 PR 因为引入了含原型污染漏洞的 set-value@4.0.0 被自动拒绝。前端同事一开始抱怨“耽误我提测”,直到我给他看了 CVE 编号……


效果实测:安全与效率可以兼得

这套流程跑下来,我们最近三个运营项目:

指标 旧方式 (CRA) 新方式 (Vite + 安全左移)
首屏加载 2.8s 1.1s
安全漏洞数 平均 3.2/项目 0
CI 失败率 15% (多为低级错误) 5% (多为 lint 问题)
上线速度 2天 1天

性能提升来自 Vite 的原生 ES 模块加载,安全提升则来自流程约束。最爽的是,现在 PM 再也不敢说“先上线再补安全”了——因为 CI 卡着,他连预发环境都上不去。


最后一点碎碎念

写这篇文章时,窗外天都亮了。作为 DevOps,我其实挺羡慕前端同学的——他们能直接看到像素动起来的快乐。而我整天和 pipeline、镜像、K8s YAML 打交道,成就感来得慢。

但正是这种“幕后工作”,让运营活动页在双11流量洪峰下稳如老狗。安全不是成本,是信任的基石。下次当你看到一个“简单”的活动页时,请记住:背后可能有个 DevOps 老狗,在凌晨三点为你挡住了一次 XSS 攻击。

对了,如果你们团队还在用 console.log 打印用户手机号……快住手!那玩意儿会被 Sentry 自动采集的!(别问我怎么知道的)


本文所有配置已在 GitHub 开源:github.com/yourname/secure-frontend-boilerplate
(链接是假的,但如果你真想要,评论区喊一声,我整理下丢上去)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝