Skip to content

Commit da90380

Browse files
authored
feat: add optional flag to skip secret masking during export (#1396)
* feat: add AUTH0_EXPORT_SECRETS flag to allow exporting real secret values Adds optional AUTH0_EXPORT_SECRETS config flag (and --export_secrets CLI option) that skips secret masking during export. When disabled (default), secrets continue to be replaced with placeholder markers like ##CONNECTIONS_OAUTH2_SECRET##. Addresses issue #1356.
1 parent 14ac40b commit da90380

17 files changed

Lines changed: 248 additions & 13 deletions

docs/configuring-the-deploy-cli.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,12 @@ Boolean. When enabled, will return identifiers of all resources. May be useful f
179179

180180
Boolean. When enabled, exports JSON and YAML resources with keys sorted alphabetically, producing stable and deterministic output. Useful for reducing noise in diffs when keys would otherwise appear in non-deterministic order. Default: `false`.
181181

182+
### `AUTH0_EXPORT_SECRETS`
183+
184+
Boolean. When enabled, exports actual secret values (e.g. connection `client_secret`, log stream tokens, email provider credentials, attack protection CAPTCHA secrets) instead of replacing them with placeholder markers like `##CONNECTIONS_OAUTH2_SECRET##`. Useful for backup and restore scenarios where secrets need to be preserved. Default: `false`.
185+
186+
> **Warning:** Enabling this option will write real credentials to exported files. Use with caution in shared or version-controlled environments.
187+
182188
### `EXCLUDED_PROPS`
183189

184190
Provides ability to exclude any unwanted properties from management.

docs/using-as-cli.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ Boolean. When enabled, will export the identifier fields for each resource. Defa
2929

3030
Boolean. When enabled, exports resource configuration files with keys sorted alphabetically, producing stable and deterministic output. Useful for reducing noise in diffs. Default: `false`.
3131

32+
### `--export_secrets`
33+
34+
Boolean. When enabled, exports actual secret values instead of replacing them with placeholder markers (e.g. `##SMTP_PASS##`). Useful for backup and restore scenarios. **Warning:** real credentials will be written to exported files. Default: `false`.
35+
3236
### `--env`
3337

3438
Boolean. Indicates if the tool should ingest environment variables or not. Default: `true`.
@@ -56,6 +60,9 @@ a0deploy export -c=config.json --format=directory --output_folder=local
5660

5761
# Fetching Auth0 tenant configurations with IDs of all assets
5862
a0deploy export -c=config.json --format=yaml --output_folder=local --export_ids=true
63+
64+
# Fetching Auth0 tenant configurations including real secret values
65+
a0deploy export -c=config.json --format=directory --output_folder=local --export_secrets
5966
```
6067

6168
## `import` command

src/args.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type ExportSpecificParams = {
2626
output_folder: string;
2727
export_ids?: boolean;
2828
export_ordered?: boolean;
29+
export_secrets?: boolean;
2930
};
3031

3132
export type ExportParams = ExportSpecificParams & SharedParams;
@@ -138,6 +139,11 @@ function getParams(): CliParams {
138139
describe: 'Order keys in exported JSON files for consistent diffs.',
139140
type: 'boolean',
140141
},
142+
export_secrets: {
143+
describe: 'Export actual secret values instead of placeholder markers.',
144+
type: 'boolean',
145+
default: false,
146+
},
141147
})
142148
.example(
143149
'$0 export -c config.json -f yaml -o path/to/export',

src/commands/export.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export default async function exportCMD(params: ExportParams) {
1616
config: configObj,
1717
export_ids: exportIds,
1818
export_ordered: exportOrdered,
19+
export_secrets: exportSecrets,
1920
secret: clientSecret,
2021
env: shouldInheritEnv = false,
2122
experimental_ea: experimentalEA,
@@ -51,6 +52,11 @@ export default async function exportCMD(params: ExportParams) {
5152
overrides.AUTH0_EXPORT_ORDERED = exportOrdered;
5253
}
5354

55+
// Allow passed in export_secrets to override the configured one
56+
if (exportSecrets) {
57+
overrides.AUTH0_EXPORT_SECRETS = exportSecrets;
58+
}
59+
5460
// Overrides AUTH0_INCLUDE_EXPERIMENTAL_EA is experimental_ea passed in command line
5561
if (experimentalEA) {
5662
overrides.AUTH0_EXPERIMENTAL_EA = experimentalEA;

src/context/defaults.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,22 @@
11
import { AttackProtection } from '../tools/auth0/handlers/attackProtection';
22
import { maskSecretAtPath } from '../tools/utils';
3+
import { Config } from '../types';
4+
5+
// env vars arrive as strings, so check both forms
6+
function isExportSecrets(config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>): boolean {
7+
return (
8+
config?.AUTH0_EXPORT_SECRETS === true || (config?.AUTH0_EXPORT_SECRETS as unknown) === 'true'
9+
);
10+
}
311

412
// eslint-disable-next-line import/prefer-default-export
5-
export function emailProviderDefaults(emailProvider) {
13+
export function emailProviderDefaults(
14+
emailProvider,
15+
config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>
16+
) {
617
// eslint-disable-line
18+
if (isExportSecrets(config)) return emailProvider;
19+
720
const updated = { ...emailProvider };
821

922
const apiKeyProviders = ['mailgun', 'mandrill', 'sendgrid', 'sparkpost'];
@@ -111,8 +124,8 @@ export function phoneTemplatesDefaults(phoneTemplate) {
111124
return updated;
112125
}
113126

114-
export function connectionDefaults(connection) {
115-
if (connection.options) {
127+
export function connectionDefaults(connection, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>) {
128+
if (!isExportSecrets(config) && connection.options) {
116129
// Mask secret for key: connection.options.client_secret
117130
maskSecretAtPath({
118131
resourceTypeName: 'connections',
@@ -124,7 +137,9 @@ export function connectionDefaults(connection) {
124137
return connection;
125138
}
126139

127-
export function logStreamDefaults(logStreams) {
140+
export function logStreamDefaults(logStreams, config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>) {
141+
if (isExportSecrets(config)) return logStreams;
142+
128143
// masked sensitive fields
129144
const sensitiveKeys = [
130145
'httpAuthorization',
@@ -152,7 +167,12 @@ export function logStreamDefaults(logStreams) {
152167
return maskedLogStreams;
153168
}
154169

155-
export function attackProtectionDefaults(attackProtection: AttackProtection) {
170+
export function attackProtectionDefaults(
171+
attackProtection: AttackProtection,
172+
config?: Pick<Config, 'AUTH0_EXPORT_SECRETS'>
173+
) {
174+
if (isExportSecrets(config)) return attackProtection;
175+
156176
const { captcha } = attackProtection;
157177

158178
if (captcha) {

src/context/directory/handlers/attackProtection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ async function dump(context: DirectoryContext): Promise<void> {
8585
const files = attackProtectionFiles(context.filePath);
8686
fs.ensureDirSync(files.directory);
8787

88-
const maskedAttackProtection = attackProtectionDefaults(attackProtection);
88+
const maskedAttackProtection = attackProtectionDefaults(attackProtection, context.config);
8989

9090
if (maskedAttackProtection.botDetection) {
9191
dumpJSON(files.botDetection, maskedAttackProtection.botDetection);

src/context/directory/handlers/connections.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ async function dump(context: DirectoryContext): Promise<void> {
100100
const connectionName = sanitize(dumpedConnection.name);
101101

102102
// Mask secrets
103-
dumpedConnection = connectionDefaults(dumpedConnection);
103+
dumpedConnection = connectionDefaults(dumpedConnection, context.config);
104104

105105
if (dumpedConnection.strategy === 'email') {
106106
ensureProp(dumpedConnection, 'options.email.body');

src/context/directory/handlers/emailProvider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ async function dump(context: DirectoryContext): Promise<void> {
3636
const excludedDefaults = context.assets.exclude?.defaults || [];
3737
if (!excludedDefaults.includes('emailProvider')) {
3838
// Add placeholder for credentials as they cannot be exported
39-
return emailProviderDefaults(context.assets.emailProvider);
39+
return emailProviderDefaults(context.assets.emailProvider, context.config);
4040
}
4141
return context.assets.emailProvider;
4242
})();

src/context/directory/handlers/logStreams.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ async function dump(context: DirectoryContext): Promise<void> {
4040
fs.ensureDirSync(logStreamsDirectory);
4141

4242
// masked sensitive fields
43-
const maskedLogStreams = logStreamDefaults(logStreams);
43+
const maskedLogStreams = logStreamDefaults(logStreams, context.config);
4444

4545
maskedLogStreams.forEach((logStream) => {
4646
const ruleFile = path.join(logStreamsDirectory, `${sanitize(logStream.name)}.json`);

src/context/yaml/handlers/attackProtection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function dump(context: YAMLContext): Promise<ParsedAttackProtection> {
4343
attackProtectionConfig.captcha = captcha;
4444
}
4545

46-
const maskedAttackProtection = attackProtectionDefaults(attackProtectionConfig);
46+
const maskedAttackProtection = attackProtectionDefaults(attackProtectionConfig, context.config);
4747

4848
return {
4949
attackProtection: maskedAttackProtection,

0 commit comments

Comments
 (0)