@dotenvx/dotenvx 2.6.0 → 2.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,19 +2,25 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.6.0...main)
5
+ [Unreleased](https://github.com/dotenvx/dotenvx/compare/v2.7.0...main)
6
+
7
+ ## [2.7.0](https://github.com/dotenvx/dotenvx/compare/v2.6.0...v2.7.0) (2026-07-13)
8
+
9
+ ### Added
10
+
11
+ * Added support for `--mask` on `get, run, decrypt` ([#885](https://github.com/dotenvx/dotenvx/pull/885))
6
12
 
7
13
  ## [2.6.0](https://github.com/dotenvx/dotenvx/compare/v2.5.0...v2.6.0) (2026-07-11)
8
14
 
9
15
  ### Added
10
16
 
11
- * Support `native up|down|pull|push` for windows ([#834](https://github.com/dotenvx/dotenvx/pull/884))
17
+ * Support `native up|down|pull|push` for windows ([#884](https://github.com/dotenvx/dotenvx/pull/884))
12
18
 
13
19
  ## [2.5.0](https://github.com/dotenvx/dotenvx/compare/v2.4.2...v2.5.0) (2026-07-11)
14
20
 
15
21
  ### Added
16
22
 
17
- * Support `native up|down|pull|push` for windows ([#833](https://github.com/dotenvx/dotenvx/pull/883))
23
+ * Support `native up|down|pull|push` for windows ([#883](https://github.com/dotenvx/dotenvx/pull/883))
18
24
 
19
25
  ## [2.4.1](https://github.com/dotenvx/dotenvx/compare/v2.4.0...v2.4.1) (2026-07-10)
20
26
 
package/README.md CHANGED
@@ -1471,6 +1471,21 @@ $ dotenvx set HELLO Dotenvx -fk .env.keys -f apps/app1/.env
1471
1471
  $ dotenvx run -fk .env.keys -f apps/app1/.env -- yourcommand
1472
1472
  ```
1473
1473
 
1474
+ </details>
1475
+ <details><summary>`run --mask`</summary><br>
1476
+
1477
+ Inject masked values into the command. By default, up to the first six characters are visible.
1478
+
1479
+ ```sh
1480
+ $ echo "SECRET=abcdefghijkl" > .env
1481
+ $ echo "console.log(process.env.SECRET)" > index.js
1482
+
1483
+ $ dotenvx run --mask --quiet -- node index.js
1484
+ abcdef******
1485
+ ```
1486
+
1487
+ Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
1488
+
1474
1489
  </details>
1475
1490
  <details><summary>`run --token`</summary><br>
1476
1491
 
@@ -1510,6 +1525,20 @@ $ dotenvx get HELLO
1510
1525
  World
1511
1526
  ```
1512
1527
 
1528
+ </details>
1529
+ <details><summary>`get KEY --mask`</summary><br>
1530
+
1531
+ Return a masked environment variable value. By default, up to the first six characters are visible.
1532
+
1533
+ ```sh
1534
+ $ echo "SECRET=abcdefghijkl" > .env
1535
+
1536
+ $ dotenvx get SECRET --mask
1537
+ abcdef******
1538
+ ```
1539
+
1540
+ Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
1541
+
1513
1542
  </details>
1514
1543
  <details><summary>`get KEY --no-native`</summary><br>
1515
1544
 
@@ -2141,6 +2170,23 @@ or send to a file:
2141
2170
  $ dotenvx decrypt --stdout > somefile.txt
2142
2171
  ```
2143
2172
 
2173
+ </details>
2174
+ <details><summary>`decrypt --stdout --mask`</summary><br>
2175
+
2176
+ Decrypt the contents of an encrypted `.env` file to stdout with its values masked. The encrypted `.env` file remains unchanged.
2177
+
2178
+ ```sh
2179
+ $ echo "SECRET=abcdefghijkl" > .env
2180
+ $ dotenvx encrypt
2181
+ ◈ encrypted (.env)
2182
+
2183
+ $ dotenvx decrypt --stdout --mask
2184
+ ...
2185
+ SECRET="abcdef******"
2186
+ ```
2187
+
2188
+ Pass a number to control how many characters are visible, such as `--mask 0` to fully mask values.
2189
+
2144
2190
  </details>
2145
2191
  <details><summary>`keypair`</summary><br>
2146
2192
 
@@ -2725,6 +2771,33 @@ Hello Dotenvx
2725
2771
 
2726
2772
  It defaults to looking for a `.env` file.
2727
2773
 
2774
+ </details>
2775
+ <details><summary>`config(mask: true)` - mask</summary><br>
2776
+
2777
+ Inject and return masked values. By default, up to the first six characters are visible.
2778
+
2779
+ ```ini
2780
+ # .env
2781
+ SECRET="abcdefghijkl"
2782
+ ```
2783
+
2784
+ ```js
2785
+ // index.js
2786
+ const dotenvx = require('@dotenvx/dotenvx')
2787
+ const result = dotenvx.config({ mask: true, quiet: true })
2788
+
2789
+ console.log(process.env.SECRET)
2790
+ console.log(result.parsed.SECRET)
2791
+ ```
2792
+
2793
+ ```sh
2794
+ $ node index.js
2795
+ abcdef******
2796
+ abcdef******
2797
+ ```
2798
+
2799
+ Set `mask: 0` to fully mask values.
2800
+
2728
2801
  </details>
2729
2802
  <details><summary>`config(path: ['.env.local', '.env'])` - multiple files</summary><br>
2730
2803
 
@@ -3051,6 +3124,25 @@ console.log(decryptedValue)
3051
3124
 
3052
3125
  This is known as *Decryption at Access* and is written about in [the whitepaper](https://dotenvx.com/dotenvx.pdf).
3053
3126
 
3127
+ </details>
3128
+ <details><summary>`get(KEY, {mask:})`</summary><br>
3129
+
3130
+ Programmatically return a masked environment variable value.
3131
+
3132
+ ```js
3133
+ // index.js
3134
+ const dotenvx = require('@dotenvx/dotenvx')
3135
+ const maskedValue = await dotenvx.get('SECRET', { mask: true })
3136
+ console.log(maskedValue)
3137
+ ```
3138
+
3139
+ ```sh
3140
+ $ node index.js
3141
+ abcdef******
3142
+ ```
3143
+
3144
+ Set `mask: 0` to fully mask values.
3145
+
3054
3146
  </details>
3055
3147
 
3056
3148
  &nbsp;
package/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "2.6.0",
2
+ "version": "2.7.0",
3
3
  "name": "@dotenvx/dotenvx",
4
4
  "description": "a secure dotenv–from the creator of `dotenv`",
5
5
  "author": "@motdotla",
@@ -6,6 +6,7 @@ const createSpinner = require('../../lib/helpers/createSpinner')
6
6
  const Session = require('../../db/session')
7
7
 
8
8
  const decryptTransform = require('./../../lib/transforms/decrypt')
9
+ const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
9
10
 
10
11
  async function decrypt () {
11
12
  const options = this.opts()
@@ -44,7 +45,16 @@ async function decrypt () {
44
45
  errorCount += 1
45
46
  logger.error(processedEnv.error.messageWithHelp || processedEnv.error.message)
46
47
  } else {
47
- console.log(processedEnv.envSrc)
48
+ let showChar = options.mask
49
+ if (options.mask === true) {
50
+ showChar = 6
51
+ }
52
+
53
+ let envSrc = processedEnv.envSrc
54
+ if (options.mask !== undefined) {
55
+ envSrc = maskEnvSrc(processedEnv.envSrc, showChar)
56
+ }
57
+ console.log(envSrc)
48
58
  }
49
59
  }
50
60
 
@@ -8,6 +8,7 @@ const getResolver = require('./../../lib/resolvers/get')
8
8
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
9
9
  const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
10
10
  const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
11
+ const mask = require('../../lib/helpers/mask')
11
12
 
12
13
  async function get (key) {
13
14
  const options = normalizeDotenvConfigConvention(this.opts())
@@ -55,6 +56,17 @@ async function get (key) {
55
56
  }
56
57
 
57
58
  if (spinner) spinner.stop()
59
+ if (options.mask !== undefined) {
60
+ let showChar = options.mask
61
+ if (options.mask === true) {
62
+ showChar = 6
63
+ }
64
+
65
+ for (const key of Object.keys(parsed)) {
66
+ parsed[key] = mask(parsed[key], showChar)
67
+ }
68
+ }
69
+
58
70
  if (key) {
59
71
  const single = parsed[key]
60
72
  if (single === undefined) {
@@ -10,6 +10,9 @@ const normalizeDotenvConfigQuiet = require('../../lib/helpers/normalizeDotenvCon
10
10
  const normalizeDotenvConfigConvention = require('../../lib/helpers/normalizeDotenvConfigConvention')
11
11
  const buildCommandEnvs = require('../../lib/helpers/buildCommandEnvs')
12
12
  const resolveEnvKeysFile = require('../../lib/helpers/resolveEnvKeysFile')
13
+ const mask = require('../../lib/helpers/mask')
14
+ const maskEnvSrc = require('../../lib/helpers/maskEnvSrc')
15
+ const maskProcessedEnvs = require('../../lib/helpers/maskProcessedEnvs')
13
16
 
14
17
  const { determine } = require('./../../lib/helpers/envResolution')
15
18
 
@@ -48,6 +51,12 @@ function uniqueInjectedKeys (processedEnvs) {
48
51
 
49
52
  async function run () {
50
53
  const options = normalizeDotenvConfigConvention(normalizeDotenvConfigQuiet(this.opts()))
54
+ const maskEnabled = options.mask !== undefined
55
+ let showChar = options.mask
56
+ if (options.mask === true) {
57
+ showChar = 6
58
+ }
59
+ let commandEnv = process.env
51
60
 
52
61
  let commandArgs = this.args
53
62
  if (commandArgs.length < 1) {
@@ -56,7 +65,16 @@ async function run () {
56
65
 
57
66
  const spinner = await createSpinner({ ...options, text: 'injecting' })
58
67
 
59
- logger.debug(`options: ${JSON.stringify(options)}`)
68
+ let debugOptions = options
69
+ if (maskEnabled) {
70
+ let token = options.token
71
+ if (options.token) {
72
+ token = mask(options.token, showChar)
73
+ }
74
+
75
+ debugOptions = { ...options, env: (options.env || []).map(envSrc => maskEnvSrc(envSrc, showChar)), token }
76
+ }
77
+ logger.debug(`options: ${JSON.stringify(debugOptions)}`)
60
78
  logger.debug(`process command [${commandArgs.join(' ')}]`)
61
79
 
62
80
  const ignore = options.ignore || []
@@ -103,13 +121,22 @@ async function run () {
103
121
  }
104
122
  })
105
123
 
124
+ if (maskEnabled) {
125
+ commandEnv = { ...process.env }
126
+ maskProcessedEnvs(processedEnvs, commandEnv, showChar)
127
+ }
128
+
106
129
  for (const processedEnv of processedEnvs) {
107
130
  if (processedEnv.type === 'envFile') {
108
131
  logger.verbose(`loading env from ${processedEnv.filepath} (${path.resolve(processedEnv.filepath)})`)
109
132
  }
110
133
 
111
134
  if (processedEnv.type === 'env') {
112
- logger.verbose(`loading env from string (${processedEnv.string})`)
135
+ let envString = processedEnv.string
136
+ if (maskEnabled) {
137
+ envString = maskEnvSrc(processedEnv.string, showChar)
138
+ }
139
+ logger.verbose(`loading env from string (${envString})`)
113
140
  }
114
141
 
115
142
  for (const error of processedEnv.errors || []) {
@@ -161,7 +188,7 @@ async function run () {
161
188
  process.exit(1)
162
189
  }
163
190
 
164
- await executeCommand(commandArgs, process.env)
191
+ await executeCommand(commandArgs, commandEnv)
165
192
  }
166
193
 
167
194
  module.exports = run
@@ -69,6 +69,7 @@ program.command('run')
69
69
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
70
70
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
71
71
  .option('--token <token>', 'set Armor ⛨ token')
72
+ .option('--mask [characters]', 'inject masked values, optionally setting visible characters')
72
73
  .option('--no-armor', 'disable Dotenvx Armor features')
73
74
  .option('--no-native', 'disable OS secret store features')
74
75
  .action(function (...args) {
@@ -89,6 +90,7 @@ program.command('get')
89
90
  .option('--convention <name>', 'load a .env convention (available conventions: [\'nextjs\', \'flow\'])')
90
91
  .option('--ignore <errorCodes...>', 'error code(s) to ignore (example: --ignore=MISSING_ENV_FILE)')
91
92
  .option('-a, --all', 'include all machine envs as well')
93
+ .option('--mask [characters]', 'mask values, optionally setting visible characters')
92
94
  .option('-pp, --pretty-print', 'pretty print output')
93
95
  .option('--pp', 'pretty print output (alias)')
94
96
  .option('--format <type>', 'format of the output (json, shell, colon, eval)', 'json')
@@ -146,6 +148,7 @@ program.command('decrypt')
146
148
  .option('--no-armor', 'disable Dotenvx Armor features')
147
149
  .option('--no-native', 'disable OS secret store features')
148
150
  .option('--stdout', 'send to stdout')
151
+ .option('--mask [characters]', 'mask stdout values, optionally setting visible characters')
149
152
  .action(function (...args) {
150
153
  this.envs = envs
151
154
  return require('./actions/decrypt').apply(this, args)
@@ -0,0 +1,22 @@
1
+ function mask (str, showChar = 6) {
2
+ if (!str || str.length < 1) {
3
+ return ''
4
+ }
5
+
6
+ const number = Number(showChar)
7
+ showChar = 0
8
+ if (Number.isFinite(number)) {
9
+ showChar = Math.abs(Math.trunc(number))
10
+ }
11
+
12
+ let visibleChars = Math.min(showChar, Math.ceil(str.length / 2))
13
+ if (str.length === 1) {
14
+ visibleChars = 0
15
+ }
16
+ const visiblePart = str.slice(0, visibleChars)
17
+ const maskedPart = '*'.repeat(str.length - visibleChars)
18
+
19
+ return visiblePart + maskedPart
20
+ }
21
+
22
+ module.exports = mask
@@ -0,0 +1,15 @@
1
+ const { scan, upsert } = require('@dotenvx/primitives')
2
+
3
+ const mask = require('./mask')
4
+
5
+ function maskEnvSrc (envSrc, showChar) {
6
+ const { parsed } = scan(envSrc)
7
+
8
+ for (const [key, values] of Object.entries(parsed)) {
9
+ envSrc = upsert(envSrc, key, values.map(value => mask(value, showChar)))
10
+ }
11
+
12
+ return envSrc
13
+ }
14
+
15
+ module.exports = maskEnvSrc
@@ -0,0 +1,29 @@
1
+ const mask = require('./mask')
2
+
3
+ function maskProcessedEnvs (processedEnvs, processEnv, showChar) {
4
+ // Track every key resolved from the requested env sources so the same keys
5
+ // can also be masked in the environment receiving the resolved values.
6
+ const resolvedKeys = new Set()
7
+
8
+ for (const processedEnv of processedEnvs) {
9
+ for (const key of Object.keys(processedEnv.parsed || {})) {
10
+ resolvedKeys.add(key)
11
+ }
12
+
13
+ // These values may be logged, so mask each resolver reporting bucket.
14
+ for (const values of [processedEnv.parsed, processedEnv.injected, processedEnv.existed]) {
15
+ for (const key of Object.keys(values || {})) {
16
+ values[key] = mask(values[key], showChar)
17
+ }
18
+ }
19
+ }
20
+
21
+ // Leave unrelated inherited environment variables unchanged.
22
+ for (const key of resolvedKeys) {
23
+ if (processEnv[key] !== undefined) {
24
+ processEnv[key] = mask(processEnv[key], showChar)
25
+ }
26
+ }
27
+ }
28
+
29
+ module.exports = maskProcessedEnvs
package/src/lib/main.d.ts CHANGED
@@ -114,6 +114,14 @@ export interface DotenvConfigOptions {
114
114
  */
115
115
  processEnv?: DotenvPopulateInput;
116
116
 
117
+ /**
118
+ * Mask resolved values, optionally setting the number of visible characters.
119
+ * @default false
120
+ * @example require('@dotenvx/dotenvx').config({ mask: true })
121
+ * @example require('@dotenvx/dotenvx').config({ mask: 0 })
122
+ */
123
+ mask?: boolean | number;
124
+
117
125
  /**
118
126
  * Customize the path to your .env.keys file. This is useful with monorepos.
119
127
  * @default []
@@ -309,6 +317,14 @@ export function set(
309
317
  ): Promise<SetOutput>;
310
318
 
311
319
  export interface GetOptions {
320
+ /**
321
+ * Mask returned values, optionally setting the number of visible characters.
322
+ * @default false
323
+ * @example require('@dotenvx/dotenvx').get('KEY', { mask: true })
324
+ * @example require('@dotenvx/dotenvx').get('KEY', { mask: 0 })
325
+ */
326
+ mask?: boolean | number;
327
+
312
328
  /**
313
329
  * Suppress specific errors like MISSING_ENV_FILE. The error keys can be found
314
330
  * in src/lib/helpers/errors.js
package/src/lib/main.js CHANGED
@@ -25,6 +25,8 @@ const decryptKeyValue = require('./helpers/cryptography/decryptKeyValue')
25
25
  const Errors = require('./helpers/errors')
26
26
  const normalizeDotenvConfigQuiet = require('./helpers/normalizeDotenvConfigQuiet')
27
27
  const normalizeDotenvConfigConvention = require('./helpers/normalizeDotenvConfigConvention')
28
+ const mask = require('./helpers/mask')
29
+ const maskProcessedEnvs = require('./helpers/maskProcessedEnvs')
28
30
 
29
31
  function uniqueInjectedKeys (processedEnvs) {
30
32
  const result = new Set()
@@ -88,6 +90,11 @@ const config = function (options = {}) {
88
90
  token: options.token
89
91
  })
90
92
 
93
+ if (options.mask !== undefined) {
94
+ const showChar = options.mask === true ? 6 : options.mask
95
+ maskProcessedEnvs(processedEnvs, processEnv, showChar)
96
+ }
97
+
91
98
  let lastError
92
99
  /** @type {Record<string, string>} */
93
100
  const parsedAll = {}
@@ -326,6 +333,13 @@ const get = async function (key, options = {}) {
326
333
  noKeychain
327
334
  })
328
335
 
336
+ if (options.mask !== undefined) {
337
+ const showChar = options.mask === true ? 6 : options.mask
338
+ for (const key of Object.keys(parsed)) {
339
+ parsed[key] = mask(parsed[key], showChar)
340
+ }
341
+ }
342
+
329
343
  for (const error of errors || []) {
330
344
  if (ignore.includes(error.code)) {
331
345
  continue // ignore error