企业级项目 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
配置是影响程序行为的参数,例如:
- 服务端口:
PORT=3000 - 日志级别:
LOG_LEVEL=info - 当前运行环境:
APP_ENV=production - 请求超时时间:
REQUEST_TIMEOUT_MS=10000 - 是否开启某个功能:
FEATURE_X_ENABLED=false
配置不一定敏感。比如端口号通常不是密钥,但错误配置仍可能导致线上事故。
2.2 环境变量 Environment Variable
环境变量是向程序注入配置的一种方式。程序启动时可以从操作系统进程环境中读取变量,例如 Node.js 中的process.env.PORT。
环境变量本身不是安全机制。它只是传递配置的通道。变量是否安全,取决于:
- 值是否敏感。
- 谁能读取运行环境。
- 是否被写入日志、构建产物或镜像层。
- 是否有访问控制、审计和轮换机制。
2.3 Secret / 密钥
Secret 是具有访问能力的敏感值,例如:
- 数据库密码:
DB_PASSWORD - 数据库连接串:
DATABASE_URL - JWT 签名密钥:
JWT_SECRET - 第三方平台 API Key:
PAYMENT_API_KEY - 云厂商 AK/SK:
CLOUD_ACCESS_KEY_ID、CLOUD_SECRET_ACCESS_KEY - LLM Provider API Key:
LLM_API_KEY - Vector DB Token:
VECTOR_DB_API_KEY
Secret 一旦泄露,攻击者可能直接访问数据、调用接口、伪造身份或消耗资源。
2.4 .env
.env是一种常见的本地开发配置文件格式。它适合本地快速启动项目,但不等于企业级 Secret 管理系统。
企业中可以使用.env.example帮助开发者了解变量清单,但真实值通常应由 Secret Manager、Vault、KMS、CI Secrets、Kubernetes Secret 或配置中心在运行时注入。
| 类型 | 示例 | 是否敏感 | 推荐管理方式 |
|---|---|---|---|
| 普通配置 | APP_ENV、PORT、LOG_LEVEL | 通常不敏感 | 配置文件、环境变量、配置中心 |
| 半敏感配置 | INTERNAL_API_BASE_URL | 视情况而定 | 环境隔离、访问控制 |
| Secret | DB_PASSWORD、JWT_SECRET、API_KEY | 敏感 | Secret Manager / Vault / CI Secret |
3. 企业级 .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.example:
APP_ENV=local PORT=3000 LOG_LEVEL=info DATABASE_URL=__SET_IN_SECRET_MANAGER__ REDIS_URL=__SET_IN_SECRET_MANAGER__ JWT_SECRET=__SET_IN_SECRET_MANAGER__ THIRD_PARTY_API_BASE_URL=https://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}from"zod"constenvSchema=z.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")})exportconstenv=envSchema.parse(process.env)然后业务代码统一从env对象读取配置,而不是到处直接访问process.env:
import{env}from"./env"app.listen(env.PORT)这样做有几个好处:
- 必填变量缺失时,服务启动立即失败。
- 变量类型集中转换,例如把字符串端口转换成数字。
- 生产环境不会意外使用弱默认值。
- 业务代码不需要反复处理
undefined。 - 变量清单可以和
.env.example对齐。
更严格的企业项目还会区分不同环境的校验规则。例如本地可以允许某些 Mock 配置,生产必须要求真实 Secret:
constparsedEnv=envSchema.parse(process.env)if(parsedEnv.APP_ENV==="production"&&parsedEnv.JWT_SECRET.length<64){thrownewError("JWT_SECRET is too weak for production")}exportconstenv=parsedEnv这里要注意:启动错误可以提示“哪个变量缺失或不合法”,但不要把变量真实值打印出来。
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/checkout@v4-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-file:
docker run --env-file .env.local your-service:local但生产环境不应依赖开发者手动维护的.env.production文件,而应由部署平台、Secret Manager 或编排系统注入。
7.2 Docker Compose
本地开发可以使用 Compose 的env_file:
services:api:image:your-service:localenv_file:-.env.localports:-"3000:3000"这适合本地开发,但不代表生产密钥已经被安全治理。生产环境仍应考虑访问控制、审计、轮换和平台级注入。
7.3 Kubernetes
Kubernetes 通常用 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:"production"LOG_LEVEL:"info"Pod 中引用示例:
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_PROVIDER=example-provider LLM_MODEL=example-chat-model LLM_API_KEY=__SET_IN_SECRET_MANAGER__ EMBEDDING_PROVIDER=example-provider EMBEDDING_MODEL=example-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_TOKENS=4096 LLM_REQUEST_TIMEOUT_MS=60000 LLM_MONTHLY_BUDGET_USD=500 LLM_PROMPT_LOGGING_MODE=metadata_only MODEL_DATA_RETENTION_POLICY=zero_retention_required这里的重点不是某个具体模型供应商,而是配置设计:模型供应商、模型名、API Key、向量库凭证、超时、token 上限、预算、日志模式和数据保留策略都应该显式配置并受控。
10.2 Chat Key、Embedding Key、Eval Key 分离
很多 AI 项目会同时做:
- 在线对话。
- 文档 embedding。
- 离线评测。
- 批量总结。
- Agent 工具调用。
如果所有任务共用一个高权限 Key,一旦泄露就会影响所有能力。更好的做法是按用途拆分:
| 用途 | 示例变量 | 权限建议 |
|---|---|---|
| 在线对话 | LLM_API_KEY | 限制模型、限额、限流 |
| Embedding | EMBEDDING_API_KEY | 只允许 embedding 相关能力 |
| 离线评测 | EVAL_LLM_API_KEY | 可低优先级、独立预算 |
| 后台批处理 | BATCH_LLM_API_KEY | 独立队列和预算 |
10.3 模型白名单与费用控制
AI 项目中,不能让用户从前端任意传入模型名、max tokens 或 temperature 等关键参数。
错误示例:
constresult=awaitllm.chat({model:req.body.model,maxTokens:req.body.maxTokens,messages:req.body.messages})更安全的做法是在服务端维护模型白名单和参数上限:
constMODEL_ALLOWLIST=newSet(["example-chat-model","example-fast-model"])constrequestedModel=String(req.body.model??"example-chat-model")constmodel=MODEL_ALLOWLIST.has(requestedModel)?requestedModel:"example-chat-model"constmaxTokens=Math.min(Number(req.body.maxTokens??1024),env.LLM_MAX_OUTPUT_TOKENS)constresult=awaitllm.chat({model,maxTokens,messages:req.body.messages})企业项目还应做:
- 用户级限流。
- 租户级限流。
- 月度预算。
- 单次请求 token 上限。
- 高价模型审批。
- 异常调用告警。
10.4 Prompt / Completion 也是敏感数据
AI 项目的 prompt 可能包含:
- 用户个人信息。
- 客户合同。
- 源码片段。
- 内部文档。
- 数据库查询结果。
- API Key 或 Token。
- 工具调用参数。
因此,生产环境不应该默认记录完整 prompt 和 completion。更推荐默认记录 metadata:
- requestId
- tenantId
- userId
- model
- token usage
- latency
- status
- error 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必须来自服务端认证上下文,而不是来自用户请求体。
constdocs=awaitvectorDb.search({query,filter:{tenantId:req.body.tenantId}})上面这种写法是危险的,因为用户可以伪造tenantId越权检索。
更安全的写法:
consttenantId=req.auth.tenantIdconstdocs=awaitvectorDb.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提交到 Git | Git 历史长期保留,泄露后难清理 | 提交.env.example,真实值进 Secret Manager |
| 生产使用开发者个人 Key | 人员离职或权限变化会影响生产 | 使用服务账号或组织级凭证 |
| 所有环境共用一个数据库密码 | 低环境泄露可影响生产 | 按环境隔离密钥和权限 |
在日志中打印process.env | 密钥进入日志平台 | 结构化日志 allowlist + 脱敏 |
Dockerfile 写入ENV SECRET=xxx | 密钥进入镜像层 | 运行时注入 Secret |
| 前端环境变量保存 Secret | 浏览器产物可被用户看到 | Secret 只放后端或平台密钥区 |
CI 中echo $TOKEN | Token 进入流水线日志 | 避免打印,依赖平台脱敏能力 |
.env.example写真实示例密码 | 新人可能照抄,甚至误连共享资源 | 只写安全占位符 |
| AI 项目记录完整 prompt | prompt 可能包含 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 工程实践,不只是能调用模型,而是能在安全、合规、成本和可维护性之间建立稳定边界。