https://github.com/igrishaev/mask
https://github.com/igrishaev/mask
Last synced: 9 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/igrishaev/mask
- Owner: igrishaev
- License: other
- Created: 2023-02-25T12:40:09.000Z (almost 3 years ago)
- Default Branch: master
- Last Pushed: 2023-03-08T08:18:05.000Z (almost 3 years ago)
- Last Synced: 2025-04-01T20:51:24.762Z (10 months ago)
- Language: Clojure
- Size: 24.4 KB
- Stars: 12
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Mask
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:
```clojure
[com.github.igrishaev/mask "0.1.1"]
```
Clojure CLI/deps.edn:
```clojure
com.github.igrishaev/mask {:mvn/version "0.1.1"}
```
## 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:
```clojure
(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:
```clojure
(-> -m mask mask mask)
<< masked >>
```
To release a value from a mask, `unmask` it:
```clojure
(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:
```clojure
(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:
```clojure
(let [config
{:username "Ivan"
:password #mask "secret"}]
(is (s/valid? ::config config)))
;; true
```
### Clojure tags
The built-in `#mask` tag wraps any value with a mask:
```clojure
=> {:token #mask "abc123" :password "SecretABC"}
{:token << masked >>, :password "SecretABC"}
```
The `#unmask` tag does the opposite: unwraps a masked value:
```clojure
(def token (mask "secret123"))
#unmask token
;; "secret123"
```
### EDN tag
There is a `reader-edn` function that acts like an EDN reader for the same tag:
```clojure
(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:
```clojure
(require 'mask.aero)
```
Then read a config with the tag:
```clojure
;; 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*