The Mask library for Clojure
(This is a copy of the readme file from the repository.)
Mask is a small library to prevent secrets from being logged, printed or leaked in any similar way. Ships tags for Clojure, EDN and Aero.
Why? Because I’ve been in such a situation three times, namely:
- We don’t mask the secrets.
- Someone logs the entire config.
- Secrets have leaked!
- Rotate all the keys, tokens, etc.
- Change the team and face the same.
This library is an attempt to break this vicious circle.
Installation
Leiningen/Boot:
[com.github.igrishaev/mask "0.1.0"]
Clojure CLI/deps.edn:
com.github.igrishaev/mask {:mvn/version "0.1.0"}
Usage
The mask.core
namespace provides mask
and unmask
functions. Pass a value
to mask
to make it safe for logging or printing in REPL:
(in-ns 'mask.core)
#namespace[mask.core]
(def -m (mask "Secret123"))
-m
<< masked >>
(str "The password is " -m)
"The password is << masked >>"
Masking is idempotent meaning that you can mask the same value multiple times but the result will be one-level masked value:
(-> -m mask mask mask)
<< masked >>
To release a value from a mask, unmask
it:
(unmask -m)
"Secret123"
Unmasking is idempotent a well:
(-> -m unmask unmask unmask)
"Secret123"
Note: the library treats nil
as an error value that cannot be masked. You’ll
get an exception:
(mask nil)
Execution error (IllegalArgumentException) at ... (core.clj:34).
Cannot mask a nil value
Masking an empty value signals you’re doing something wrong. Most likely you’ve missed a corresponding key or an environment variable. Thus, the further work makes no sense.
Spec
The mask.spec
module provides the ::mask
spec that checks if a value is
really masked. An example from the tests:
(let [config
{:username "Ivan"
:password #mask "secret"}]
(is (s/valid? ::config config)))
;; true
Clojure tag
The built-in #mask
tag wraps any value with a mask:
=> {:token #mask "abc123" :password "SecretABC"}
{:token << masked >>, :password "SecretABC"}
EDN tag
There is a reader-edn
function that acts like an EDN reader for the same tag:
(let [source (-> "{:foo #mask 42}")]
(edn/read-string {:readers {'mask reader-edn}}
source))
;; {:foo << masked >>}
Aero tag
To extend Aero with the #mask
tag, import the mask.aero
namespace:
(require 'mask.aero)
Then read a config with the tag:
;; config.edn
{:foo #mask #env "SOME_PASSWORD"}
;; code
(aero/read-config (io/resource "config.edn"))
;; {:foo << masked >>}
The Aero dependency is not included. You’ve got to provide it by your own.
Ivan Grishaev, 2023
Нашли ошибку? Выделите мышкой и нажмите Ctrl/⌘+Enter