Effect 的 Redacted 怎么防止密钥泄漏到日志?

密钥是一个普通字符串,一旦被 console.log(config) 或错误对象带进日志,就永久留在日志系统里了,之后你只能轮换密钥。

答案是:用 Redacted 包住密钥。 Config.Redacted("API_KEY") 读出来的不是字符串而是 Redacted<string>,被打印、被字符串化、被 JSON 序列化时都显示为 <redacted>,要取明文必须显式调用 Redacted.value。

它的价值在于把「不泄漏」从「每个人都记得别打印」变成「类型上就拿不到明文」。下面讲怎么用、什么时候取出明文,以及它的边界。

Redacted 打印出来是什么样?

import { Redacted } from "effect"

const key = Redacted.make("sk-secret")
console.log(key)                   // <redacted>
console.log(`${key}`)              // <redacted>
console.log(JSON.stringify({ key })) // {"key":"<redacted>"}
Redacted.value(key)                // "sk-secret",只在真正要用的地方取出

console.log、模板字符串和 JSON.stringify 三种情形输出的都是 <redacted>,我在本地都跑过。只有 Redacted.value 会拿到明文。

从环境变量读密钥时怎么用?

用 Config.Redacted,它把读到的值直接包成 Redacted<string>:

import { Config } from "effect"

const AppConfig = Config.all({
  apiKey: Config.Redacted("API_KEY")
})

这样密钥从读出的那一刻起就没有裸字符串形态。Config 本身怎么在 Layer 构造时读取、缺项时怎么在启动阶段失败,见 Effect 里怎么读配置,而不是直接读 process.env?。

什么时候可以取出明文?

只在调用边界解开,比如设置 HTTP 头的那一行。解开后的明文不要再赋给变量往下传。

好处是代码评审时 Redacted.value 这一行很显眼:每一处出现明文的地方都是显式的,数量少,也容易审。

密钥应该有默认值吗?

不应该。缺密钥应该是启动失败,而不是带着一个开发环境的占位值悄悄跑在生产上。判断标准是「缺了这个值,程序继续运行是否安全」,对密钥和连接串来说答案是不安全。

Redacted 能防住什么,防不住什么?

它不是加密,内存里仍然是明文。它防的是「顺手泄漏」,也就是日志与序列化这类场景,不是恶意读取。解开之后的明文该怎么保护,仍是你的责任。

有了 Redacted,把整个配置对象打日志不再会泄漏密钥,但我仍建议只打必要字段,并且只在启动时打一次。

版本说明:原稿没有标注 effect 的具体版本号,本文也不给出版本。console.log、模板字符串、JSON.stringify 三种输出情形已在本地跑过;Config.Redacted 与使用原则沿用原稿的描述,没有在这里单独验证。