企业级项目 env 配置安全:从 .env 到 AI 项目密钥治理 企业级项目 env 配置安全从 .env 到 AI 项目密钥治理1. 引言为什么企业级项目不能随意管理 .env在个人项目或课程 Demo 中很多人会把配置写进.env再通过dotenv、框架配置系统或运行环境读取。这样做在本地开发阶段很方便变量集中、修改简单、不会把配置硬编码进业务代码。但在真正的企业级项目中.env绝不能只被理解成“放配置的文件”。企业关注的不是“程序能不能读到变量”而是以下问题谁能看到这些配置哪些配置是普通配置哪些是敏感密钥开发、测试、预发、生产是否严格隔离密钥泄露后能否快速吊销和轮换谁在什么时候修改过配置是否可审计CI/CD、日志、Docker 镜像、构建产物中是否会意外泄露密钥AI 项目中 prompt、completion、embedding、RAG 数据、工具调用日志是否也会携带敏感信息所以企业级.env配置安全的核心不是“把变量放在哪里”而是建立一套围绕配置和密钥的治理体系分类、隔离、注入、校验、审计、轮换、应急响应。配置泄露的影响也往往不是小问题。数据库连接串泄露可能导致拖库云厂商 AK/SK 泄露可能导致资源被盗刷第三方 API Key 泄露可能导致接口被滥用AI 项目的模型 Key 泄露还可能造成高额 token 费用、客户数据外泄和合规风险。2. 基础概念配置、环境变量、密钥不是一回事很多初学者会把“配置”“环境变量”“.env”“Secret”混为一谈但企业开发中必须区分它们。2.1 配置 Configuration配置是影响程序行为的参数例如服务端口PORT3000日志级别LOG_LEVELinfo当前运行环境APP_ENVproduction请求超时时间REQUEST_TIMEOUT_MS10000是否开启某个功能FEATURE_X_ENABLEDfalse配置不一定敏感。比如端口号通常不是密钥但错误配置仍可能导致线上事故。2.2 环境变量 Environment Variable环境变量是向程序注入配置的一种方式。程序启动时可以从操作系统进程环境中读取变量例如 Node.js 中的process.env.PORT。环境变量本身不是安全机制。它只是传递配置的通道。变量是否安全取决于值是否敏感。谁能读取运行环境。是否被写入日志、构建产物或镜像层。是否有访问控制、审计和轮换机制。2.3 Secret / 密钥Secret 是具有访问能力的敏感值例如数据库密码DB_PASSWORD数据库连接串DATABASE_URLJWT 签名密钥JWT_SECRET第三方平台 API KeyPAYMENT_API_KEY云厂商 AK/SKCLOUD_ACCESS_KEY_ID、CLOUD_SECRET_ACCESS_KEYLLM Provider API KeyLLM_API_KEYVector DB TokenVECTOR_DB_API_KEYSecret 一旦泄露攻击者可能直接访问数据、调用接口、伪造身份或消耗资源。2.4 .env.env是一种常见的本地开发配置文件格式。它适合本地快速启动项目但不等于企业级 Secret 管理系统。企业中可以使用.env.example帮助开发者了解变量清单但真实值通常应由 Secret Manager、Vault、KMS、CI Secrets、Kubernetes Secret 或配置中心在运行时注入。类型示例是否敏感推荐管理方式普通配置APP_ENV、PORT、LOG_LEVEL通常不敏感配置文件、环境变量、配置中心半敏感配置INTERNAL_API_BASE_URL视情况而定环境隔离、访问控制SecretDB_PASSWORD、JWT_SECRET、API_KEY敏感Secret Manager / Vault / CI Secret3. 企业级 .env / Secret 管理核心原则3.1 永远不要提交真实 .env真实.env文件通常包含数据库密码、Token、私钥、内部服务地址等内容。一旦提交到 Git即使后来删除历史记录中仍可能保留泄露副本。正确做法是仓库提交.env.example或.env.template。本地开发者自行创建.env.local。生产、预发、测试环境的真实密钥由平台注入。使用 Git secret scanning、pre-commit hook 或 CI 扫描阻止密钥进入仓库。3.2 不同环境必须隔离企业项目通常至少区分以下环境环境用途配置要求local开发者本机可以使用本地.env.local不能复用生产密钥development联调环境使用开发环境数据库和低权限密钥test自动化测试使用测试专用资源数据可重置staging预发环境尽量接近生产但密钥仍应独立production生产环境最高安全要求必须审计、轮换、最小权限低环境泄露不应该影响生产。因此不要让 local、dev、test、staging、production 共用同一组数据库密码、JWT 密钥或第三方 API Key。3.3 最小权限最小权限原则要求一个服务只拿自己真正需要的密钥。例如一个订单服务可能需要支付接口 Key但不应该拥有用户中心数据库的管理员密码。一个只负责向量检索的服务需要读取向量库但不应该拥有删除整个索引的管理员权限。企业中常见拆分方式包括按服务拆分密钥。按环境拆分密钥。按用途拆分密钥。按租户拆分密钥。使用服务账号而不是个人账号。3.4 密钥必须可轮换、可吊销、可审计如果一个密钥泄露后无法快速吊销或者不知道哪些服务使用它这就是治理失败。企业级 Secret 管理至少应该回答谁创建了这个密钥哪些服务能读取它它什么时候被读取过它多久轮换一次泄露后如何吊销新旧密钥如何平滑切换3.5 密钥值不进入 Git、日志、镜像层和制品密钥不应该出现在Git 仓库和 Git 历史。CI/CD 日志。Dockerfile。Docker 镜像层。前端构建产物。错误堆栈和日志平台。工单、聊天记录、截图、文档。AI prompt、completion 和调试追踪中。4. 推荐仓库结构与 .env.example 示例企业项目中仓库里应该提交“变量清单”和“安全占位符”而不是提交真实值。推荐结构如下project-root/ ├─ .env.example # 可提交只放变量名和安全占位符 ├─ .env.local # 本地个人配置不提交 ├─ .gitignore ├─ src/ └─ README.md推荐.gitignore.env .env.* !.env.example !.env.test.example这段规则的含义是忽略.env。忽略.env.local、.env.production、.env.staging等真实配置文件。允许提交.env.example和.env.test.example这类安全模板。示例.env.exampleAPP_ENVlocal PORT3000 LOG_LEVELinfo DATABASE_URL__SET_IN_SECRET_MANAGER__ REDIS_URL__SET_IN_SECRET_MANAGER__ JWT_SECRET__SET_IN_SECRET_MANAGER__ THIRD_PARTY_API_BASE_URLhttps://api.example.com THIRD_PARTY_API_KEY__SET_IN_SECRET_MANAGER__.env.example的价值是让新人知道项目启动需要哪些变量而不是告诉他真实密钥是什么。真实值应通过本地安全渠道、企业 Secret Manager、CI Secret 或运行平台注入。需要注意前端项目中以PUBLIC_、NEXT_PUBLIC_、VITE_等前缀暴露的变量通常会进入浏览器构建产物因此不能放任何密钥。错误示例VITE_PAYMENT_SECRET_KEY__DO_NOT_PUT_SECRET_IN_FRONTEND__正确思路是前端只持有公开配置真正的密钥由后端服务保存和调用。5. 应用启动时的配置校验企业项目不能等线上流量进来后才发现配置缺失。配置错误应该在应用启动阶段就失败也就是 fail fast。以 TypeScript Zod 为例可以把环境变量集中校验import{z}fromzodconstenvSchemaz.object({APP_ENV:z.enum([local,development,test,staging,production]),PORT:z.coerce.number().int().positive().default(3000),DATABASE_URL:z.string().min(1),JWT_SECRET:z.string().min(32),LOG_LEVEL:z.enum([debug,info,warn,error]).default(info)})exportconstenvenvSchema.parse(process.env)然后业务代码统一从env对象读取配置而不是到处直接访问process.envimport{env}from./envapp.listen(env.PORT)这样做有几个好处必填变量缺失时服务启动立即失败。变量类型集中转换例如把字符串端口转换成数字。生产环境不会意外使用弱默认值。业务代码不需要反复处理undefined。变量清单可以和.env.example对齐。更严格的企业项目还会区分不同环境的校验规则。例如本地可以允许某些 Mock 配置生产必须要求真实 SecretconstparsedEnvenvSchema.parse(process.env)if(parsedEnv.APP_ENVproductionparsedEnv.JWT_SECRET.length64){thrownewError(JWT_SECRET is too weak for production)}exportconstenvparsedEnv这里要注意启动错误可以提示“哪个变量缺失或不合法”但不要把变量真实值打印出来。6. CI/CD 中如何安全注入密钥企业流水线中密钥不应该写进 YAML 文件本身而应该保存在 CI/CD 平台的 Secrets 配置区。Pipeline 运行时再临时注入为环境变量。通用 CI/CD 示例name:deployon:push:branches:[main]jobs:deploy:runs-on:ubuntu-latestenv:APP_ENV:productionDATABASE_URL:${{secrets.DATABASE_URL}}JWT_SECRET:${{secrets.JWT_SECRET}}steps:-uses:actions/checkoutv4-run:npm ci-run:npm test-run:npm run build不同平台的语法会有差异例如 GitHub Actions、Gitee Actions、GitLab CI、Jenkins、云厂商流水线都有自己的写法。但核心思想一致Secret 存在平台密钥区。运行时注入。不提交到 Git。不输出到日志。不写入构建产物。CI/CD 中常见危险写法包括steps:-run:echo DATABASE_URL$DATABASE_URL-run:docker build--build-arg JWT_SECRET$JWT_SECRET .第一行会把密钥打印进日志。第二行可能让密钥进入构建历史或镜像层。更安全的思路是镜像构建阶段只构建代码和依赖运行阶段再由部署平台注入密钥。steps:-run:docker build-t your-service:${{github.sha}}.-run:npm run deploy如果确实需要在部署命令中使用密钥也要确认平台日志会自动脱敏并避免使用set -x、printenv、env等会打印全部变量的命令。7. Docker / Docker Compose / Kubernetes 中的配置与密钥7.1 Docker 本地运行本地学习或开发时可以使用--env-filedocker run --env-file .env.local your-service:local但生产环境不应依赖开发者手动维护的.env.production文件而应由部署平台、Secret Manager 或编排系统注入。7.2 Docker Compose本地开发可以使用 Compose 的env_fileservices:api:image:your-service:localenv_file:-.env.localports:-3000:3000这适合本地开发但不代表生产密钥已经被安全治理。生产环境仍应考虑访问控制、审计、轮换和平台级注入。7.3 KubernetesKubernetes 通常用 ConfigMap 管普通配置用 Secret 管敏感配置。apiVersion:v1kind:Secretmetadata:name:app-secretstype:OpaquestringData:DATABASE_URL:__SET_IN_SECRET_MANAGER_OR_EXTERNAL_SECRET__JWT_SECRET:__SET_IN_SECRET_MANAGER_OR_EXTERNAL_SECRET__---apiVersion:v1kind:ConfigMapmetadata:name:app-configdata:APP_ENV:productionLOG_LEVEL:infoPod 中引用示例apiVersion:apps/v1kind:Deploymentmetadata:name:your-servicespec:replicas:2selector:matchLabels:app:your-servicetemplate:metadata:labels:app:your-servicespec:containers:-name:apiimage:your-service:__IMAGE_TAG__envFrom:-configMapRef:name:app-config-secretRef:name:app-secrets需要明确Kubernetes Secret 本身不是企业密钥治理的终点。成熟企业还会关注etcd 加密。RBAC 最小权限。谁能读取 Secret。Secret 访问审计。密钥轮换自动化。External Secrets、Vault 或云厂商 Secret Manager 集成。8. 密钥生命周期创建、分发、使用、轮换、吊销、审计企业级密钥管理强调生命周期而不是只强调保存位置。8.1 创建生产密钥不应该由开发者随手在本地生成后复制到各处。更推荐通过以下方式创建云控制台或 Secret Manager。Vault / KMS。安全团队批准的服务账号。自动化基础设施脚本。密钥命名也应规范例如your-service/production/database-url your-service/production/jwt-secret your-service/staging/llm-api-key8.2 分发密钥分发应该通过受控系统完成而不是通过聊天软件、截图、邮件或文档。常见方式包括CI/CD Secret 注入。Secret Manager 按服务账号授权读取。Kubernetes External Secrets 同步。Vault Agent 注入。平台运行时环境变量注入。8.3 使用应用程序通常只需要在运行时读取密钥并在内存中使用。不要把密钥写入临时文件、日志、缓存、前端页面或错误响应。8.4 轮换密钥轮换包括定期轮换和事件触发轮换。常见触发条件人员离职或权限调整。供应商安全事件。日志误打印。Git 提交泄露。CI/CD 日志泄露。生产访问异常。对于数据库密码、JWT 密钥等核心 Secret需要设计平滑轮换策略。例如 JWT 可以支持一段时间内同时验证旧密钥和新密钥但只用新密钥签发新 token。8.5 吊销发现泄露后第一优先级不是“删除泄露文件”而是让旧密钥立即失效。因为泄露内容可能已经被复制、缓存或同步到其他地方。8.6 审计审计关注的是谁创建了密钥。谁修改了密钥。哪个服务读取了密钥。什么时候读取。是否出现异常读取频率。是否存在越权访问。泄露应急流程可以这样设计发现疑似泄露 → 立即吊销或禁用旧密钥 → 生成新密钥并更新 Secret Manager → 重新部署服务 → 检查访问日志和异常账单 → 清理 Git 历史 / CI 日志 / 镜像制品中的泄露副本 → 复盘并补充扫描规则9. 日志与错误处理中的 Secret 泄露风险很多密钥泄露不是因为开发者主动提交.env而是因为日志、报错、调试工具或监控系统记录了敏感值。错误示例logger.error(failed to connect database,{databaseUrl:process.env.DATABASE_URL,error})这会把完整数据库连接串写入日志。连接串里可能包含用户名、密码、主机、库名等信息。更安全的做法是只记录排障所需的非敏感字段logger.error(failed to connect database,{host:newURL(process.env.DATABASE_URL!).hostname,errorName:errorinstanceofError?error.name:UnknownError})企业项目中建议使用 allowlist 思路只允许明确安全的字段进入日志而不是把整个对象展开打印。危险示例logger.info({env:process.env})logger.error({requestHeaders:req.headers})logger.debug({config})更安全的脱敏示例constSECRET_PATTERNS[// 规则1匹配 Bearer Token例如 Authorization: Bearer sk-xxxxxx/Bearer\s[A-Za-z0-9._-]/g,/(api[_-]?key|token|secret|password)([^\s])/gi,/(DATABASE_URL|REDIS_URL|JWT_SECRET)([^\s])/g]functionredactText(input:string):string{returnSECRET_PATTERNS.reduce((text,pattern)text.replace(pattern,$1[REDACTED]),input)}logger.info({requestId,message:redactText(message)})上面的示例展示的是思路生产项目通常还应结合日志框架、网关、APM、错误追踪平台提供的脱敏能力并通过测试确保敏感字段不会进入日志。10. AI 项目的额外特殊规范AI 项目不是“普通项目多一个 API Key”这么简单。它的特殊性在于模型调用链路里会经过 prompt、completion、embedding、RAG 文档、向量数据库、工具调用、Agent 执行日志等数据而这些内容都可能包含敏感信息。AI 项目还常常按 token、模型等级、上下文长度、并发量计费。因此它不仅有传统安全风险还有费用失控和数据合规风险。10.1 LLM Provider API Key 管理AI 项目的模型 API Key 应该和普通第三方 API Key 一样进入 Secret Manager、Vault、CI Secret 或运行平台密钥区。推荐原则local、dev、staging、production 使用不同 Key。Chat、Embedding、Eval、Batch Job 使用不同 Key。生产禁止使用开发者个人 Key。Key 绑定服务账号或组织级账号。Key 权限尽量最小化。设置调用额度、速率限制和告警。Provider-neutral 的.env.example可以这样写LLM_PROVIDERexample-provider LLM_MODELexample-chat-model LLM_API_KEY__SET_IN_SECRET_MANAGER__ EMBEDDING_PROVIDERexample-provider EMBEDDING_MODELexample-embedding-model EMBEDDING_API_KEY__SET_IN_SECRET_MANAGER__ VECTOR_DB_URL__SET_IN_SECRET_MANAGER__ VECTOR_DB_API_KEY__SET_IN_SECRET_MANAGER__ LLM_MAX_OUTPUT_TOKENS4096 LLM_REQUEST_TIMEOUT_MS60000 LLM_MONTHLY_BUDGET_USD500 LLM_PROMPT_LOGGING_MODEmetadata_only MODEL_DATA_RETENTION_POLICYzero_retention_required这里的重点不是某个具体模型供应商而是配置设计模型供应商、模型名、API Key、向量库凭证、超时、token 上限、预算、日志模式和数据保留策略都应该显式配置并受控。10.2 Chat Key、Embedding Key、Eval Key 分离很多 AI 项目会同时做在线对话。文档 embedding。离线评测。批量总结。Agent 工具调用。如果所有任务共用一个高权限 Key一旦泄露就会影响所有能力。更好的做法是按用途拆分用途示例变量权限建议在线对话LLM_API_KEY限制模型、限额、限流EmbeddingEMBEDDING_API_KEY只允许 embedding 相关能力离线评测EVAL_LLM_API_KEY可低优先级、独立预算后台批处理BATCH_LLM_API_KEY独立队列和预算10.3 模型白名单与费用控制AI 项目中不能让用户从前端任意传入模型名、max tokens 或 temperature 等关键参数。错误示例constresultawaitllm.chat({model:req.body.model,maxTokens:req.body.maxTokens,messages:req.body.messages})更安全的做法是在服务端维护模型白名单和参数上限constMODEL_ALLOWLISTnewSet([example-chat-model,example-fast-model])constrequestedModelString(req.body.model??example-chat-model)constmodelMODEL_ALLOWLIST.has(requestedModel)?requestedModel:example-chat-modelconstmaxTokensMath.min(Number(req.body.maxTokens??1024),env.LLM_MAX_OUTPUT_TOKENS)constresultawaitllm.chat({model,maxTokens,messages:req.body.messages})企业项目还应做用户级限流。租户级限流。月度预算。单次请求 token 上限。高价模型审批。异常调用告警。10.4 Prompt / Completion 也是敏感数据AI 项目的 prompt 可能包含用户个人信息。客户合同。源码片段。内部文档。数据库查询结果。API Key 或 Token。工具调用参数。因此生产环境不应该默认记录完整 prompt 和 completion。更推荐默认记录 metadatarequestIdtenantIduserIdmodeltoken usagelatencystatuserror type如确实需要采样保存 prompt 用于调试或评测也应满足明确用户授权或企业合规依据。脱敏后保存。采样比例受控。保留时间短。可查询、可删除、可审计。AI 请求日志脱敏示例constSECRET_PATTERNS[/sk-[A-Za-z0-9_-]{20,}/g,/Bearer\s[A-Za-z0-9._-]/g,/(api[_-]?key|token|secret|password)([^\s])/gi]functionredactText(input:string):string{returnSECRET_PATTERNS.reduce((text,pattern)text.replace(pattern,[REDACTED]),input)}functionsafeLogLLMRequest(req:{requestId:stringtenantId:stringmodel:stringprompt?:string}){logger.info({requestId:req.requestId,tenantId:req.tenantId,model:req.model,promptPreview:req.prompt?redactText(req.prompt).slice(0,200):undefined})}10.5 RAG / 向量数据库凭证安全RAG 项目通常会引入文档解析、embedding、向量库写入、向量检索、答案生成等链路。不同链路需要不同权限。推荐拆分服务需要的权限不应该拥有的权限ingestion worker写入文档、写入向量删除全部租户数据retrieval service读取当前租户向量读取其他租户数据admin job索引维护在线回答用户问题eval job读取评测集访问生产客户原始数据RAG 多租户项目中tenantId必须来自服务端认证上下文而不是来自用户请求体。constdocsawaitvectorDb.search({query,filter:{tenantId:req.body.tenantId}})上面这种写法是危险的因为用户可以伪造tenantId越权检索。更安全的写法consttenantIdreq.auth.tenantIdconstdocsawaitvectorDb.search({query,filter:{tenantId,visibility:active}})10.6 模型数据保留与合规企业 AI 项目需要确认模型供应商或运行平台的数据策略prompt 和 completion 是否会被保留是否会用于训练数据保留多久是否支持 zero retention 或企业数据协议是否涉及跨境传输是否满足行业合规要求删除请求是否可执行、可审计这些内容不应该由开发者凭感觉决定而应由技术、法务、安全、合规和业务共同确认。10.7 BYOK 场景BYOK 指 Bring Your Own Key客户自带模型 Key 或加密 Key。它常见于 ToB SaaS、私有化部署和多租户 AI 平台。BYOK 的安全要求包括每个租户 Key 独立保存。Key 加密存储。运行时按租户动态读取。租户可自行轮换和吊销。服务端不能把 A 租户 Key 用到 B 租户请求中。日志中不能出现客户 Key。10.8 Agent / MCP / 工具调用的密钥边界AI Agent 项目往往会连接 Git、数据库、工单系统、聊天工具、浏览器、云资源等外部工具。此时风险不只来自模型 API Key还来自工具凭证。关键原则工具凭证不能写进 prompt。模型不应该直接看到原始 Secret。工具调用应由服务端按权限执行。高风险操作需要审批或策略限制。不同工具使用不同服务账号。工具执行日志必须脱敏。例如Agent 可以请求“查询某个工单”但真正的工单系统 Token 应由后端工具层保存和使用而不是作为文本塞进模型上下文。11. 常见反模式与正确做法反模式为什么危险正确做法把.env提交到 GitGit 历史长期保留泄露后难清理提交.env.example真实值进 Secret Manager生产使用开发者个人 Key人员离职或权限变化会影响生产使用服务账号或组织级凭证所有环境共用一个数据库密码低环境泄露可影响生产按环境隔离密钥和权限在日志中打印process.env密钥进入日志平台结构化日志 allowlist 脱敏Dockerfile 写入ENV SECRETxxx密钥进入镜像层运行时注入 Secret前端环境变量保存 Secret浏览器产物可被用户看到Secret 只放后端或平台密钥区CI 中echo $TOKENToken 进入流水线日志避免打印依赖平台脱敏能力.env.example写真实示例密码新人可能照抄甚至误连共享资源只写安全占位符AI 项目记录完整 promptprompt 可能包含 PII、合同、源码、密钥默认 metadata_only必要时脱敏采样RAG tenantId 来自前端用户可伪造 tenantId 越权检索tenantId 来自认证上下文用户可传任意 model id成本和数据策略失控服务端模型白名单Agent 工具 Token 进入 prompt模型上下文和日志可能泄露凭证Token 留在服务端工具层12. 企业落地检查清单是否只提交.env.example不提交真实.env.gitignore是否覆盖.env、.env.*并保留示例文件local、development、test、staging、production 是否使用不同密钥生产密钥是否由 Secret Manager / Vault / CI Secret 管理应用启动时是否校验必要配置生产环境是否禁止弱默认密钥日志是否避免输出环境变量、连接串、Token 和请求头日志平台、错误追踪平台和 APM 是否配置脱敏规则密钥是否有轮换、吊销、审计流程CI/CD 是否避免在日志和制品中泄露密钥Docker 镜像是否避免把密钥写入构建层Kubernetes Secret 是否配合 RBAC、审计、加密和外部密钥系统AI Key 是否按环境、用途、租户隔离Prompt / Completion 是否默认不记录全文RAG / Vector DB 是否做租户隔离和最小权限是否设置 token、费用、并发和模型白名单Agent / 工具调用凭证是否留在服务端工具层而不是进入模型上下文密钥泄露时是否有明确应急流程13. 总结企业级.env配置安全的重点不是简单地把变量放进某个文件而是建立完整的配置和密钥治理体系配置分类、环境隔离、最小权限、运行时注入、启动校验、日志脱敏、轮换吊销、审计追踪和泄露应急。.env更适合作为本地开发便利工具.env.example适合作为安全变量模板生产环境的真实 Secret 应进入 Secret Manager、Vault、KMS、CI Secret、Kubernetes Secret 或企业配置平台并通过权限和审计进行治理。AI 项目还需要额外关注 prompt、completion、embedding、RAG、向量数据库、模型调用、Agent 工具凭证、数据保留和 token 费用控制。真正成熟的 AI 工程实践不只是能调用模型而是能在安全、合规、成本和可维护性之间建立稳定边界。