# Redacted Secrets

Some configuration values — API keys, database passwords, tokens — must never
appear in logs, error messages, or serialized output. `Redacted<A>` wraps a
sensitive value so that any normal rendering path (string interpolation,
`JSON.stringify`, `console.log`, inspection) shows `<redacted>` instead of the
real value. The underlying value stays recoverable, but only through an explicit,
visible call.

```ts
import { Config, Effect, Redacted } from "effect"

const program = Effect.gen(function* () {
  // Config.redacted parses the value and wraps it in Redacted<string>
  const apiKey = yield* Config.redacted("API_KEY")

  // Logging the wrapper is safe — it renders as <redacted>
  yield* Effect.log(`Loaded API key: ${apiKey}`)

  // Unwrap only at the trusted boundary where the secret is actually used
  return yield* callApi(Redacted.value(apiKey))
})

declare const callApi: (key: string) => Effect.Effect<unknown>

// API_KEY=sk-1234567890 node app.js
// Logs: Loaded API key: <redacted>
```

The key idea: a `Redacted` is safe to pass around, log, and store. The secret is
exposed only at the moment you call `Redacted.value`, which makes those call
sites easy to audit.

## Reading secrets from config

`Config.redacted(name)` is the usual entry point. It reads a string from the
[provider](https://effect.plants.sh/configuration/config-providers/), validates it, and returns a
`Redacted<string>`:

```ts
import { Config, Context, Effect, Layer, Redacted } from "effect"

// A service that needs a secret token to do its job
class GitHubClient extends Context.Service<GitHubClient, {
  readonly listRepos: Effect.Effect<ReadonlyArray<string>>
}>()("app/GitHubClient") {
  static readonly layer = Layer.effect(
    GitHubClient,
    Effect.gen(function* () {
      // Read the secret once, when the layer is built
      const token = yield* Config.redacted("GITHUB_TOKEN")

      return {
        listRepos: Effect.gen(function* () {
          // The unwrapped value never leaves this Effect
          const auth = `Bearer ${Redacted.value(token)}`
          yield* Effect.log("Fetching repos") // token is not logged
          return []
        })
      }
    })
  )
}
```

This is the idiomatic shape: read the secret into a service when its layer is
built, keep it as a `Redacted` inside the service, and unwrap it only where it is
sent to the external system.

## Creating and inspecting Redacted values

Outside of config, build a `Redacted` directly with `Redacted.make`. An optional
`label` is shown in the rendered placeholder, which helps you tell secrets apart
in logs without revealing them.

```ts
import { Redacted } from "effect"

const token = Redacted.make("secret-token", { label: "github-token" })

String(token) // "<redacted:github-token>"
JSON.stringify(token) // "\"<redacted:github-token>\""
Redacted.value(token) // "secret-token"  (explicit, auditable)
```
**Caution:** The `label` is rendered in plain text — never put the secret itself in the
  label. `Redacted` reduces *accidental* disclosure; it is not encryption and
  does not zero memory. The value remains in memory and recoverable until the
  wrapper is wiped or garbage-collected.

## Comparing redacted values

Equality and hashing operate on the underlying value, so two `Redacted`s wrapping
the same secret are equal — without ever exposing it. For custom element types,
`Redacted.makeEquivalence` derives an equivalence from one on the inner value:

```ts
import { Equivalence, Redacted } from "effect"

const a = Redacted.make("1234567890")
const b = Redacted.make("1234567890")
const c = Redacted.make("0000000000")

const eq = Redacted.makeEquivalence(Equivalence.strictEqual<string>())

eq(a, b) // true  — same secret
eq(a, c) // false — different secret
```

## Wiping a secret

When a secret is no longer needed, `Redacted.wipeUnsafe` removes it from the
internal registry so future `Redacted.value` calls on that wrapper fail. This is
a best-effort scrub for long-lived processes, not a security guarantee.

```ts
import { Redacted } from "effect"

const token = Redacted.make("one-time-token")

Redacted.value(token) // "one-time-token"

Redacted.wipeUnsafe(token)

// Redacted.value(token) now throws: "Unable to get redacted value"
```
**Note:** Prefer the `Config.redacted` flow for anything coming from the environment —
  it gives you validation and a `Redacted` in one step. Reach for
  `Redacted.make` when wrapping secrets produced inside your own code (e.g. a
  generated session token).

## Redacted in schemas and config

`Config.redacted(name)` is built on `Schema.Redacted(Schema.String)` — it parses
a string from the active provider and wraps it in a `Redacted<string>` in one
step. Use the schema directly when a secret is a field of a larger decoded
structure (a request body, a config object) rather than a standalone config key.

```ts
import { Config, ConfigProvider, Effect, Redacted } from "effect"

const program = Effect.gen(function* () {
  const token = yield* Config.redacted("API_TOKEN")
  return Redacted.value(token) // unwrap at the trusted boundary
})

const provider = ConfigProvider.fromUnknown({ API_TOKEN: "sk-secret" })

program.pipe(Effect.provide(ConfigProvider.layer(provider)))
```

See [Config](https://effect.plants.sh/configuration/config/) for the full set of config constructors and
[Schema](https://effect.plants.sh/schema/) for using `Schema.Redacted` inside larger decoded shapes.

## Redacted API reference

The `Redacted` module is small — a constructor, an unwrapper, a guard, a wipe,
and an equivalence builder — plus the rendering and equality behavior baked into
every wrapper. This section enumerates every public export.
**Tip:** For the data-types perspective (Redacted as a generic wrapper, not just for
  config secrets), see [Redacted](https://effect.plants.sh/data-types/redacted/). This page focuses on
  the secret/config workflow and the full operation list.

### Redacted.make

Wraps a sensitive value, returning a `Redacted<A>`. The optional `label` is shown
inside the rendered placeholder so you can tell wrappers apart in logs. The type
parameter defaults to `string`.

```ts
import { Redacted } from "effect"

const plain = Redacted.make("sk-1234567890")
String(plain) // => "<redacted>"

const labeled = Redacted.make("sk-1234567890", { label: "api-key" })
String(labeled) // => "<redacted:api-key>"

// Works with any value type, not just strings
const creds = Redacted.make({ user: "admin", pass: "hunter2" })
String(creds) // => "<redacted>"
```

### Redacted.value

Unwraps a `Redacted`, returning the underlying value. This is the only way to
read the secret, so call sites are easy to audit. Throws
`Error("Unable to get redacted value")` if the wrapper has been wiped.

```ts
import { Redacted } from "effect"

const token = Redacted.make("secret-token")

Redacted.value(token) // => "secret-token"
```

### Redacted.isRedacted

A type guard that returns `true` for any `Redacted` wrapper. When it returns
`true`, TypeScript narrows the value to `Redacted<unknown>`.

```ts
import { Redacted } from "effect"

const secret = Redacted.make("my-secret")

Redacted.isRedacted(secret) // => true
Redacted.isRedacted("plain") // => false
Redacted.isRedacted(null) // => false
```

### Redacted.wipeUnsafe

Deletes the wrapper's stored value from the internal redacted registry, so future
`Redacted.value` calls on that wrapper throw. Returns `true` if a value was
present and removed, `false` otherwise. This does not zero memory and does not
affect other references to the original value — it is a best-effort scrub, not a
cryptographic guarantee.

```ts
import { Redacted } from "effect"

const token = Redacted.make("one-time-token")

Redacted.value(token) // => "one-time-token"

Redacted.wipeUnsafe(token) // => true (value was removed)
Redacted.wipeUnsafe(token) // => false (nothing left to remove)

// Redacted.value(token) now throws: "Unable to get redacted value"
```

### Redacted.makeEquivalence

Derives an `Equivalence<Redacted<A>>` from an `Equivalence<A>`, comparing the
underlying values without exposing them. Useful when an API needs to compare
secrets structurally (e.g. a non-string payload).

```ts
import { Equivalence, Redacted } from "effect"

const eq = Redacted.makeEquivalence(
  Equivalence.mapInput(
    Equivalence.String,
    (c: { token: string }) => c.token
  )
)

const a = Redacted.make({ token: "abc" })
const b = Redacted.make({ token: "abc" })
const c = Redacted.make({ token: "xyz" })

eq(a, b) // => true  — same token, never exposed
eq(a, c) // => false
```

### Render behavior: toString / toJSON

Every wrapper's `toString()` and `toJSON()` return the same `<redacted>` or
`<redacted:label>` placeholder. This is what makes string interpolation,
`JSON.stringify`, `console.log`, and Node inspection all safe by default.

```ts
import { Redacted } from "effect"

const token = Redacted.make("secret", { label: "session" })

`${token}` // => "<redacted:session>"
JSON.stringify({ token }) // => '{"token":"<redacted:session>"}'
```

### Equality and hashing

`Redacted` implements the `Equal.Equal` interface, so structural equality and
hashing operate on the *underlying* value — two wrappers holding the same secret
are equal, without exposing it. The wrapper is also `Pipeable`.

```ts
import { Equal, Redacted } from "effect"

const a = Redacted.make("same")
const b = Redacted.make("same")
const c = Redacted.make("different")

Equal.equals(a, b) // => true
Equal.equals(a, c) // => false
```

### Types

`Redacted<A = string>` is the wrapper interface (it carries the optional `label`
and extends `Equal.Equal` and `Pipeable`). The `Redacted` namespace holds the
type-level helpers:

- `Redacted.Redacted.Value<T>` — extracts the underlying value type from a
  `Redacted` type.
- `Redacted.Redacted.Variance<A>` — the internal covariance marker; you rarely
  reference it directly.

```ts
import { Redacted } from "effect"

type ApiKey = Redacted.Redacted<{ readonly token: string }>
type ApiKeyValue = Redacted.Redacted.Value<ApiKey>
// => { readonly token: string }

const rotate = (value: ApiKeyValue): ApiKeyValue => ({
  token: `${value.token}:rotated`
})

rotate({ token: "secret" }) // => { token: "secret:rotated" }
```