Socket
Socket
Sign inDemoInstall

@contrast/protect

Package Overview
Dependencies
Maintainers
17
Versions
74
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@contrast/protect - npm Package Compare versions

Comparing version 1.4.0 to 1.5.0

lib/error-handlers/install/fastify.js

11

lib/error-handlers/index.js

@@ -21,10 +21,13 @@ /*

require('./install/fastify3')(core);
require('./install/fastify')(core);
require('./install/koa2')(core);
require('./install/express4')(core);
require('./install/hapi')(core);
errorHandlers.install = function() {
errorHandlers.fastify3ErrorHandler.install();
errorHandlers.koa2ErrorHandler.install();
errorHandlers.express4ErrorHandler.install();
for (const component of Object.values(errorHandlers)) {
if (component.install) {
component.install();
}
}
};

@@ -31,0 +34,0 @@

@@ -40,3 +40,3 @@ /*

const ruleId = 'untrusted-deserialization';
const { mode } = sourceContext.rules.agentRules[ruleId];
const mode = sourceContext.policy[ruleId];
const { name, value } = sinkContext;

@@ -43,0 +43,0 @@

@@ -19,2 +19,3 @@ /*

const agentLib = require('@contrast/agent-lib');
const { installChildComponentsSync } = require('@contrast/common');

@@ -26,10 +27,3 @@ module.exports = function(core) {

const rules = instantiateRulesFromConfig(
core.config.protect.rules,
core.config.protect.disabled_rules,
protect.agentLib,
);
protect.rules = rules;
require('./policy')(core);
require('./throw-security-exception')(core);

@@ -49,6 +43,3 @@ require('./make-response-blocker')(core);

protect.install = function() {
protect.inputAnalysis.install();
protect.inputTracing.install();
protect.hardening.install();
protect.errorHandlers.install();
installChildComponentsSync(protect)
};

@@ -77,36 +68,1 @@

}
/**
* This function instatiates the rules as defined in the configuration into
* some structure. I'm in no way convinced or asserting that this is the right
* structure but it does get a usable definition of rules in place. The final
* structure will change based on what exactly TS sends as well as what the needs
* of the code accessing the rules, exclusions, virtual-patches, etc.
*
* @param {Object} rules the rules object in the config.protect object.
* @param {string[]} disabled array of disabled rules from config.protect
* @param {Object} agentLib the agent-lib instance
* @returns {Object} { agentLibRules, agentLibRulesMask, agentRules }
*/
function instantiateRulesFromConfig(rules, disabled, agentLib) {
const agentLibRules = {};
let agentLibRulesMask = 0;
const agentRules = {};
for (const ruleId in rules) {
if (disabled.indexOf(ruleId) >= 0 || rules[ruleId].mode === 'off') {
continue;
}
// [matt] this is awkward. we should probably make each nosql-injection-x
// rule separate in the config and only convert them to 'nosql-injection'
// for reporting.
if (agentLib.RuleType[ruleId]) {
agentLibRules[ruleId] = rules[ruleId];
agentLibRulesMask = agentLibRulesMask | agentLib.RuleType[ruleId];
} else {
agentRules[ruleId] = rules[ruleId];
}
}
return { agentLibRules, agentLibRulesMask, agentRules };
}

@@ -116,3 +116,3 @@ /*

const { rules: { agentLibRules, agentLibRulesMask: mask } } = sourceContext;
const { policy: { rulesMask } } = sourceContext;

@@ -123,5 +123,5 @@ inputAnalysis.handleVirtualPatches(sourceContext, { URLS: connectInputs.rawUrl, HEADERS: connectInputs.headers });

let block = undefined;
if (mask !== 0) {
const findings = agentLib.scoreRequestConnect(mask, connectInputs, preferWW);
block = mergeFindings(agentLibRules, sourceContext.findings, findings);
if (rulesMask !== 0) {
const findings = agentLib.scoreRequestConnect(rulesMask, connectInputs, preferWW);
block = mergeFindings(sourceContext, findings);
}

@@ -203,3 +203,3 @@

const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
const { policy: { rulesMask } } = sourceContext;
const resultsList = [];

@@ -213,3 +213,3 @@ const { UrlParameter } = agentLib.InputType;

}
const items = agentLib.scoreAtom(mask, value, UrlParameter, preferWW);
const items = agentLib.scoreAtom(rulesMask, value, UrlParameter, preferWW);
if (!items) {

@@ -243,3 +243,3 @@ return;

const block = mergeFindings(rules.agentLibRules, sourceContext.findings, urlParamsFindings);
const block = mergeFindings(sourceContext, urlParamsFindings);

@@ -270,6 +270,6 @@ if (block) {

const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
const cookieFindings = agentLib.scoreRequestConnect(mask, { cookies: cookiesArr }, preferWW);
const { policy: { rulesMask } } = sourceContext;
const cookieFindings = agentLib.scoreRequestConnect(rulesMask, { cookies: cookiesArr }, preferWW);
const block = mergeFindings(rules.agentLibRules, sourceContext.findings, cookieFindings);
const block = mergeFindings(sourceContext, cookieFindings);

@@ -406,7 +406,8 @@ if (block) {

function commonObjectAnalyzer(sourceContext, object, inputTypes) {
const { rules, rules: { agentLibRulesMask: mask } } = sourceContext;
const { policy: { rulesMask } } = sourceContext;
if (!rulesMask) return;
// use inputTypes to set params...
const { keyType, inputType } = inputTypes;
const inputTypeStr = inputTypes === jsonInputTypes ? 'Json' : 'Parameter';
const { Where } = agentLib.MongoQueryType;
const resultsList = [];

@@ -430,3 +431,3 @@

let itemType;
let mongoQueryType;
let isMongoQueryType;
// this is a bit awkward now because nosql-injection-mongo is not integrated

@@ -439,4 +440,4 @@ // into the scoreAtom() function (or the check_input() function it uses). as

itemType = keyType;
if (mask & agentLib.RuleType['nosql-injection-mongo']) {
mongoQueryType = agentLib.getMongoQueryType(value);
if (rulesMask & agentLib.RuleType['nosql-injection-mongo']) {
isMongoQueryType = agentLib.isMongoQueryType(value);
}

@@ -446,4 +447,4 @@ } else {

}
let items = agentLib.scoreAtom(mask, value, itemType, preferWW);
if (!items && !mongoQueryType) {
let items = agentLib.scoreAtom(rulesMask, value, itemType, preferWW);
if (!items && !isMongoQueryType) {
return;

@@ -457,3 +458,3 @@ }

// that additional information is kept as well.
if (mongoQueryType) {
if (isMongoQueryType) {
const inputToCheck = getValueAtKey(object, path, value);

@@ -463,9 +464,4 @@ // because scoreRequestConnect() returns the query type in the value, we

// to match is stored as `inputToCheck`.
const inputType = typeof inputToCheck;
// query types up to Where, inclusive, accept either string or object values. Where and above accept only string values
if (mongoQueryType <= Where || inputType === 'string') {
// the query-type/input-type combination is valid. add a synthesized item.
const item = { ruleId: 'nosql-injection-mongo', score: 10, mongoContext: { inputToCheck } };
items.push(item);
}
const item = { ruleId: 'nosql-injection-mongo', score: 10, mongoContext: { inputToCheck } };
items.push(item);
}

@@ -479,4 +475,3 @@ // make each item a complete Finding

key: type === 'Key' ? value : path[path.length - 1],
// mimic scoreRequestConnect() returning the query type as the value
value: mongoQueryType || value,
value,
score: item.score,

@@ -504,3 +499,3 @@ idsList: [],

return mergeFindings(rules.agentLibRules, sourceContext.findings, findings);
return mergeFindings(sourceContext, findings);
}

@@ -560,7 +555,9 @@

*/
function mergeFindings(rules, findings, newFindings) {
function mergeFindings(sourceContext, newFindings) {
const { findings, policy } = sourceContext;
if (!newFindings.trackRequest) {
return findings.securityException;
}
normalizeFindings(rules, newFindings);
normalizeFindings(policy, newFindings);

@@ -584,3 +581,3 @@ findings.trackRequest = findings.trackRequest || newFindings.trackRequest;

//
function normalizeFindings(rules, findings) {
function normalizeFindings(policy, findings) {
// now both augment the rules and check to see if any require blocking

@@ -609,3 +606,3 @@ // at perimeter.

// block.
const { mode } = rules[r.ruleId];
const mode = policy[r.ruleId];
if (r.score >= 90 && BLOCKING_MODES.includes(mode)) {

@@ -612,0 +609,0 @@ r.blocked = true;

@@ -18,2 +18,4 @@ /*

const { installChildComponentsSync } = require('@contrast/common');
module.exports = function(core) {

@@ -39,5 +41,6 @@ const inputAnalysis = core.protect.inputAnalysis = {};

// framework specific instrumentation
require('./install/fastify3')(core);
require('./install/fastify')(core);
require('./install/koa2')(core);
require('./install/express4')(core);
require('./install/hapi')(core);

@@ -49,7 +52,3 @@ // virtual patches

inputAnalysis.install = function() {
Object.values(inputAnalysis)
.filter((property) => property.install)
.forEach((library) => {
library.install();
});
installChildComponentsSync(inputAnalysis);
};

@@ -56,0 +55,0 @@

@@ -36,4 +36,14 @@ /*

if (fnName === 'bodyParser.text' && typeof req.body === 'string') {
try {
sourceContext.parsedBody = JSON.parse(req.body);
} catch (err) {
logger.error({ err }, 'Error parsing with bodyParser.text()');
origNext();
return;
}
}
try {
inputAnalysis.handleParsedBody(sourceContext, req.body);
inputAnalysis.handleParsedBody(sourceContext, sourceContext.parsedBody);
} catch (err) {

@@ -40,0 +50,0 @@ if (isSecurityException(err)) {

@@ -37,2 +37,3 @@ /*

this.protect = core.protect;
this.patcher = core.patcher;
this.makeSourceContext = this.protect.makeSourceContext;

@@ -55,2 +56,3 @@ this.maxBodySize = 16 * 1024 * 1024;

this.hookHttps();
this.hookHttp2();
}

@@ -67,3 +69,3 @@

this.logger.debug('hooking library: http');
this.depHooks.resolve({ name: 'http' }, this.hookServer.bind(this));
this.depHooks.resolve({ name: 'http' }, (http) => this.hookServerEmit.call(this, http, 'httpServer'));
}

@@ -76,32 +78,74 @@

this.logger.debug('hooking library: https');
this.depHooks.resolve({ name: 'https' }, this.hookServer.bind(this));
this.depHooks.resolve({ name: 'https' }, (https) => this.hookServerEmit.call(this, https, 'httpsServer'));
}
/**
* Instruments the `Server` prototype from `http(s)`. This patches `emit` and
* Sets hooks to instrument `http2 Servers`.
*/
hookHttp2() {
this.logger.debug('hooking library: http2');
// http2 library does not expose its Server class, so we need to hook the createServer function
this.depHooks.resolve({ name: 'http2' }, (http2) => this.hookCreateServer.call(this, http2, 'http2Server'));
this.depHooks.resolve({ name: 'http2' }, (http2) => this.hookCreateServer.call(this, http2, 'http2SecureServer', 'createSecureServer'));
this.logger.debug('hooking library: spdy');
this.depHooks.resolve({ name: 'spdy' }, (spdy) => this.hookServerEmit.call(this, spdy, 'spdyServer'));
}
/**
* Instruments the `Server` prototype from `http(s)` or spdy's http2 Server. This patches `emit` and
* invokes the protect service to do analysis when appropriate.
*
* @param {Object} xport The http(s) module export
*/
hookServer(xport) {
hookServerEmit(serverSource, sourceName) {
serverSource.Server.prototype = this.patcher.patch(serverSource.Server.prototype, 'emit', {
name: `${sourceName}.Server.prototype.emit`,
patchType: 'initiate-handling',
around: this.emitAroundHook.bind(this)
});
}
/**
* Instruments the `Http2Server` prototype which results from the http2.createServer/createSecureServer() call.
* This also patches `emit` and
* invokes the protect service to do analysis when appropriate.
*/
hookCreateServer(serverSource, sourceName, constructorName = 'createServer') {
const self = this;
const {
Server: {
prototype: { emit }
}
} = xport;
return this.patcher.patch(serverSource, constructorName, {
name: sourceName,
patchType: 'initiate-handling',
post(data) {
xport.Server.prototype.emit = function(...args) {
const [type] = args;
const { result: server } = data;
const serverPrototype = server ? Object.getPrototypeOf(server) : null;
if (type !== 'request') {
return emit.call(this, ...args);
if (!serverPrototype) {
self.logger.error('Unable to patch server prototype, continue without instrumentation');
return;
}
self.patcher.patch(serverPrototype, 'emit', {
name: `${sourceName}.Server.prototype.emit`,
patchType: 'req-async-storage',
around: self.emitAroundHook.bind(self)
});
}
});
}
const context = { instance: this, method: emit, args };
self.initiateRequestHandling(context);
/**
* The around hook for `emit` that
* invokes the protect service to do analysis when appropriate.
*/
emitAroundHook(next, data) {
const [type] = data.args;
return !!this._events[type];
};
if (type !== 'request') {
return next();
}
const context = { instance: data.obj, method: next, args: data.args };
this.initiateRequestHandling(context);
return !!data.obj._events[type];
}

@@ -124,11 +168,2 @@

// URL exclusions should be applied here. there is no point in doing any additional
// work if the url is excluded for a particular rule, i.e., that rule should be removed
// from the list of rules for this request. and if all rules are excluded for this url
// then none of the following needs to be done.
if (this.protect.rules.agentLibRulesMask === 0) {
this.logger.debug('no agent-lib rules are enabled, not checking request');
return;
}
let store;

@@ -144,4 +179,6 @@ let block;

// nothing can be done if async context is not available.
if (!store) {
this.logger.debug('cannot acquire store for initiateRequestHandling()');
setImmediate(() => method.call(instance, ...args));
return;

@@ -178,2 +215,3 @@ }

};
// only add queries if it's known that 'qs' or equivalent won't be used.

@@ -184,8 +222,11 @@ /* c8 ignore next 3 */

}
if (inputAnalysis.virtualPatchesEvaluators?.length) {
store.protect.virtualPatchesEvaluators.push(...inputAnalysis.virtualPatchesEvaluators.map((e) => new Map(e)));
}
if (inputAnalysis.ipDenylist?.length) {
block = inputAnalysis.handleIpDenylist(store.protect, inputAnalysis.ipDenylist);
}
if (inputAnalysis.ipAllowlist?.length) {

@@ -192,0 +233,0 @@ const allowed = inputAnalysis.handleIpAllowlist(store.protect, inputAnalysis.ipAllowlist);

@@ -19,3 +19,8 @@ /*

const util = require('util');
const { BLOCKING_MODES, isString, simpleTraverse } = require('@contrast/common');
const {
ProtectRuleMode: { OFF },
BLOCKING_MODES,
isString,
simpleTraverse
} = require('@contrast/common');

@@ -28,3 +33,3 @@ module.exports = function(core) {

const { mode } = sourceContext.rules.agentLibRules[ruleId];
const mode = sourceContext.policy[ruleId];

@@ -220,3 +225,3 @@ if (BLOCKING_MODES.includes(mode)) {

function getResultsByRuleId(ruleId, context) {
if (context.rules.agentLibRules[ruleId].mode === 'off') {
if (context.policy[ruleId] === OFF) {
return;

@@ -223,0 +228,0 @@ }

@@ -18,17 +18,11 @@ /*

/**
* INPUT TRACING is a STAGE of Protect.
* The specification can be found here https://protect-spec.prod.dotnet.contsec.com/guide/input-tracing.html.
*
* To view other STAGES see https://protect-spec.prod.dotnet.contsec.com/guide/protect-types.html#protection-types
* @param {object} core composed dependencies
* @returns {object}
*/
const { installChildComponentsSync } = require('@contrast/common');
module.exports = function(core) {
const inputTracing = core.protect.inputTracing = {};
// load the interfaces that will be used by input tracing instrumentation
// api
require('./handlers')(core);
// load the instrumentation installers
// instrumentation
require('./install/child-process')(core);

@@ -42,13 +36,9 @@ require('./install/fs')(core);

require('./install/http')(core);
require('./install/vm')(core);
require('./install/eval')(core);
require('./install/function')(core);
// TODO: NODE-2360 (oracledb)
inputTracing.install = function() {
inputTracing.cpInstrumentation.install();
inputTracing.fsInstrumentation.install();
inputTracing.mongodbInstrumentation.install();
inputTracing.mysqlInstrumentation.install();
inputTracing.postgresInstrumentation.install();
inputTracing.sequelizeInstrumentation.install();
inputTracing.sqlite3Instrumentation.install();
inputTracing.httpInstrumentation.install();
// TODO: NODE-2360 (2260?)
installChildComponentsSync(inputTracing);
};

@@ -55,0 +45,0 @@

@@ -32,3 +32,3 @@ /*

function install() {
if (!global.ContrastMethods.__contrastEval) {
if (!global.ContrastMethods.eval) {
logger.error('Cannot install `eval` instrumentation - Contrast method DNE');

@@ -38,4 +38,4 @@ return;

patcher.patch(global.ContrastMethods, '__contrastEval', {
name: 'global.ContrastMethods.__contrastEval',
patcher.patch(global.ContrastMethods, 'eval', {
name: 'global.ContrastMethods.eval',
patchType,

@@ -42,0 +42,0 @@ pre: ({ args, hooked, orig }) => {

@@ -19,2 +19,3 @@ /*

module.exports = function(core) {
const { protect } = core;

@@ -83,5 +84,4 @@ function makeSourceContext(req, res) {

// this should be changed to capture only the rules applicable to this
// particular request (if any route exclusions, etc.)
rules: core.protect.rules,
policy: protect.getPolicy(),
exclusions: [],

@@ -103,46 +103,2 @@ virtualPatchesEvaluators: [],

},
/*
findings: {
trackRequest: true,
resultsList: [
// Example 2
{
// return value from agent-lib
value: 'kill -9 1',
type: 'PARAMETER_VALUE',
ruleId: 'cmd-injection',
path: ['path', 'to', 'val'],
// other data added during lifecycle
// could we add these by mutating agent-lib return values?
// What if there are multiple injections for the same value? The `details` value
// could be an array in that case, or is this too complicated.
blocked: false,
details: [
{
context: {
id: 'child_process.exec',
get stack() {}, // lazy
command: 'sudo kill -9 1',
index: 5,
}
},
{
sinkId: 'child_process.exec',
get stack() {}, // lazy
command: 'sudo kill -9 1',
index: 5,
}]
},
]
}
// (scoreAtom() returns only the ruleId and score because the caller supplied
// the input and type; no key or path is known to scoreAtom(). code calling
// scoreAtom() will need to augment the finding to match the above.)
//
// each finding is augmented with additional properties
// - blocked: false // set to true if the finding causes the request to be blocked
// - mappedId: ruleId // normalized ruleId, e.g., nosql-injection-mongo => nosql-injection
// -
// */
};

@@ -153,3 +109,3 @@

core.protect.makeSourceContext = makeSourceContext;
return core.protect.makeSourceContext = makeSourceContext;
};

@@ -20,2 +20,3 @@ /*

BLOCKING_MODES,
ProtectRuleMode: { OFF },
InputType,

@@ -54,5 +55,5 @@ isString,

const ruleId = 'cmd-injection-semantic-dangerous-paths';
const { mode } = sourceContext.rules.agentRules[ruleId];
const mode = sourceContext.policy[ruleId];
if (mode == 'off') return;
if (mode == OFF) return;

@@ -68,5 +69,5 @@ const result = agentLib.containsDangerousPath(sinkContext.value);

const ruleId = 'cmd-injection-semantic-chained-commands';
const { mode } = sourceContext.rules.agentRules[ruleId];
const mode = sourceContext.policy[ruleId];
if (mode == 'off') return;
if (mode == OFF) return;

@@ -82,5 +83,5 @@ const indexOfChaining = agentLib.indexOfChaining(sinkContext.value);

const ruleId = 'cmd-injection-command-backdoors';
const { mode } = sourceContext.rules.agentRules[ruleId];
const mode = sourceContext.policy[ruleId];
if (mode == 'off') return;
if (mode == OFF) return;

@@ -87,0 +88,0 @@ const finding = findBackdoorInjection(sourceContext, sinkContext.value);

{
"name": "@contrast/protect",
"version": "1.4.0",
"version": "1.5.0",
"description": "Contrast service providing framework-agnostic Protect support",

@@ -22,6 +22,6 @@ "license": "SEE LICENSE IN LICENSE",

"@babel/types": "^7.16.8",
"@contrast/agent-lib": "^5.0.0",
"@contrast/common": "1.1.0",
"@contrast/core": "1.3.0",
"@contrast/esm-hooks": "1.1.4",
"@contrast/agent-lib": "^5.1.0",
"@contrast/common": "1.1.1",
"@contrast/core": "1.4.0",
"@contrast/esm-hooks": "1.1.5",
"@contrast/scopes": "1.1.1",

@@ -28,0 +28,0 @@ "builtin-modules": "^3.2.0",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc