README.md

# redact

[![Package Version](https://img.shields.io/hexpm/v/redact)](https://hex.pm/packages/redact)
[![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/redact/)

```sh
gleam add redact
```

Further documentation can be found at <https://hexdocs.pm/redact>.

## Usage

The recommended usage of this library is use `secret.Secret` on the edges of IO. 

For instance, if you need to store an `API_KEY` in your application's config record, wrap it with `secret.new` as soon as it is read in, and call `secret.expose` when it is needed to make a request.

Usage of `secret.expose` also allows you to easily audit your code for where your secrets are being used.

```gleam
import envoy
import gleam/int
import gleam/result
import redact/secret

pub type Error {
  Required(name: String)
}

pub type Env {
  Env(
    database_url: secret.Secret(String),
    port: Int,
    secret_key: secret.Secret(String),
  )
}

pub fn extract_env() -> Result(Env, Error) {
  use database_url <- result.try(database_url())
  let port = port()
  use secret_key <- result.try(secret_key())

  Ok(Env(database_url:, port:, secret_key:))
}

pub fn database_url() {
  required_env("DATABASE_URL") |> result.map(secret.new)
}

pub fn port() {
  envoy.get("PORT")
  |> result.map(int.parse)
  |> result.flatten()
  |> result.unwrap(8000)
}

pub fn secret_key() {
  required_env("SECRET_KEY") |> result.map(secret.new)
}

pub fn required_env(name: String) -> Result(String, Error) {
  envoy.get(name)
  |> result.replace_error(Required(name))
}
```

## Caveats

### string.inspect output could change at any time

The implementation of [string.inspect](https://hexdocs.pm/gleam_stdlib/gleam/string.html#inspect) is not considered stable and may change at any time. It is possible in the future that `string.inspect` may change and expose the value stored in a closure. When in doubt, verify that however you output a `secret.Secret` that it does not expose that secret.

### Not cryptographically secure

This library is **not** a cryptographically secure way to store a secret in RAM. It is a convenience. An attacker could find a way to inspect your program's memory at runtime and see the secret in the clear. If your threat model requires more secrecy, please use a different library.

### secret.Secret equality

Two instances of a `secret.Secret` with the same inner value are not equal. The test `secret.Secret("wibble") == secret.Secret("wibble")` appears to work in Erlang, but fails in JavaScript.


## Sample output

If you use the `string.inspect` method to convert a Gleam value to a string, any values of type `secret.Secret` will be hidden.

For instance, given the following record:

```gleam
pub type Env {
  Env(
    api_key: secret.Secret(String),
  )
}
```

In the Erlang target, the Secret record looks like the following when passed to `string.inspect`

```gleam
string.inspect(
  Env(api_key: secret.new(
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzEyIiwibmFtZSI6IkpvaG4gQnJvd24iLCJpYXQiOjE1MTYyMzkwMjJ9.__2EDbjt_49s6ssnr1tQgg--xEK6fbEK5bWG85OqB_c",
  )),
)
// -> "Env(Secret(//fn() { ... }))"
```

In JavaScript, the result of `string.inspect` looks slightly different:

```gleam
string.inspect(
  Env(api_key: secret.new(
    "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMzEyIiwibmFtZSI6IkpvaG4gQnJvd24iLCJpYXQiOjE1MTYyMzkwMjJ9.__2EDbjt_49s6ssnr1tQgg--xEK6fbEK5bWG85OqB_c",
  )),
)
// -> "Env(api_key: Secret(expose: //fn() { ... }))"
```

Using `echo` in the Erlang target will print the following:

```
test/redact_test.gleam:53
Env(Secret(//fn() { ... }))
```

In JavaScript, `echo` will print the following:

```
test/redact_test.gleam:53
Env(api_key: Secret(expose: //fn() { ... }))
```

## Development

```sh
gleam test  # Run the tests
gleam test --target javascript # Run the tests
```