用途
本技能旨在防止 API 密钥、密码和令牌等硬编码密钥泄露到源代码管理中。资料指出,公开仓库中的密钥被视为严重疏忽(gross negligence),这是马来西亚 PDPA 相关法院案例中的发现 #4。
核心规则
绝不将密钥提交到源代码。始终使用环境变量或密钥管理器。
什么算作密钥
资料列出的密钥类型包括:
- API 密钥(Stripe、OpenAI、AWS 等)
- 带密码的数据库连接字符串
- JWT 签名密钥
- OAuth 客户端密钥
- Webhook 签名密钥
- 私钥(RSA、SSH)
- 服务账号凭据
用于审计的搜索模式
资料提供了以下检测模式:
# Hardcoded strings that look like secrets
(api[_-]?key|secret|password|token|credential|private[_-]?key)\s*[:=]\s*["'][^"']{8,}["']
# AWS keys
AKIA[0-9A-Z]{16}
# Generic high-entropy strings in assignments
(SECRET|KEY|TOKEN|PASSWORD|CREDENTIAL)\s*=\s*["'][A-Za-z0-9+/=]{20,}["']
# Connection strings with embedded passwords
(mongodb|postgres|mysql|redis):\/\/[^:]+:[^@]+@
正确设置方式
1. 创建 .env 文件(绝不提交)
# .env (local development only)
DATABASE_URL=postgres://user:pass@localhost:5432/mydb
STRIPE_SECRET_KEY=sk_test_abc123
JWT_SECRET=your-random-64-char-string
OPENAI_API_KEY=sk-proj-abc123
2. 创建 .env.example(提交,不含真实值)
# .env.example (committed to repo as documentation)
DATABASE_URL=postgres://user:password@localhost:5432/dbname
STRIPE_SECRET_KEY=sk_test_xxx
JWT_SECRET=generate-with-openssl-rand-base64-48
OPENAI_API_KEY=sk-proj-xxx
3. 添加到 .gitignore
# Secrets - NEVER commit these
.env
.env.local
.env.production
.env.*.local
*.pem
*.key
service-account.json
credentials.json
各框架特定模式
Node.js / TypeScript
错误写法:
const stripe = new Stripe('sk_live_abc123xyz');
const jwt = sign(payload, 'my-super-secret-key');
const db = new Pool({ password: 'production-password-123' });
正确写法:
import { env } from 'process';
const stripe = new Stripe(env.STRIPE_SECRET_KEY!);
const jwt = sign(payload, env.JWT_SECRET!);
const db = new Pool({ connectionString: env.DATABASE_URL });
// Validate at startup
const required = ['STRIPE_SECRET_KEY', 'JWT_SECRET', 'DATABASE_URL'];
for (const key of required) {
if (!env[key]) throw new Error(`Missing required env var: ${key}`);
}
Cloudflare Workers
错误写法:
const API_KEY = 'sk-abc123';
export default {
async fetch(request: Request) {
return fetch('https://api.openai.com/v1/chat', {
headers: { Authorization: `Bearer ${API_KEY}` }
});
}
};
正确写法:
// wrangler.toml - declare bindings
// [vars]
// PUBLIC_URL = "https://myapp.com"
//
// Secrets added via: npx wrangler secret put OPENAI_API_KEY
interface Env {
OPENAI_API_KEY: string;
PUBLIC_URL: string;
}
export default {
async fetch(request: Request, env: Env) {
return fetch('https://api.openai.com/v1/chat', {
headers: { Authorization: `Bearer ${env.OPENAI_API_KEY}` }
});
}
};
Laravel / PHP
错误写法:
'stripe' => [
'secret' => 'sk_live_abc123xyz',
],
正确写法:
// config/services.php
'stripe' => [
'secret' => env('STRIPE_SECRET_KEY'),
],
// Validate in AppServiceProvider::boot()
$required = ['STRIPE_SECRET_KEY', 'DB_PASSWORD', 'APP_KEY'];
foreach ($required as $key) {
if (empty(env($key))) {
throw new \RuntimeException("Missing env: {$key}");
}
}
Python
错误写法:
OPENAI_API_KEY = "sk-proj-abc123"
DATABASE_URL = "postgres://admin:password123@prod-db:5432/app"
正确写法:
import os
from dotenv import load_dotenv
load_dotenv()
OPENAI_API_KEY = os.environ["OPENAI_API_KEY"]
DATABASE_URL = os.environ["DATABASE_URL"]
# Pydantic settings (recommended)
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
openai_api_key: str
database_url: str
jwt_secret: str
class Config:
env_file = ".env"
settings = Settings()
密钥轮换策略
何时轮换
- 密钥曾被提交到 git 时立即轮换(即使后来被移除)
- 团队成员离开时立即轮换
- 生产密钥每 90 天轮换一次(最佳实践)
- 任何安全事件之后
如何在不中断服务的情况下轮换
// Support both old and new key during rotation window
const VALID_API_KEYS = [
env.API_KEY_CURRENT,
env.API_KEY_PREVIOUS, // Remove after 24h
].filter(Boolean);
function validateApiKey(key: string): boolean {
return VALID_API_KEYS.includes(key);
}
紧急情况:密钥已被提交
如果密钥被推送到 git,即使只是短暂地:
- 立即轮换密钥——从提供者处生成新密钥
- 撤销旧密钥——不要只是轮换,要明确撤销
- 检查未授权使用——查看访问日志
- 清理 git 历史(可选,密钥已经泄露):
# Only if repo is private and you caught it fast
git filter-branch --force --index-filter \
"git rm --cached --ignore-unmatch .env" \
--prune-empty --tag-name-filter cat -- --all
- 添加到 .gitignore 以防止再次发生
CI/CD 密钥注入
GitHub Actions
# Use repository secrets, never hardcode
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }}
Cloudflare Workers (Wrangler)
# Set secrets via CLI (never in wrangler.toml)
npx wrangler secret put DATABASE_URL
npx wrangler secret put STRIPE_SECRET_KEY
PDPA 2024 后果
资料指出,根据 2010 年 PDPA 法案第 5(1) 和 5(2) 条(2024 年修订):
- 罚款最高 RM1,000,000
- 监禁最长 3 年
公开仓库中的硬编码密钥被视为严重疏忽(kecuaian melampau)。如果这些密钥提供对个人数据的访问,开发者将承担刑事责任。