敏感配置必须彻底脱离进程内存:用os.LookupEnv校验必填环境变量,结构体敏感字段加json:"-"并实现String()脱敏,环境变量仅存文件路径,禁用godotenv除cmd/main.go外的加载。

os.LookupEnv 是起点,不是终点。敏感配置必须从进程内存之外彻底消失——它不该出现在 .gitignore 漏掉的 .env 里,也不该被 os.Getenv 静默转成空字符串后还继续启动。
用 os.LookupEnv 显式校验必填字段
常见错误现象:服务启动后连不上数据库,日志只报 dial tcp: i/o timeout,查半天才发现 DB_PASSWORD 环境变量根本没设,但 os.Getenv("DB_PASSWORD") 返回空串,代码照常往下走。
- 必须用
os.LookupEnv而非os.Getenv:它返回(string, bool),能区分“未设置”和“设为空字符串” - 对每个敏感字段做双重判断:
!ok || len(value) == 0 - 校验失败立即
log.Fatal,不 fallback、不打日志、不尝试连接 - 放在
main()最开头,或init()中,确保早于任何依赖初始化
结构体字段加 json:"-" 并实现 String()
常见错误现象:fmt.Printf("%+v", cfg) 或 slog.With("cfg", cfg).Info("startup") 把密钥直接打到 stdout、Sentry、ELK 里,且无法事后删除。
- 所有敏感字段(如
Password、APIKey)必须加json:"-" yaml:"-"标签 - 结构体实现
String()方法,返回脱敏形式,例如:Password: *** - HTTP handler 中禁止传整个配置 struct 给
slog.With;必须手动提取非敏感字段构造键值对 - 即使调试时,也禁用
log.Printf("config: %+v", cfg)——长度、类型、字段名本身都可能构成侧信道
文件挂载路径只存环境变量,内容绝不进内存以外任何地方
常见错误现象:export API_KEY="abc123" 后,ps aux | grep myapp 全暴露;Dockerfile 中 ENV DB_PASSWORD=... 让密钥固化在镜像层。
- 环境变量只存路径,例如
export DB_PASSWORD_FILE="/run/secrets/db_password" - Go 代码用
os.ReadFile("/run/secrets/db_password")读取(Go 1.16+,不用ioutil) - 文件权限设为
0440,仅限运行用户可读 - 本地开发用相同路径(如
./secrets/db_password),由make secrets生成,不提交 Git - 绝不打印读取内容,连
log.Printf("secret length: %d", len(b))都建议去掉
禁用 godotenv 在 pkg/ 或 internal/ 中加载
常见错误现象:.env 被误提交、CI 流水线加载了本地测试密钥、不同服务复用同一份 .env 导致密钥污染。
-
godotenv.Load()只允许在cmd/包的main.go开头调用 -
.env必须进.gitignore,配套提供.env.example(只有变量名和注释,无值) - CI 环境禁用
.env:GitHub Actions 用secrets,Kubernetes 用Secretvolume 挂载,Docker Compose 用environment+env_file分离 - 若需本地快速启动,配合
//go:build dev标签,让godotenv仅在dev构建中生效
真正难的不是读到密钥,而是让它在内存里待得足够短、在磁盘上藏得足够深、在日志里彻底消失——每一步松懈,都可能让 *** 变成真实字符。


















