Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Featureful configuration management library for Node.js (nested structure, schema validation, etc.)
Convict is a configuration management tool for Node.js applications. It allows you to define a schema for your configuration files, validate them, and load them from various sources such as JSON files, environment variables, or command-line arguments.
Define a Configuration Schema
This feature allows you to define a schema for your configuration. The schema can include various types of data such as strings, numbers, and arrays. You can also specify default values and environment variables.
const convict = require('convict');
const config = convict({
env: {
doc: 'The application environment.',
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
port: {
doc: 'The port to bind.',
format: 'port',
default: 8080,
env: 'PORT'
}
});
console.log(config.get('env'));
Load Configuration from Multiple Sources
Convict allows you to load configuration from multiple sources such as JSON files, environment variables, and command-line arguments. This makes it easy to manage configuration in different environments.
const convict = require('convict');
const config = convict({
env: {
doc: 'The application environment.',
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
port: {
doc: 'The port to bind.',
format: 'port',
default: 8080,
env: 'PORT'
}
});
config.loadFile('./config.json');
config.load({ port: 3000 });
console.log(config.get('port'));
Validate Configuration
Convict can validate your configuration against the schema you defined. This ensures that your configuration is correct and helps prevent runtime errors.
const convict = require('convict');
const config = convict({
env: {
doc: 'The application environment.',
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
port: {
doc: 'The port to bind.',
format: 'port',
default: 8080,
env: 'PORT'
}
});
config.validate({ allowed: 'strict' });
console.log('Configuration is valid');
The 'config' package is another popular configuration management tool for Node.js. It allows you to define configuration files for different environments and load them based on the current environment. Unlike Convict, it does not provide schema validation out of the box.
The 'dotenv' package is a simple module that loads environment variables from a .env file into process.env. It is more lightweight compared to Convict and does not provide schema validation or support for multiple configuration sources.
The 'nconf' package is a hierarchical configuration manager for Node.js. It allows you to load configuration from multiple sources and provides a flexible way to manage configuration. However, it does not provide schema validation like Convict.
Convict expands on the standard pattern of configuring node.js applications in a way that is more robust and accessible to collaborators, who may have less interest in digging through code in order to inspect or modify settings. By introducing a configuration schema, convict gives project collaborators more context on each setting and enables validation and early failures for when configuration goes wrong.
npm install convict
An example config.js
file:
var convict = require('convict');
convict.addFormat(require('convict-format-with-validator').ipaddress);
// Define a schema
var config = convict({
env: {
doc: 'The application environment.',
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
ip: {
doc: 'The IP address to bind.',
format: 'ipaddress',
default: '127.0.0.1',
env: 'IP_ADDRESS',
},
port: {
doc: 'The port to bind.',
format: 'port',
default: 8080,
env: 'PORT',
arg: 'port'
},
db: {
host: {
doc: 'Database host name/IP',
format: '*',
default: 'server1.dev.test'
},
name: {
doc: 'Database name',
format: String,
default: 'users'
}
},
admins: {
doc: 'Users with write access, or null to grant full access without login.',
format: Array,
nullable: true,
default: null
}
});
// Load environment dependent configuration
var env = config.get('env');
config.loadFile('./config/' + env + '.json');
// Perform validation
config.validate({allowed: 'strict'});
module.exports = config;
An example server.js
file leveraging the config.js
file above:
var http = require('http');
var config = require('./config.js');
var server = http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
});
// Consume
server.listen(config.get('port'), config.get('ip'), function(x) {
var addy = server.address();
console.log('running on http://' + addy.address + ':' + addy.port);
});
To launch your example server, and set a port:
node ./server.js --port 8080
Note: Command-line arguments must be supplied with a double-hyphen prefix (e.g. --arg
). Single-hypen prefixes (e.g. -a
) are not supported at this time.
A configuration module, with its deep nested schema, could look like this:
config.js:
var config = convict({
db: {
name: {
format: String,
default: ''
},
synchro: {
active: {
format: 'Boolean',
default: false
},
remote_url: {
format: 'url',
default: 'http://localhost:8080/'
}
}
},
secret: {
doc: 'Secret used for session cookies and CSRF tokens',
format: '*',
default: '',
sensitive: true
}
});
config.loadFile(['./prod.json', './config.json']);
Each setting in the schema has the following possible properties, each aiding in convict's goal of being more robust and collaborator friendly.
format
property specifies either a built-in convict format (ipaddress
, port
, int
, etc.), or it can be a function to check a custom format. During validation, if a format check fails it will be added to the error report.env
has a value, it will overwrite the setting's default value. An environment variable may not be mapped to more than one setting.arg
is supplied, it will overwrite the setting's default value or the value derived from env
.doc
property is pretty self-explanatory. The nice part about having it in the schema rather than as a comment is that we can call config.getSchemaString()
and have it displayed in the output.sensitive
is set to true
, this value will be masked to "[Sensitive]"
when config.toString()
is called. This helps avoid disclosing secret keys when printing configuration at application start for debugging purposes.nullable
is set to true
, the value counts as valid not only if it matches the specified format
, but also when it is null
.In order to help detect misconfigurations, convict allows you to define a format for each setting. By default, convict checks if the value of the property has the same type (according to Object.prototype.toString.call
) as the default value specified in the schema. You can define a custom format checking function in the schema by setting the format
property.
convict provides several predefined formats for validation that you can use. Most of them are self-explanatory:
*
- any value is validint
port
windows_named_pipe
port_or_windows_named_pipe
nat
- positive integer (natural number)You can find other format here.
If format
is set to one of the built-in JavaScript constructors, Object
, Array
, String
, Number
, RegExp
, or Boolean
, validation will use Object.prototype.toString.call to check that the setting is the proper type.
If nullable
is set to true, null
will be a valid value as well.
You can specify a custom format checking method on a property basis.
For example:
var config = convict({
key: {
doc: 'API key',
format: function check (val) {
if (!/^[a-fA-F0-9]{64}$/.test(val)) {
throw new Error('must be a 64 character hex key')
}
},
default: '3cec609c9bc601c047af917a544645c50caf8cd606806b4e0a23312441014deb'
},
name: {
doc: 'user name',
format: function check (val) {
if (typeof val.first_name !== 'string') {
throw new TypeError(`first name '${val.first_name}' is not a string`);
}
if (typeof val.last_name !== 'string') {
throw new TypeError(`last name '${val.last_name}' is not a string`);
}
},
default: {
first_name: 'John',
last_name: 'Doe'
}
}
});
Or, you can use convict.addFormat()
to register a custom format checking
method that can be reused for many different properties:
convict.addFormat({
name: 'float-percent',
validate: function(val) {
if (val !== 0 && (!val || val > 1 || val < 0)) {
throw new Error('must be a float between 0 and 1, inclusive');
}
},
coerce: function(val) {
return parseFloat(val, 10);
}
});
var config = convict({
space_used: {
format: 'float-percent',
default: 0.5
},
success_rate: {
format: 'float-percent',
default: 60.0
}
});
The coerce
function is optional.
You can specify a custom format checking for array items:
convict.addFormat({
name: 'source-array',
validate: function(sources, schema) {
if (!Array.isArray(sources)) {
throw new Error('must be of type Array');
}
for (source of sources) {
convict(schema.children).load(source).validate();
}
}
});
convict.addFormat(require('convict-format-with-validator').url);
const schema = {
sources: {
doc: 'A collection of data sources.',
format: 'source-array',
default: [],
children: {
type: {
doc: 'The source type',
format: ['git', 'hg', 'svn'],
default: null
},
url: {
doc: 'The source URL',
format: 'url',
default: null
}
}
}
};
convict(schema).load({
'sources': [
{
'type': 'git',
'url': 'https://github.com/mozilla/node-convict.git'
},
{
'type': 'git',
'url': 'https://github.com/github/hub.git'
}
]
}).validate();
Convict will automatically coerce environmental variables from strings to their proper types when importing them.
For instance, values with the format int
, nat
, port
, or Number
will become numbers after a straight
forward parseInt
or parseFloat
. duration
and timestamp
are also parse and converted into numbers,
though they utilize moment.js for date parsing.
When merging configuration values from different sources, Convict follows precedence rules.
The order, from lowest to highest, for config.loadFile(file)
and config.load(json)
is:
env
property is set in schema)arg
property is set in schema)This order means that if schema defines parameter to be taken from an environment variable
and environment variable is set then you cannot override it with config.loadFile(file)
or config.load(json)
.
process.env.PORT = 8080; // environment variable is set
const config = convict({
port: {
default: 3000,
env: 'PORT'
}
});
config.load({ port: 9000 });
console.log(config.get('port')); // still 8080 from env
Convict allows to override Environment variables and Command line arguments. It can be helpful for testing purposes.
When creating a config object pass an object with two optional properties as the 2nd parameter:
env: Object
- this object will be used instead of process.env
args: Array<string>
- this array will be used instead of process.argv
var config = convict({
// configuration schema
}, {
env: {
// Environment variables
},
args: [
// Command line arguments
]
});
Convict is able to parse files with custom file types during loadFile
.
For this specify the corresponding parsers with the associated file extensions.
convict.addParser({ extension: 'toml', parse: toml.parse });
convict.addParser({ extension: ['yml', 'yaml'], parse: yaml.load });
convict.addParser([
{ extension: 'json', parse: JSON.parse },
{ extension: 'json5', parse: json5.parse },
{ extension: ['yml', 'yaml'], parse: yaml.load },
{ extension: 'toml', parse: toml.parse }
]);
const config = convict({ ... });
config.loadFile('config.toml');
If no supported extension is detected, loadFile
will fallback to using the
default json parser.
If you want to allow comments in your JSON file, use JSON5.
convict.addParser({extension: 'json', parse: require('json5').parse});
convict()
takes a schema object or a path to a schema JSON file and returns a
convict configuration object.
opts: Optional object:
process.env
if specified using an object {'NODE_ENV': 'production'}
.process.argv
if specified using an array ['--argname', 'value']
or
a string --argname value
.The configuration object has an API for getting and setting values, described below.
var config = convict({
env: {
doc: 'The application environment.',
format: ['production', 'development', 'test'],
default: 'development',
env: 'NODE_ENV'
},
log_file_path: {
'doc': 'Log file path',
'format': String,
'default': '/tmp/app.log'
}
});
// or
config = convict('/some/path/to/a/config-schema.json');
Adds new parsers for custom file extensions
Adds a new custom format, format
being an object, see example below.
convict.addFormat({
name: 'float-percent',
validate: function(val) {
if (val !== 0 && (!val || val > 1 || val < 0)) {
throw new Error('must be a float between 0 and 1, inclusive');
}
},
coerce: function(val) {
return parseFloat(val, 10);
}
});
Adds new custom formats, formats
being an object whose keys are the new custom
format names, see example below.
convict.addFormats({
prime: {
validate: function(val) {
function isPrime(n) {
if (n <= 1) return false; // zero and one are not prime
for (let i=2; i*i <= n; i++) {
if (n % i === 0) return false;
}
return true;
}
if (!isPrime(val)) throw new Error('must be a prime number');
},
coerce: function(val) {
return parseInt(val, 10);
}
},
'hex-string': {
validate: function(val) {
if (/^[0-9a-fA-F]+$/.test(val)) {
throw new Error('must be a hexadecimal string');
}
}
}
});
Returns the current value of the name
property. name
can use dot notation to reference nested values. E.g.:
config.get('db.host');
// or
config.get('db').host;
Returns the default value of the name
property. name
can use dot notation to reference nested values. E.g.:
config.default('server.port');
Resets a property to its default value as defined in the schema. E.g.:
config.reset('server.port');
Returns true
if the property name
is defined, or false
otherwise. E.g.:
if (config.has('some.property')) {
// Do something
}
Sets the value of name
to value. name
can use dot notation to reference
nested values, e.g. "db.port"
. If objects in the chain don't yet exist,
they will be initialized to empty objects.
E.g.:
config.set('property.that.may.not.exist.yet', 'some value');
config.get('property.that.may.not.exist.yet');
// Returns "some value"
If you call config.load
or config.loadFile
after config.set
then value provided by config.set
will be overridden in case of conflict.
Loads and merges a JavaScript object into config
. E.g.:
config.load({
'env': 'test',
'ip': '127.0.0.1',
'port': 80
});
Loads and merges one or multiple JSON configuration files into config
.
E.g.:
config.loadFile('./config/' + conf.get('env') + '.json');
Or, loading multiple files at once:
// CONFIG_FILES=/path/to/production.json,/path/to/secrets.json,/path/to/sitespecific.json
config.loadFile(process.env.CONFIG_FILES.split(','));
Validates config
against the schema used to initialize it. All errors are
collected and thrown or displayed at once.
warn
: If set to warn
(that is {allowed: 'warn'}
is passed), any
properties specified in config files that are not declared in the schema will
print a warning. This is the default behavior.
strict
: If set to strict
(that is {allowed: 'strict'}
is passed), any
properties specified in config files that are not declared in the schema will
throw errors. This is to ensure that the schema and the config files are in
sync.
output
: You can replace the default output console.log
by your own output function. You can use debug module like this:
output: require('debug')('convict:validate:error')
Exports all the properties (that is the keys and their current values) as JSON.
Exports all the properties (that is the keys and their current values) as a JSON string, with sensitive values masked. Sensitive values are masked even if they aren't set, to avoid revealing any information.
Exports the schema as JSON.
Exports the schema as a JSON string.
The array of process arguments (not including the launcher and application file arguments). Defaults to process.argv unless an override is specified using the args key of the second (options) argument of the convict function.
The map of environment variables. Defaults to process.env unless an override is specified using the env key of the second argument (options) argument of the convict function.
The philosophy was to have production values be the default values. Usually you only want to change defaults for deploy or instance (in aws speak) specific tweaks. However, you can set a default value to null
and if your format doesn't accept null
it will throw an error.
Thanks to browserify, convict
can be used for web applications too. To do so,
brfs
to ensure the fs.loadFileSync
schema-loading calls are inlined at build time rather than resolved at runtime (in Gulp, add .transform(brfs)
to your browserify pipe).http://foo.bar/some.json
URL", build a thin wrapper around convict using your favorite http package (e.g. superagent
). Typically, in the success callback, call convict's load()
on the body of the response.FAQs
Featureful configuration management library for Node.js (nested structure, schema validation, etc.)
The npm package convict receives a total of 356,651 weekly downloads. As such, convict popularity was classified as popular.
We found that convict demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 5 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.