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.
A zero configuration (but highly configurable) library for managing app
configuration data. This module uses a simple yet sophisticated algorithm,
based on functional programming principles, to provide a robust and
flexible mechanism for managing and merging configuration data trees.
Alpha version
This library is still in a very early stage, and the API is likely to change.
Also, currently this requires Node.js version 6. I'll fix the build process so that the distribution version can be used with earlier versions, but that is not yet done.
Install:
npm install config-one
Create a file called "config.js", with, for example:
module.exports = {
'current-site': 'prod',
sites: {
dev: {
port: 8870,
path: '/home/acme/project',
},
prod: {
port: 80,
path: '/var/acme/data',
}
}
};
Access this from your scripts with, for example:
var cfg = require('config-one')();
var siteConfig = cfg.sites[cfg['current-site']];
var port = siteConfig.port; //=> 80
You can apply overrides from other sources at any time. By default, this library looks for and reads config values from files in the current process' working directory, and from environment variables.
You can specify an alternate directory to use as an option to require
, or
by setting the CONFIG1_DIR
environment variable.
The sources it uses by default are:
config.json
, config.js
- default valuesconfig-local.json
, config-local.js
- local overridesCONFIG1_
- take precedence over filesFor example, to run a development version of your app, you could create a file named "config-local.json" (which typically would not be included in version control) with:
{ "current-site": "dev",
"sites": {
"dev": { "port": 8880 }
}
}
The port
variable in the example above would then resolve to 8880
.
You could override any of those local settings with environment variables.
For example, if you ran with CONFIG1_sites.dev.port=9000
, the example above
would yield 9000
.
In your JavaScript configuration files, you can use functions (called "recipes") that specify how to derive some settings from others. For example, you could include the logic to select the highest-version library that matches a semver expression, right in your JavaScript configuration file:
// config.js
var C1 = require('config-one');
var semver = require('semver');
// Compose a function to select the best matching semver from a list
var sort = semvers => semvers.sort(semver.lt);
var match = vexpr => (ver => semver.satisfies(ver, vexpr));
var best = (semvers, vexpr) => sort(semvers).find(match(vexpr));
module.exports = {
cdn: 'https://cdn.org/',
libs: {
available: {
'markdown-it': {
versions: [ '1.0.0', '1.1.0', '2.0.1' ],
},
// ...
},
required: { // specify libs using semver
'markdown-it': '>=2.0.0',
// ...
},
// Recipe to determine the enabled libraries
enabled: C1(X=> {
var enabled = {}; // The recipe will return this object
// References to other config items:
var available = X.libs.available,
required = X.libs.required;
Object.keys(required).forEach(key => {
var req = required[key],
avail = available[key];
if (!avail || !avail.versions) { throw Error('lib not available'); }
// Find a matching version [FIXME: need `freeze` for now]
var versions = C1.freeze(avail.versions);
var winner = best(versions, req);
if (!winner) { throw Error('no matching semver'); }
enabled[key] = winner;
});
return enabled;
})
}
};
Putting these functions in the settings file allows you to override them at any step in the processing chain, and other settings that use those values will be correct. In other words, the semantic logic that ties the settings together does not get clobbered by the overrides.
More detailed documentation is coming soon, but for another example of what you can do, have a look at the options settings for this library itself, at src/defaults.js.
Note how the settings variables are normalized to eliminate redundancy, and logic that derives some settings from others is baked in, using recipes (sorry for the pun)! These recipes work without your having to fret about the order of evaluation, or how or when they are overridden.
var C1 = require('config-one');
// Read configuration data according to the current options
var cfg = C1();
// Make a new, permanent clone with custom options.
var C2 = C1.new(opts);
// Read config data from a set of source specifiers.
var cfg = C1.read(...sourceSpecifiers)
// Create a new view over the supplied config objects and/or views
var cfg = C1.extend(...configs);
// Create a new recipe (used inside configuration objects)
C1.recipe(<function>)
// Shortcut for .recipe() (one argument which is a function)
C1(<function>)
// Make a permanent, static deep copy of a configuration view
var opts = C1.freeze(cfg);
You can turn on some verbose debugging messages in various places by setting
the C1_DEBUG
environment variable to "true". This will go away once we
integrate a real logging framework.
To install all the needed dependencies:
npm install
Run unit tests against the original source files with:
npm test
To compile all the ES2015 (also known as EcmaScript 6) into more portable ES5,
invoke babel
with:
[FIXME: when Babel is integrated with webpack, we should probably do away with this step.]
npm run build #=> outputs are written to build/
To generate the main cross-platform distribution bundle:
webpack #=> dist/config-one.js:
The next sections give reference information and some how-tos
The directory structure of this app looks like the following. This list is annotated with some rationale information.
/
package.json
webpack.config.js
...
src/
dist/ - just for final distribution packages
test/
✓run.sh - just runs the mocha tests without bundling - this is currently
test.sh. It is what is invoked by the `npm test` scripts.
✓index.html - driver for the mocha tests in the browser, the main JS
this invokes are ../build-{src|dest}/test-bundle.js.
webpack.test.config.js
check-spec.js - and any other utility modules
unit/ - sources for the unit tests
main.js - currently this is test/index.js
<name>.test.js
...
smoke/
browser-test.html
browser-test.js
build/ - test bundles
test-bundle-src.js
test-bundle-dist.js
reports/
src/ - separate destination for each unit-under-test
dist/
The unit test scripts use mocha/chai, and are in the test/unit/ subdirectory, with names that end with ".test.js".
Building and running the various combinations of tests and environments is controlled by two environment variables:
C1_UUT
- defaults to 'src'C1_TARGET
- defaults to 'node'[FIXME: I don't thing TARGET
actually makes sense here, since we are
producing a single "UMD" bunde that runs anywhere.]
A third variable can but used to turn on some verbose messages in places:
C1_DEBUG
As describe above, to run the unit tests against the original source files, from the command line, just enter
npm test
The test
script command line is defined in package.json. It invokes the
bash shell script test/run.sh. You can run it
manually, without npm
, if you need to:
test/run.sh
That shell script, in turn, is just a thin wrapper around this command:
mocha -R mochawesome --reporter-options \
"reportDir=$REPORT_DIR,reportName=test-report" $TEST_FILES
In addition to reporting the test status on the console, this also produces a very informative HTML report at test/reports/src/test-report.html.
To run an individual test from the suite, just specify it by filename. For example:
mocha test/unit/low-level.test.js
This mode runs the same suite of tests against the final, bundled library.
First make sure you build the distribution bundle (with webpack
) and then,
to run the tests:
npm run test-dist
This invokes the same script as above, test/test.sh, but with the C1_UUT
environment variable set to "dist".
This doesn't use mocha, and doesn't run extensive unit testing. It is just a basic test to ensure that the distribution bundle can run in whatever browser you use.
Running this without using any runtime webpack components (such as the webpack-dev-server), provides an additional assurance that the bundle can be distributed.
To use it, make sure the distribution bundle has been build, then start a static server, with, for example:
http-server -p 9000
Then open http://localhost:9000/test/smoke/browser-test.html, and verify that you see a green "pass" indication on the page.
Viewing this smoke test page through the dev server allows it to update dynamically with every change to a source file.
webpack-dev-server
Then open http://localhost:8080/test/browser-smoke-test.html. (Note the port number is different!)
When there are changes to source files, the dev server will automatically rebuild the bundle and serve it from memory at the URL path /dist/config-one.js.
As with a normal web application, there are two main ways of running the Mocha test suite through a browser: with a static bundle (in this case, it's a test bundle, that includes the library code bundle within it), or by using the webpack-dev-server.
To create the static bundle:
webpack --config test/webpack.test.config.js
Then, start your static web server, and bring up this page (assuming your server is on port 9000): http://localhost:9000/test/test.html.
The work with these tests using the dev server:
[This is the best option for dynamically viewing test results as you develop. since this dev server instance will rebuild everything whenever there is any change to a library source file or to a test.]
webpack-dev-server --config webpack.test.config.js
Then bring up http://localhost:8080/test/test.html. (If you want to change the dev server port number, you have to do it from the command line.)
This is nearly the same as the previous example. The only difference is that
the test modules require
the library from the distribution bundle, rather
than directly from the sources.
To create the static bundle:
C1_UUT=dist webpack --config webpack.test.config.js
Then bring the file up in http://localhost:9000/test/test.html.
To view it through the webpack-dev-server, do the following (but note that this does not auto-rebuild when there are changes to the original source files, so this setup isn't recommended):
C1_UUT=dist webpack-dev-server --config webpack.test.config.js --hot
And then point your browser at http://localhost:8080/test/test.html.
node-config library
This is a fantastic package, with a very well-thought out API. I particularly like the default behavior, which makes it dead simple to use out of the box. (It would be nice if it were more configurable, though -- after all, it's a configuration manager!)
Mark Stosberg developed the "deferred" feature of that library, and described an algorithm (see this comment, under "Proposed design: lazy resolution using getters") that evolved into the one now used here.
Chainmap (Python recipe)
See this Python recipe, by Raymond Hettinger. This was where I first learned of the value of maintaining the chain of maps indefinitely, and rather than resolving them, to provide a view into them, that looks like a single map to the user.
Are at config-one-project.
Public domain.
FAQs
One config library to rule them all
We found that config-one demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer 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.