@contrast/core 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -142,9 +142,7 @@ class StacktraceFactory {
142
142
  * @returns {}
143
143
  */
144
144
  static makeFrame(callsite) {
145
- let evalOrigin;
146
- let file;
147
- let lineNumber;
145
+ let evalOrigin, file, lineNumber, method, type;
148
146
 
149
147
  if (callsite.isEval()) {
150
148
  evalOrigin = StacktraceFactory.formatFileName(callsite.getEvalOrigin());
@@ -154,13 +152,15 @@ class StacktraceFactory {
154
152
  file = file || callsite.getFileName();
155
153
  lineNumber = lineNumber || callsite.getLineNumber();
156
154
 
157
- return {
158
- eval: evalOrigin,
159
- file,
160
- lineNumber,
161
- method: callsite.getFunctionName(),
162
- type: callsite.getTypeName()
163
- };
155
+ method = callsite.getFunctionName();
156
+ type = callsite.getTypeName();
157
+
158
+ if (method === null && type === 'Object') {
159
+ method = '<anonymous>';
160
+ type = null;
161
+ }
162
+
163
+ return { eval: evalOrigin, file, lineNumber, method, type };
164
164
  }
165
165
 
166
166
  static formatFileName(fileName = '') {
package/lib/index.js CHANGED
@@ -22,10 +22,10 @@ module.exports = function init(core = {}) {
22
22
 
23
23
  require('@contrast/config')(core);
24
24
  require('@contrast/logger').default(core);
25
+ require('./sensitive-data-masking')(core);
25
26
  require('./app-info')(core);
26
27
  require('./is-agent-path')(core);
27
28
  require('./capture-stacktrace')(core);
28
- require('./cli-rewriter')(core);
29
29
  require('@contrast/patcher')(core);
30
30
  require('@contrast/rewriter')(core);
31
31
  require('@contrast/dep-hooks')(core);
@@ -15,25 +15,6 @@
15
15
 
16
16
  'use strict';
17
17
 
18
- module.exports = function(deps) {
19
- const cliRewriter = deps.cliRewriter = async function (preHook) {
20
- // fix this - config doesn't resolve this properly for this cli use case
21
- deps.config.entrypoint = process.argv[2];
22
-
23
- require('./dependency-rewriter')(deps);
24
-
25
- /*
26
- * Callers just need to set up their rewriter policy e.g.
27
- * deps.depHooks.rewriting.install()
28
- * deps.assess.rewriting.install()
29
- */
30
- preHook(deps);
31
-
32
- // once clients configure their stuff
33
- await deps.dependencyRewriter.rewrite();
34
-
35
- deps.logger.info('done');
36
- };
37
-
38
- return cliRewriter;
18
+ module.exports = {
19
+ CONTRAST_REDACTED: 'contrast-redacted',
39
20
  };
@@ -0,0 +1,69 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { Event, simpleTraverse } = require('@contrast/common');
19
+ const { CONTRAST_REDACTED } = require('./constants');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ logger,
24
+ messages
25
+ } = core;
26
+
27
+ const idMap = new Map();
28
+ const keywordSets = [];
29
+
30
+ function getRedactedText(key) {
31
+ key = key.toLowerCase();
32
+ for (const set of keywordSets) {
33
+ if (set.has(key)) {
34
+ return `${CONTRAST_REDACTED}-${idMap.get(set)}`;
35
+ }
36
+ }
37
+ }
38
+
39
+ function traverseAndMask(target) {
40
+ let redactedText;
41
+ if (!target) return;
42
+
43
+ simpleTraverse(target, (path, type, value, obj) => {
44
+ if (type === 'Key') {
45
+ redactedText = getRedactedText(value);
46
+ if (redactedText) {
47
+ obj[value] = redactedText;
48
+ redactedText = undefined;
49
+ }
50
+ }
51
+ });
52
+ }
53
+
54
+ const sensitiveDataMasking = core.sensitiveDataMasking = {
55
+ policy: {
56
+ idMap,
57
+ keywordSets,
58
+ maskHttpBody: false,
59
+ maskAttackVector: false,
60
+ },
61
+ getRedactedText,
62
+ traverseAndMask
63
+ };
64
+
65
+ require('./server-settings-listener')(core);
66
+ require('./protect-listener')(core);
67
+
68
+ return sensitiveDataMasking;
69
+ };
@@ -0,0 +1,99 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { URLSearchParams } = require('url');
19
+ const { Event } = require('@contrast/common');
20
+
21
+ const { CONTRAST_REDACTED } = require('./constants');
22
+
23
+ module.exports = function(core) {
24
+ const {
25
+ messages,
26
+ logger,
27
+ sensitiveDataMasking: {
28
+ policy,
29
+ getRedactedText,
30
+ traverseAndMask
31
+ }
32
+ } = core;
33
+
34
+ messages.on(Event.PROTECT, (msg) => {
35
+ if (!msg.protect || !policy.keywordSets.length) {
36
+ return;
37
+ }
38
+
39
+ logger.trace(`masking sensitive fields in ${Event.PROTECT} message`);
40
+
41
+ if (policy.maskHttpBody) {
42
+ msg.protect.parsedBody = `${CONTRAST_REDACTED}-body`;
43
+ } else {
44
+ traverseAndMask(msg.protect?.parsedBody);
45
+ }
46
+
47
+ traverseAndMask(msg.protect?.parsedCookies);
48
+ traverseAndMask(msg.protect?.parsedQuery);
49
+
50
+ // Do parsed URL path params and urlPath together
51
+ const params = msg.protect?.parsedParams;
52
+ if (params) {
53
+ for (const [key, value] of Object.entries(params)) {
54
+ const redactedText = getRedactedText(key);
55
+ if (redactedText) {
56
+ const encoded = encodeURIComponent(value);
57
+ msg.protect.reqData.uriPath = msg.protect.reqData.uriPath.replace(encoded, redactedText);
58
+ msg.protect.parsedParams[key] = redactedText;
59
+ }
60
+ }
61
+ }
62
+
63
+ // raw headers
64
+ const headers = msg.protect?.reqData.headers;
65
+ for (let i = 0; i <= headers.length - 2; i += 2) {
66
+ const key = headers[i];
67
+
68
+ const redactedText = getRedactedText(key);
69
+ if (redactedText) {
70
+ headers[i + 1] = redactedText;
71
+ }
72
+ }
73
+
74
+ // raw queries
75
+ if (msg.protect?.reqData?.queries) {
76
+ const searchParams = new URLSearchParams(msg.protect.reqData.queries);
77
+ for (const [key, value] of searchParams) {
78
+ const redactedText = getRedactedText(key);
79
+ if (redactedText) {
80
+ searchParams.set(key, redactedText);
81
+ }
82
+ }
83
+ msg.protect.reqData.queries = searchParams.toString();
84
+ }
85
+
86
+ if (policy.maskAttackVector) {
87
+ // attack values
88
+ const inputAnalysis = Object.entries(msg.protect?.findings.resultsMap);
89
+ for (const [, results] of inputAnalysis) {
90
+ for (const result of results) {
91
+ const redactedText = getRedactedText(result.key);
92
+ if (redactedText) {
93
+ result.value = redactedText;
94
+ }
95
+ }
96
+ }
97
+ }
98
+ });
99
+ };
@@ -0,0 +1,44 @@
1
+ /*
2
+ * Copyright: 2022 Contrast Security, Inc
3
+ * Contact: support@contrastsecurity.com
4
+ * License: Commercial
5
+
6
+ * NOTICE: This Software and the patented inventions embodied within may only be
7
+ * used as part of Contrast Security’s commercial offerings. Even though it is
8
+ * made available through public repositories, use of this Software is subject to
9
+ * the applicable End User Licensing Agreement found at
10
+ * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
+ * between Contrast Security and the End User. The Software may not be reverse
12
+ * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
+ * way not consistent with the End User License Agreement.
14
+ */
15
+
16
+ 'use strict';
17
+
18
+ const { Event } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ logger,
23
+ messages,
24
+ sensitiveDataMasking: { policy },
25
+ } = core;
26
+
27
+ messages.on(Event.SERVER_SETTINGS_UPDATE, (settingsMsg) => {
28
+ const dtm = settingsMsg?.sensitive_data_masking_policy;
29
+
30
+ if (!dtm) return;
31
+
32
+ logger.trace('updating sensitive data masking policy');
33
+
34
+ policy.maskHttpBody = dtm.mask_http_body;
35
+ policy.maskAttackVector = dtm.mask_attack_vector;
36
+ policy.keywordSets.length = 0;
37
+ policy.idMap.clear();
38
+ dtm.rules.forEach(({ id, keywords }) => {
39
+ const kwSet = new Set(keywords);
40
+ policy.keywordSets.push(kwSet);
41
+ policy.idMap.set(kwSet, id);
42
+ });
43
+ });
44
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/core",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Preconfigured Contrast agent core services and models",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -17,15 +17,15 @@
17
17
  "test": "../scripts/test.sh"
18
18
  },
19
19
  "dependencies": {
20
- "@contrast/agentify": "1.0.4",
21
- "@contrast/config": "1.1.3",
22
- "@contrast/dep-hooks": "1.0.3",
20
+ "@contrast/agentify": "1.0.5",
21
+ "@contrast/config": "1.1.4",
22
+ "@contrast/dep-hooks": "1.0.4",
23
23
  "@contrast/fn-inspect": "^3.1.0",
24
- "@contrast/logger": "1.0.3",
25
- "@contrast/patcher": "1.0.3",
26
- "@contrast/reporter": "1.1.0",
27
- "@contrast/rewriter": "1.0.3",
28
- "@contrast/scopes": "1.1.0",
24
+ "@contrast/logger": "1.0.4",
25
+ "@contrast/patcher": "1.0.4",
26
+ "@contrast/reporter": "1.2.0",
27
+ "@contrast/rewriter": "1.0.4",
28
+ "@contrast/scopes": "1.1.1",
29
29
  "builtin-modules": "^3.2.0",
30
30
  "semver": "^7.3.7"
31
31
  }
@@ -1,222 +0,0 @@
1
- /*
2
- * Copyright: 2022 Contrast Security, Inc
3
- * Contact: support@contrastsecurity.com
4
- * License: Commercial
5
-
6
- * NOTICE: This Software and the patented inventions embodied within may only be
7
- * used as part of Contrast Security’s commercial offerings. Even though it is
8
- * made available through public repositories, use of this Software is subject to
9
- * the applicable End User Licensing Agreement found at
10
- * https://www.contrastsecurity.com/enduser-terms-0317a or as otherwise agreed
11
- * between Contrast Security and the End User. The Software may not be reverse
12
- * engineered, modified, repackaged, sold, redistributed or otherwise used in a
13
- * way not consistent with the End User License Agreement.
14
- */
15
-
16
- 'use strict';
17
-
18
- const Module = require('module');
19
- const process = require('process');
20
- const fs = require('fs');
21
- const path = require('path');
22
- const util = require('util');
23
- const builtins = require('builtin-modules');
24
- const readFile = util.promisify(fs.readFile);
25
-
26
- module.exports = function(core) {
27
- const dependencyRewriter = new RecursiveDependencyRewriter(core);
28
- core.dependencyRewriter = dependencyRewriter;
29
- return dependencyRewriter;
30
- };
31
-
32
- // Ported from node agent lib/cli-rewriter/index.js
33
- class RecursiveDependencyRewriter {
34
- /**
35
- * Receives entrypoint used to initialize agent.
36
- * Sets up config, logging, agent, and rewriter state.
37
- * @param {string} entrypoint application's entrypoint script
38
- */
39
- constructor(core) {
40
- this.deps = core;
41
- this.logger = core.logger;
42
- this.rewriter = core.rewriter;
43
- this.rewriter.visitors.push(RecursiveDependencyRewriter.requireDetector);
44
- // keep track of files rewritten - don't rewrite if file is required more than once
45
- this.visited = new Set();
46
- this.entrypoint = core.config.entrypoint;
47
- this.filename = path.resolve(process.cwd(), this.entrypoint);
48
- try {
49
- fs.statSync(this.filename);
50
- } catch (err) {
51
- this.logger.error('entrypoint file not found: %s', this.filename);
52
- // eslint-disable-next-line no-process-exit
53
- process.exit(1);
54
- }
55
- }
56
-
57
- /**
58
- * Starting at the provided entrypoint will rewrite it and any dependencies
59
- * detected. Recursively continues until all files are rewritten.
60
- * @param {string} entrypoint provided application entrypoint script
61
- */
62
- async rewrite() {
63
-
64
- const parent = RecursiveDependencyRewriter.getModuleData(this.filename);
65
- const start = Date.now();
66
-
67
- await this.traverse(this.filename, parent);
68
-
69
- this.logger.info('rewriting complete [%ss]', (Date.now() - start) / 1000);
70
- }
71
-
72
- /**
73
- * Visits the filename in order to rewrite and cache. Visitor returns found
74
- * dependencies in the file. Recursively traverses the absolute file paths of
75
- * dependencies found by the require detector.
76
- * @param {string} filename file to rewrite
77
- * @param {object} parent parent module data
78
- */
79
- async traverse(filename, parent) {
80
- const fileDependencies = await this.visitDependency(filename);
81
-
82
- return Promise.all(
83
- fileDependencies.map((request) => {
84
- try {
85
- const _filename = Module._resolveFilename(request, parent);
86
- if (_filename.endsWith('.js')) {
87
- const _parent = RecursiveDependencyRewriter.getModuleData(_filename);
88
- return this.traverse(_filename, _parent);
89
- }
90
- } catch (err) {
91
- if (err.code === 'MODULE_NOT_FOUND') {
92
- this.logger.debug(
93
- `module not found. skipping ${request} required by ${filename}`
94
- );
95
- } else {
96
- this.logger.error('error resolving filename: %o', err);
97
- }
98
- }
99
- })
100
- );
101
- }
102
-
103
- /**
104
- * For each file dependency rewrite and cache it. Returns array of found
105
- * dependencies.
106
- * @param {string} filename name of file to rewrite / cache
107
- * @returns {array[string]} list of the file's dependencies
108
- */
109
- async visitDependency(filename) {
110
- if (this.visited.has(filename)) {
111
- return [];
112
- }
113
-
114
- this.visited.add(filename);
115
-
116
- try {
117
- const content = await readFile(filename, 'utf8');
118
- const rewriteData = this.rewriter.rewriteFile({
119
- content,
120
- filename,
121
- opts: {
122
- inject: true,
123
- sourceType: 'script',
124
- wrap: true,
125
- deps: []
126
- }
127
- });
128
-
129
- if (!rewriteData.deps) {
130
- return [];
131
- }
132
-
133
- return rewriteData.deps.filter((dep) => !builtins.includes(dep));
134
- } catch (e) {
135
- console.log('visit error\n', e);
136
- return;
137
- }
138
- }
139
-
140
- /**
141
- * Gets module data in order to resolve abs paths of deps.
142
- * @param {string} filename absolute path of file being rewritten
143
- * @returns {object}
144
- */
145
- static getModuleData(filename) {
146
- return {
147
- id: filename,
148
- filename,
149
- paths: Module._nodeModulePaths(path.dirname(filename))
150
- };
151
- }
152
-
153
- /**
154
- * Added to agent's static visitors. Runs during rewriting to detect require
155
- * calls to discover dependencies.
156
- * @param {object} node AST node being visited during rewrite
157
- * @param {string} filename file being rewritten
158
- * @param {object} state rewriter state
159
- */
160
- static requireDetector(path, state) {
161
- const { node } = path;
162
- if (isRequire(node)) {
163
- if (isLiteralRequire(node) || isStaticTemplateRequire(node)) {
164
- const request = getRequireArg(node);
165
- if (request) {
166
- state.deps.push(request);
167
- }
168
- }
169
- }
170
- }
171
- }
172
-
173
- /**
174
- * Whether AST node looks like a `require([request])` call.
175
- * @param {object} node AST node
176
- * @returns {boolean}
177
- */
178
- function isRequire(node) {
179
- return (
180
- node.type === 'CallExpression' &&
181
- node.callee &&
182
- node.callee.type === 'Identifier' &&
183
- node.callee.name === 'require' &&
184
- node.arguments &&
185
- node.arguments.length === 1
186
- );
187
- }
188
-
189
- /**
190
- * Whether node is static template require
191
- * @param {object} node AST node
192
- * @returns {boolean}
193
- */
194
- function isStaticTemplateRequire(node) {
195
- return (
196
- node.arguments[0].type === 'TemplateLiteral' &&
197
- node.arguments[0].expressions.length === 0
198
- );
199
- }
200
-
201
- /**
202
- * Whether node is literal require
203
- * @param {object} node AST node
204
- * @returns {boolean}
205
- */
206
- function isLiteralRequire(node) {
207
- return (
208
- node.arguments[0].type === 'Literal' ||
209
- node.arguments[0].type === 'StringLiteral'
210
- );
211
- }
212
-
213
- /**
214
- * Gets value of argument to `require([request])`
215
- * @param {object} node AST node
216
- * @returns {string}
217
- */
218
- function getRequireArg(node) {
219
- return node.arguments[0].type === 'TemplateLiteral'
220
- ? node.arguments[0].quasis[0].value.raw
221
- : node.arguments[0].value;
222
- }