安全问题不是”高级话题”——不提交密钥到仓库、使用最小权限、审计依赖,是第一天就该养成的习惯。安全习惯一旦建立,就像系安全带一样自然。
1. 安全不是别人的事
很多开发者有一种思维定式:“我只是写业务代码,安全是安全团队的事。“这种想法本身就是最大的安全漏洞。
看看真实发生过的事情:
- AWS 密钥泄露到 GitHub:开发者不小心将 AWS Access Key 提交到公开仓库。几分钟内自动化扫描脚本捕获了密钥,攻击者启动加密货币挖矿实例,账单五位数美元。
- NPM 包被植入恶意代码:流行包的维护者账户被盗,攻击者在更新中植入窃取环境变量的恶意代码。所有下游项目全部中招——这就是供应链攻击。
- 数据库密码硬编码在源码中:生产数据库密码写死在代码里。员工离职后虽然账号被禁用,但仍可通过代码仓库中的密码访问生产数据库,三个月后才被发现。
核心理念很简单:安全不是某个人或某个团队的责任,而是每个开发者每天写代码时的基本习惯。 你不需要成为安全专家,但你需要养成安全习惯——就像开车系安全带一样自然。
2. 敏感信息不入库
什么算敏感信息
以下这些东西永远不应该出现在 Git 仓库中:
| 类型 | 示例 |
|---|---|
| API 密钥 | AKIAIOSFODNN7EXAMPLE(AWS Access Key) |
| 数据库密码 | mysql://root:password123@prod-db:3306/app |
| Token | GitHub Personal Access Token、Slack Bot Token |
| 证书私钥 | -----BEGIN RSA PRIVATE KEY----- |
| 云服务凭证 | ~/.aws/credentials、gcloud 认证文件 |
| JWT Secret | 用于签发 JWT 的密钥 |
Git 仓库 = 永久记录
很多人以为”把敏感文件删掉再 commit 就行了”。不行。Git 的设计决定了所有历史版本都永久保存。即使删除了文件,git log 和 git show 依然可以还原。GitHub 上有很多自动扫描工具专门在 commit history 中搜索密钥。
预防方案
.gitignore 是第一道防线:
# 环境变量
.env
.env.local
.env.production
.env.*.local
# 密钥文件
*.pem
*.key
*.p12
*.jks
# 云服务凭证
.aws/credentials
.gcloud/credentials.json
# 其他
credentials.json
secrets.yaml
config-private.json提供 .env.example 模板(不含真实密钥):
# .env.example — 复制为 .env 并填入真实值
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
AWS_ACCESS_KEY_ID=your_access_key_here
AWS_SECRET_ACCESS_KEY=your_secret_key_here
JWT_SECRET=change_me_to_a_random_string
GITHUB_TOKEN=ghp_your_token_here用工具自动扫描——不要只靠人眼:
brew install gitleaks
gitleaks detect --source . --verbose --exit-code泄露后的应急四步法:
- 立即轮换密钥:撤销旧密钥,生成新密钥。这比清理历史更紧急。
- 清理 Git 历史:用 BFG Repo-Cleaner 或
git filter-branch删除敏感文件。 - 审计日志:检查泄露期间是否有异常访问。
- 复盘:分析原因,更新流程。
# BFG Repo-Cleaner 清理历史
java -jar bfg.jar --delete-files .env my-repo.git
cd my-repo.git && git reflog expire --expire=now --all
git gc --prune=now --aggressive && git push --force注意:强制推送会改写远程历史,需要团队协调。公开仓库即使清理了历史,也应认为密钥已泄露,必须轮换。
3. 密钥与 Token 管理
不同密钥的管理方式
| 密钥类型 | 推荐方式 | 注意事项 |
|---|---|---|
| 云服务 API Key | 环境变量 + IAM 权限限制 | 不给 Admin 权限 |
| SSH 私钥 | 密码保护 + ssh-agent | 必须设置 passphrase |
| 数据库密码 | 环境变量 / Secrets Manager | 生产环境不用环境变量 |
| JWT Secret | Vault / 加密存储 | 定期轮换,至少 32 字节 |
| GitHub Token | 环境变量 + 权限最小化 | 设置过期时间 |
环境变量 vs Secrets Manager
环境变量适合本地开发,但生产环境有隐患:进程重启时可能被日志记录、ps aux 可见、容器编排中可能写入日志、无法审计访问。Secrets Manager 是生产环境标准:
aws secretsmanager get-secret-value --secret-id prod/database/url
vault kv get -field=password secret/prod/database优势:访问有审计日志、支持自动轮换、精细控制访问权限、密钥不出现在环境变量中。
Token 权限最小化与定期轮换
创建 Token 时只勾选真正需要的权限:GitHub Token 只需读仓库就给 repo:read,AWS 用 IAM Role 限制范围,Slack Bot 只申请需要的 OAuth Scope。
不要创建”永不过期”的 Token。GitHub 已强制要求 Personal Access Token 设置过期时间。
4. 最小权限原则
最小权限原则(Principle of Least Privilege):每个账户、服务、程序只拥有完成工作所需的最小权限。多一分都不给。
数据库权限
-- 错误:给所有权限
GRANT ALL PRIVILEGES ON mydb.* TO 'app_user'@'%';
-- 正确:只给需要的操作
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'app_user'@'%';绝对不要给:DROP、ALTER、GRANT、FILE、SUPER。
云服务 IAM
{
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-app-bucket/*"
}只允许对特定 Bucket 的读写,不能删除 Bucket、不能访问其他资源、不能修改 IAM 策略。
文件系统权限
chmod 700 ~/.ssh # 目录:只有所有者能进入
chmod 600 ~/.ssh/id_ed25519 # 私钥:只有所有者能读写
chmod 644 ~/.ssh/id_ed25519.pub # 公钥:所有人可读权限太宽松时 SSH 会拒绝使用密钥:Permissions 0644 are too open。
Docker 容器
# 错误:用 root 运行
FROM node:20
COPY . /app
CMD ["node", "server.js"]
# 正确:创建非 root 用户
FROM node:20
RUN groupadd -r appuser && useradd -r -g appuser appuser
COPY --chown=appuser:appuser . /app
USER appuser
CMD ["node", "server.js"]不要用 --privileged 运行容器——那给了容器所有内核权限。
定期权限审计
aws iam list-users
aws iam list-attached-user-policies --user-name <username>
gh api orgs/<org>/members每季度检查:“谁还有什么权限?这些权限还需要吗?“
5. 依赖安全
你的依赖树比你想象的深
一个 npm install express 会拉下来几百个依赖包。你不仅信任 express 的维护者,还信任它依赖的包、那些包依赖的包……的维护者。其中任何一个被攻破,你的项目就面临风险。
依赖风险的三种形式
已知漏洞(CVE):
npm audit # Node.js
npm audit fix # 自动修复
pip-audit # Python恶意包(Typosquatting):攻击者发布名字和流行包很像的恶意包:
pip install reqeusts # 拼写错误!恶意包
pip install requests # 正确的包名常见手法:字母调换(reqeusts)、少字母(expres)、多字母(requestss)、视觉混淆(Iodash 用大写 I 代替小写 l)。
废弃包:无人维护,漏洞不会修。用 npm view <pkg> time.modified 检查最后更新时间。
依赖安全最佳实践
| 实践 | 操作 |
|---|---|
| 锁定版本 | 提交 package-lock.json / Pipfile.lock / poetry.lock |
| 减少依赖 | 能用标准库就别装包,每个依赖都是攻击面 |
| 定期更新 | 每周检查 Dependabot PR |
| 审查来源 | 安装前看 stars、维护者、最后更新时间 |
| 自动化扫描 | CI 中加入 npm audit,高危漏洞即失败 |
# GitHub Actions 自动审计
name: Security Audit
on:
push:
paths: ['package-lock.json', 'requirements.txt']
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm audit --audit-level=high6. 身份验证强化
密码管理
不要重复使用密码。一个服务被攻破,攻击者会用你的邮箱和密码尝试登录所有其他服务。
使用密码管理器:1Password(付费体验好)、Bitwarden(开源免费可自托管)、KeePassXC(完全本地)。你只需要记住一个主密码。
不要硬编码密码——代码仓库中的密码等于公开密码。
双因素认证(2FA / MFA)
2FA 需要两样东西:你知道的(密码)+ 你拥有的(手机验证码 / 硬件密钥)。即使密码泄露,攻击者没有第二因素也无法登录。
| 方式 | 说明 |
|---|---|
| TOTP | Google Authenticator / Authy,每 30 秒生成 6 位数字,方便但手机丢了麻烦 |
| 硬件密钥 | YubiKey / Google Titan Key,最安全无法被钓鱼,但需要花钱 |
必须开启 2FA 的服务:
| 服务 | 为什么重要 |
|---|---|
| GitHub / GitLab | 仓库代码、CI/CD 密钥、组织管理权限 |
| AWS / GCP / Azure | 云资源、账单、生产环境 |
| 邮箱 | 密码重置入口,控制了邮箱就控制了一切 |
| 域名注册商 | 可以转移你的域名 |
GitHub 如果不开 2FA,你的仓库只差一个泄露的密码。2023 年起 GitHub 已强制要求活跃贡献者开启 2FA。
企业 SSO
企业环境中用 SSO 统一管理身份:员工离职只需禁用一个账号、集中审计访问记录、在 SSO 层面强制 2FA。
7. 社会工程学防范
攻击人性而非技术
社会工程学不攻击系统漏洞,而是攻击人。技术再安全,如果人被骗了,一切防护都形同虚设。
常见攻击形式
钓鱼邮件:伪装成 GitHub Security Alert,域名却是 github-alerts.com 而非 github.com,要求 24 小时内点击链接修复”高危漏洞”。防范:不点邮件中的链接,自己去 github.com 查看。
Typosquatting:npm install colour 而非 colors,npm install col0rs 用数字 0 代替字母 o。防范:仔细检查包名。
冒充同事:Slack 上收到”帮我看下这个项目”的消息,附带恶意链接。防范:通过已知渠道确认对方身份。
假招聘面试:要求下载”技术测试”项目并 npm install,postinstall 脚本窃取 SSH 密钥和 Token。防范:不要执行来路不明项目的安装命令。
防范原则
| 原则 | 具体做法 |
|---|---|
| 不点不明链接 | 自己去官网确认 |
| 不执行不明命令 | 先理解每个参数 |
curl | bash 危险 | 先 curl -O 下载,查看内容后再执行 |
| 验证身份 | 在已知渠道确认对方身份 |
| 保持怀疑 | 太好的事情、太紧急的事情往往是攻击 |
# 错误:直接执行
curl https://install.example.com/setup.sh | bash
# 正确:先下载、查看、再执行
curl -O https://install.example.com/setup.sh
cat setup.sh && chmod +x setup.sh && ./setup.sh8. 日常安全检查清单
| 频率 | 检查项 | 操作 |
|---|---|---|
| 每次 commit 前 | 检查 diff | git diff 确认没有 .env、API Key |
| 每次 commit 前 | 确认 .gitignore | 新增配置文件已加入忽略列表 |
| 每次 commit 前 | pre-commit 扫描 | gitleaks / git-secrets 自动扫描 |
| 每周 | 依赖漏洞扫描 | npm audit / pip-audit |
| 每周 | 查看 Dependabot PR | 及时合并安全更新 |
| 每月 | 云服务 IAM 检查 | 移除不必要的 Admin 权限 |
| 每月 | SSH 密钥检查 | 确认未过期,移除不再使用的密钥 |
| 每月 | 检查 2FA 状态 | 确认关键服务都开启了 2FA |
| 每季度 | 轮换 Token | 更新 GitHub Token、API Key、数据库密码 |
| 每季度 | 权限审查 | 检查谁有什么权限,移除不需要的 |
| 每季度 | 更新密码管理器 | 检查重复密码,更新弱密码 |
| 收到可疑消息时 | 不点链接 | 自己去官网确认,通过已知渠道验证 |
使用原则
开发者安全不是背清单,而是在每次写入、提交、安装、授权、分享之前多问一步:这会不会扩大暴露面?
密钥默认当作已泄露风险处理:一旦进入 Git 历史、聊天记录、截图或日志,优先撤销和轮换,不要只想着删除痕迹。
最小权限是默认姿势:数据库账号、云 IAM、Token、容器用户都只给当前任务需要的权限。
依赖安装前先看来源:陌生项目、面试题、脚本片段、curl | sh 都要先读再执行。
MFA 和密码管理器是基础设施:它们不是“高级安全配置”,而是开发者日常身份边界。
安全问题要留证据和路径:发现异常登录、密钥暴露、供应链风险时,记录时间、命令、日志和处理动作,方便复盘。
- 运行 gitleaks 扫描:
gitleaks detect --source . --verbose - 为 GitHub 开启 2FA:Settings → Password and authentication → Two-factor authentication → Enable
- 检查依赖漏洞:
npm audit(Node.js)或pip-audit(Python) - 创建
.env.example模板,确保.env已在.gitignore中 - 检查
~/.ssh权限:chmod 700 ~/.ssh && chmod 600 ~/.ssh/id_ed25519 && chmod 644 ~/.ssh/id_ed25519.pub
延伸阅读
- OWASP Top 10 — Web 应用最常见的十大安全风险
- GitHub Security Best Practices — GitHub 官方安全指南
- CISA Free Security Tools — 美国网络安全与基础设施安全局免费工具
- 12-Factor App - Config — 配置与代码分离的原则
- BFG Repo-Cleaner — 清理 Git 历史中的敏感文件
- gitleaks — Git 仓库密钥扫描工具
- Dependabot — GitHub 自动依赖更新
- Snyk — 依赖漏洞扫描和修复平台