@contrast/assess 1.1.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.
Files changed (111) hide show
  1. package/coverage/lcov-report/base.css +224 -0
  2. package/coverage/lcov-report/block-navigation.js +87 -0
  3. package/coverage/lcov-report/favicon.png +0 -0
  4. package/coverage/lcov-report/index.html +266 -0
  5. package/coverage/lcov-report/lib/dataflow/common.js.html +154 -0
  6. package/coverage/lcov-report/lib/dataflow/event-factory.js.html +598 -0
  7. package/coverage/lcov-report/lib/dataflow/index.html +191 -0
  8. package/coverage/lcov-report/lib/dataflow/index.js.html +190 -0
  9. package/coverage/lcov-report/lib/dataflow/propagation/common.js.html +145 -0
  10. package/coverage/lcov-report/lib/dataflow/propagation/index.html +131 -0
  11. package/coverage/lcov-report/lib/dataflow/propagation/index.js.html +190 -0
  12. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.html +131 -0
  13. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/index.js.html +184 -0
  14. package/coverage/lcov-report/lib/dataflow/propagation/install/contrast-methods/plus.js.html +397 -0
  15. package/coverage/lcov-report/lib/dataflow/propagation/install/string/concat.js.html +478 -0
  16. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.html +176 -0
  17. package/coverage/lcov-report/lib/dataflow/propagation/install/string/index.js.html +202 -0
  18. package/coverage/lcov-report/lib/dataflow/propagation/install/string/replace.js.html +496 -0
  19. package/coverage/lcov-report/lib/dataflow/propagation/install/string/substring.js.html +415 -0
  20. package/coverage/lcov-report/lib/dataflow/propagation/install/string/trim.js.html +403 -0
  21. package/coverage/lcov-report/lib/dataflow/signatures.js.html +5923 -0
  22. package/coverage/lcov-report/lib/dataflow/sinks/common.js.html +145 -0
  23. package/coverage/lcov-report/lib/dataflow/sinks/index.html +131 -0
  24. package/coverage/lcov-report/lib/dataflow/sinks/index.js.html +211 -0
  25. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.html +146 -0
  26. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/index.js.html +172 -0
  27. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js.html +319 -0
  28. package/coverage/lcov-report/lib/dataflow/sinks/install/fastify/xss.js.html +721 -0
  29. package/coverage/lcov-report/lib/dataflow/sinks/install/http.js.html +340 -0
  30. package/coverage/lcov-report/lib/dataflow/sinks/install/index.html +116 -0
  31. package/coverage/lcov-report/lib/dataflow/sources/common.js.html +145 -0
  32. package/coverage/lcov-report/lib/dataflow/sources/index.html +131 -0
  33. package/coverage/lcov-report/lib/dataflow/sources/index.js.html +226 -0
  34. package/coverage/lcov-report/lib/dataflow/sources/install/fastify.js.html +379 -0
  35. package/coverage/lcov-report/lib/dataflow/sources/install/http.js.html +502 -0
  36. package/coverage/lcov-report/lib/dataflow/sources/install/index.html +146 -0
  37. package/coverage/lcov-report/lib/dataflow/sources/install/qs.js.html +322 -0
  38. package/coverage/lcov-report/lib/dataflow/tag-utils.js.html +418 -0
  39. package/coverage/lcov-report/lib/dataflow/tracker.js.html +466 -0
  40. package/coverage/lcov-report/lib/dataflow/utils/index.html +116 -0
  41. package/coverage/lcov-report/lib/dataflow/utils/is-vulnerable.js.html +424 -0
  42. package/coverage/lcov-report/lib/index.html +116 -0
  43. package/coverage/lcov-report/lib/index.js.html +193 -0
  44. package/coverage/lcov-report/prettify.css +1 -0
  45. package/coverage/lcov-report/prettify.js +2 -0
  46. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  47. package/coverage/lcov-report/sorter.js +196 -0
  48. package/coverage/lcov.info +4856 -0
  49. package/coverage/tmp/coverage-9548-1675168551025-0.json +1 -0
  50. package/coverage/tmp/coverage-9551-1675168550963-0.json +1 -0
  51. package/coverage/tmp/coverage-9552-1675168550969-0.json +1 -0
  52. package/coverage/tmp/coverage-9553-1675168550970-0.json +1 -0
  53. package/coverage/tmp/coverage-9554-1675168550962-0.json +1 -0
  54. package/coverage/tmp/coverage-9555-1675168550965-0.json +1 -0
  55. package/coverage/tmp/coverage-9556-1675168550964-0.json +1 -0
  56. package/coverage/tmp/coverage-9557-1675168550969-0.json +1 -0
  57. package/coverage/tmp/coverage-9558-1675168550964-0.json +1 -0
  58. package/coverage/tmp/coverage-9559-1675168550971-0.json +1 -0
  59. package/lib/dataflow/event-factory.js +256 -0
  60. package/lib/dataflow/index.js +35 -0
  61. package/lib/dataflow/propagation/common.js +26 -0
  62. package/lib/dataflow/propagation/index.js +50 -0
  63. package/lib/dataflow/propagation/install/array-prototype-join.js +125 -0
  64. package/lib/dataflow/propagation/install/contrast-methods/add.js +104 -0
  65. package/lib/dataflow/propagation/install/contrast-methods/index.js +34 -0
  66. package/lib/dataflow/propagation/install/contrast-methods/tag.js +102 -0
  67. package/lib/dataflow/propagation/install/decode-uri-component.js +88 -0
  68. package/lib/dataflow/propagation/install/ejs/escape-xml.js +89 -0
  69. package/lib/dataflow/propagation/install/ejs/index.js +30 -0
  70. package/lib/dataflow/propagation/install/encode-uri-component.js +87 -0
  71. package/lib/dataflow/propagation/install/escape-html.js +89 -0
  72. package/lib/dataflow/propagation/install/escape.js +89 -0
  73. package/lib/dataflow/propagation/install/handlebars-utils-escape-expression.js +89 -0
  74. package/lib/dataflow/propagation/install/mysql-connection-escape.js +111 -0
  75. package/lib/dataflow/propagation/install/pug/index.js +64 -0
  76. package/lib/dataflow/propagation/install/pug-runtime-escape.js +89 -0
  77. package/lib/dataflow/propagation/install/sql-template-strings.js +103 -0
  78. package/lib/dataflow/propagation/install/string/concat.js +108 -0
  79. package/lib/dataflow/propagation/install/string/format-methods.js +83 -0
  80. package/lib/dataflow/propagation/install/string/html-methods.js +163 -0
  81. package/lib/dataflow/propagation/install/string/index.js +38 -0
  82. package/lib/dataflow/propagation/install/string/replace.js +197 -0
  83. package/lib/dataflow/propagation/install/string/substring.js +117 -0
  84. package/lib/dataflow/propagation/install/string/trim.js +115 -0
  85. package/lib/dataflow/propagation/install/unescape.js +90 -0
  86. package/lib/dataflow/propagation/install/url/domain-parsers.js +89 -0
  87. package/lib/dataflow/propagation/install/url/index.js +33 -0
  88. package/lib/dataflow/propagation/install/validator/hooks.js +172 -0
  89. package/lib/dataflow/propagation/install/validator/index.js +28 -0
  90. package/lib/dataflow/propagation/install/validator/methods.js +82 -0
  91. package/lib/dataflow/signatures.js +2022 -0
  92. package/lib/dataflow/sinks/common.js +20 -0
  93. package/lib/dataflow/sinks/index.js +44 -0
  94. package/lib/dataflow/sinks/install/fastify/index.js +30 -0
  95. package/lib/dataflow/sinks/install/fastify/unvalidated-redirect.js +107 -0
  96. package/lib/dataflow/sinks/install/http.js +119 -0
  97. package/lib/dataflow/sinks/install/postgres/index.js +129 -0
  98. package/lib/dataflow/sources/common.js +20 -0
  99. package/lib/dataflow/sources/handler.js +114 -0
  100. package/lib/dataflow/sources/index.js +35 -0
  101. package/lib/dataflow/sources/install/fastify/cookie.js +61 -0
  102. package/lib/dataflow/sources/install/fastify/fastify.js +89 -0
  103. package/lib/dataflow/sources/install/fastify/index.js +31 -0
  104. package/lib/dataflow/sources/install/http.js +181 -0
  105. package/lib/dataflow/sources/install/qs.js +88 -0
  106. package/lib/dataflow/tag-utils.js +122 -0
  107. package/lib/dataflow/tracker.js +127 -0
  108. package/lib/dataflow/utils/is-safe-content-type.js +30 -0
  109. package/lib/dataflow/utils/is-vulnerable.js +113 -0
  110. package/lib/index.js +36 -0
  111. package/package.json +21 -0
@@ -0,0 +1,89 @@
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 { InputType } = require('@contrast/common');
19
+ const { patchType } = require('../../common');
20
+
21
+ module.exports = function(core) {
22
+ const {
23
+ logger,
24
+ depHooks,
25
+ patcher,
26
+ assess: { dataflow: { sources } },
27
+ } = core;
28
+
29
+ const source = sources.fastifyInstrumentation.fastify = {
30
+ install() {
31
+ depHooks.resolve({ name: 'fastify', version: '>=3.0.0' }, (fastify) => patcher.patch(fastify, {
32
+ name: 'fastify.constructor',
33
+ patchType,
34
+ post({ orig, result: server }) {
35
+ server.addHook('onRequest', function handler(request, reply, done) {
36
+ const name = 'fastify.onRequest';
37
+ const inputType = InputType.HEADER;
38
+
39
+ try {
40
+ sources.handle({
41
+ data: request.raw.headers,
42
+ inputType,
43
+ name,
44
+ stacktraceOpts: {
45
+ constructorOpt: handler
46
+ }
47
+ });
48
+
49
+ } catch (err) {
50
+ logger.error({ err, inputType, name }, 'unable to handle fastify source');
51
+ }
52
+
53
+ done();
54
+ });
55
+
56
+ server.addHook('preValidation', function preValidationHandler(request, reply, done) {
57
+ const name = 'fastify.preValidation';
58
+ const bodyType = request?.headers?.['content-type']?.includes('/json')
59
+ ? InputType.JSON_VALUE
60
+ : InputType.PARAMETER_VALUE;
61
+
62
+ [
63
+ { key: 'query', inputType: InputType.PARAMETER_VALUE },
64
+ { key: 'params', inputType: InputType.URL_PARAMETER },
65
+ { key: 'body', inputType: bodyType }
66
+ ].forEach(({ key, inputType }) => {
67
+ try {
68
+ sources.handle({
69
+ data: request[key],
70
+ inputType,
71
+ name,
72
+ stacktraceOpts: {
73
+ constructorOpt: preValidationHandler,
74
+ }
75
+ });
76
+ } catch (err) {
77
+ logger.error({ err, inputType, name }, 'unable to handle fastify source');
78
+ }
79
+ });
80
+
81
+ done();
82
+ });
83
+ },
84
+ }));
85
+ }
86
+ };
87
+
88
+ return source;
89
+ };
@@ -0,0 +1,31 @@
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 { callChildComponentMethodsSync } = require('@contrast/common');
19
+
20
+ module.exports = function(core) {
21
+ const sources = core.assess.dataflow.sources.fastifyInstrumentation = {};
22
+
23
+ require('./fastify')(core);
24
+ require('./cookie')(core);
25
+
26
+ sources.install = function install() {
27
+ callChildComponentMethodsSync(sources, 'install');
28
+ };
29
+
30
+ return sources;
31
+ };
@@ -0,0 +1,181 @@
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
+ const { patchType } = require('../common');
18
+
19
+ // This is only just initiating an async storage for the http source
20
+ // TODO Tracking the user input
21
+ module.exports = function(core) {
22
+ const {
23
+ scopes: { sources },
24
+ instrumentation: { instrument },
25
+ patcher,
26
+ } = core;
27
+
28
+ const logger = core.logger.child('contrast:assess');
29
+
30
+ /**
31
+ * The around hook for `emit` that
32
+ * invokes the protect service to do analysis when appropriate.
33
+ */
34
+ function around(next, data) {
35
+ const [type] = data.args;
36
+
37
+ if (type !== 'request') {
38
+ return next();
39
+ }
40
+
41
+ try {
42
+ const [, req, response] = data.args;
43
+ const store = sources.getStore();
44
+
45
+ if (!store) {
46
+ logger.debug('cannot acquire store for assess request handling');
47
+ return next();
48
+ }
49
+
50
+ patcher.patch(response, 'writeHead', {
51
+ name: 'write-head',
52
+ patchType,
53
+ pre(data) {
54
+ const obj = data.args[data.args.length - 1];
55
+ if (!obj) return;
56
+
57
+ if (Array.isArray(obj)) {
58
+ for (let i = 0; i < obj.length; i += 2) {
59
+ const key = obj[i];
60
+ const value = obj[i + 1];
61
+
62
+ if (key.toLowerCase() === 'content-type') {
63
+ store.assess.responseData.contentType = value;
64
+ }
65
+ }
66
+ } else if (typeof obj === 'object') {
67
+ for (const [key, value] of Object.entries(obj)) {
68
+ if (key.toLowerCase() === 'content-type') {
69
+ store.assess.responseData.contentType = value;
70
+ }
71
+ }
72
+ }
73
+ }
74
+ });
75
+
76
+ patcher.patch(response, 'setHeader', {
77
+ alwaysRun: true,
78
+ name: 'set-header',
79
+ patchType,
80
+ pre(data) {
81
+ const [name = '', value] = data.args;
82
+ if (name.toLowerCase() === 'content-type' && value) {
83
+ store.assess.responseData.contentType = value;
84
+ }
85
+ }
86
+ });
87
+
88
+ let uriPath, queries;
89
+ const ix = req.url.indexOf('?');
90
+
91
+ if (ix >= 0) {
92
+ uriPath = req.url.slice(0, ix);
93
+ queries = req.url.slice(ix + 1);
94
+ } else {
95
+ uriPath = req.url;
96
+ queries = '';
97
+ }
98
+
99
+ const headers = {};
100
+
101
+ for (let i = 0; i < req.rawHeaders.length; i += 2) {
102
+ const header = req.rawHeaders[i].toLowerCase();
103
+ headers[header] = req.rawHeaders[i + 1];
104
+ }
105
+
106
+ const contentType = headers['content-type']?.toLowerCase();
107
+ const reqData = {
108
+ ip: req.socket.remoteAddress,
109
+ httpVersion: req.httpVersion,
110
+ method: req.method,
111
+ headers,
112
+ uriPath,
113
+ queries,
114
+ contentType,
115
+ };
116
+
117
+ store.assess = {
118
+ reqData,
119
+ responseData: {},
120
+ sourceEventsCount: 0,
121
+ propagationEventsCount: 0,
122
+ findings: {},
123
+ };
124
+ } catch (err) {
125
+ logger.error({ err }, 'Error during assess request handling');
126
+ }
127
+
128
+ return next();
129
+ }
130
+
131
+ function install() {
132
+ [{
133
+ moduleName: 'http'
134
+ },
135
+ {
136
+ moduleName: 'https'
137
+ },
138
+ {
139
+ moduleName: 'spdy'
140
+ },
141
+ {
142
+ moduleName: 'http2',
143
+ patchObjectsProps: [
144
+ {
145
+ methods: ['createServer', 'createSecureServer'],
146
+ patchType,
147
+ patchObjects: [
148
+ {
149
+ name: 'Server.prototype',
150
+ methods: ['emit'],
151
+ patchType,
152
+ around
153
+ }
154
+ ]
155
+ }
156
+ ]
157
+ }].forEach(({ moduleName, patchObjectsProps }) => {
158
+ const patchObjects = patchObjectsProps || [
159
+ {
160
+ name: 'Server.prototype',
161
+ methods: ['emit'],
162
+ patchType,
163
+ around
164
+ }
165
+ ];
166
+ instrument({
167
+ moduleName,
168
+ patchObjects
169
+ });
170
+ });
171
+ }
172
+
173
+ function uninstall() {
174
+ return null;
175
+ }
176
+
177
+ return core.assess.dataflow.sources.httpSourcesInstr = {
178
+ install,
179
+ uninstall
180
+ };
181
+ };
@@ -0,0 +1,88 @@
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 { patchType } = require('../common');
19
+
20
+ module.exports = function(core) {
21
+ const {
22
+ createSnapshot,
23
+ patcher,
24
+ depHooks,
25
+ scopes,
26
+ assess: {
27
+ dataflow: { tracker, sources },
28
+ },
29
+ logger
30
+ } = core;
31
+
32
+ function makeCapturer(opts) {
33
+ const snapshot = createSnapshot(opts);
34
+ return function(obj) {
35
+ obj.stack = snapshot();
36
+ return obj;
37
+ };
38
+ }
39
+
40
+ const qsSourcesInstr = sources.qsSourcesInstr = {
41
+ install() {
42
+ depHooks.resolve({ name: 'qs' }, (qs) => {
43
+ patcher.patch(qs, 'parse', {
44
+ name: 'qs',
45
+ patchType,
46
+ post({ args, orig, hooked, result }) {
47
+ if (result && Object.keys(result).length) {
48
+ const assessStore = scopes.sources.getStore().assess;
49
+
50
+ if (!assessStore) {
51
+ logger.debug('assessStore not available in `qs` hook');
52
+ } else {
53
+ const captureStack = makeCapturer({ constructorOpt: hooked, prependFrames: [orig] });
54
+
55
+ // We need to track `qs` result only when it's used as a query parser.
56
+ // `qs` is used also for parsing bodies, but these cases we handle individually with
57
+ // the respective library that's using it (e.g. `formidable`, `co-body`) because in
58
+ // some cases its use is optional and we cannot rely on it.
59
+ if (assessStore.reqData.queries === args[0]) {
60
+ for (const [key, value] of Object.entries(result)) {
61
+ // TODO Same as fastify - do we need a try-catch
62
+ // and checks for already tracked strings
63
+ const { extern } = tracker.track(
64
+ value,
65
+ captureStack({
66
+ inputType: 'query',
67
+ name: key,
68
+ tags: {
69
+ untrusted: [0, value.length - 1],
70
+ }
71
+ })
72
+ );
73
+
74
+ if (extern) {
75
+ result[key] = extern;
76
+ }
77
+ }
78
+ }
79
+ }
80
+ }
81
+ }
82
+ });
83
+ });
84
+ },
85
+ };
86
+
87
+ return qsSourcesInstr;
88
+ };
@@ -0,0 +1,122 @@
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
+ function ensureTagsImmutable(obj, tagName) {
19
+ return obj[tagName] ? [...obj[tagName]] : [];
20
+ }
21
+
22
+ function ensureObject(tags) {
23
+ return tags ? { ...tags } : {};
24
+ }
25
+
26
+ function atomicAppend(firstTagRanges, secondTagRanges, offset) {
27
+ const newTagRanges = [...firstTagRanges];
28
+
29
+ for (let i = 0; i < secondTagRanges.length; i++) {
30
+ secondTagRanges[i] += offset;
31
+ }
32
+
33
+ const firstTagRangesLastEnd = firstTagRanges[firstTagRanges.length - 1];
34
+
35
+ if (firstTagRangesLastEnd === secondTagRanges[0] || firstTagRangesLastEnd === secondTagRanges[0] - 1) {
36
+ newTagRanges.pop();
37
+ secondTagRanges.shift();
38
+ }
39
+
40
+ newTagRanges.push(...secondTagRanges);
41
+
42
+ return newTagRanges;
43
+ }
44
+
45
+ function atomicSubset(tags, subsetStart, len) {
46
+ const ret = [];
47
+ const subsetStop = subsetStart + len;
48
+
49
+ for (let idx = 0; idx < tags.length - 1; idx += 2) {
50
+ const tagStart = tags[idx];
51
+ const tagStop = tags[idx + 1];
52
+
53
+ if (tagStop < subsetStart) {
54
+ // substr is below - continue to check next range
55
+ continue;
56
+ }
57
+
58
+ if (tagStart > subsetStop) {
59
+ // all other tags are above subset so we can stop
60
+ break;
61
+ }
62
+ ret.push(
63
+ Math.max(tagStart, subsetStart) - subsetStart,
64
+ Math.min(tagStop, subsetStop) - subsetStart,
65
+ );
66
+ }
67
+
68
+ return ret;
69
+ }
70
+
71
+ function createAppendTags(firstTags, secondTags, offset) {
72
+ const ret = Object.create(null);
73
+ const firstTagsObject = ensureObject(firstTags);
74
+ const secondTagsObject = ensureObject(secondTags);
75
+ const tagNames = new Set([...Object.keys(firstTagsObject), ...Object.keys(secondTagsObject)]);
76
+
77
+ for (const tagName of tagNames) {
78
+ const newTagRanges = atomicAppend(ensureTagsImmutable(firstTagsObject, tagName), ensureTagsImmutable(secondTagsObject, tagName), offset);
79
+
80
+ newTagRanges.length && (ret[tagName] = newTagRanges);
81
+ }
82
+
83
+ return Object.keys(ret).length ? ret : null;
84
+ }
85
+
86
+ /**
87
+ * assumes:
88
+ * - no mutation of arguments
89
+ * - input ranges will be in non-decreasing order
90
+ * - input ranges are "merged"
91
+ * i.e. no ranges are adjacent
92
+ * i.e. [0, 0, 1, 1] should be [0, 1]
93
+ * - return ranges will be merged and in non-decreasing order
94
+ */
95
+ function createSubsetTags(tags, subsetStart, len) {
96
+ const ret = Object.create(null);
97
+
98
+ for (const tagName of Object.keys(ensureObject(tags))) {
99
+ const newTagRanges = atomicSubset(ensureTagsImmutable(tags, tagName), subsetStart, len);
100
+
101
+ newTagRanges.length && (ret[tagName] = newTagRanges);
102
+ }
103
+
104
+ return Object.keys(ret).length ? ret : null;
105
+ }
106
+
107
+ function createFullLengthCopyTags(tags, resultLength) {
108
+ if (!resultLength || resultLength <= 0) return null;
109
+ const ret = Object.create(null);
110
+
111
+ for (const tagName of Object.keys(ensureObject(tags))) {
112
+ ret[tagName] = [0, resultLength - 1];
113
+ }
114
+
115
+ return Object.keys(ret).length ? ret : null;
116
+ }
117
+
118
+ module.exports = {
119
+ createSubsetTags,
120
+ createAppendTags,
121
+ createFullLengthCopyTags
122
+ };
@@ -0,0 +1,127 @@
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 distringuish = require('@contrast/distringuish');
19
+
20
+ module.exports = function tracker(core) {
21
+ const {
22
+ assess: {
23
+ dataflow: {
24
+ eventFactory: {
25
+ createdEvents
26
+ }
27
+ }
28
+ },
29
+ logger
30
+ } = core;
31
+
32
+ const objMap = new WeakMap();
33
+
34
+ function getData(value) {
35
+ if (typeof value === 'string') {
36
+ return distringuish.getProperties(value);
37
+ }
38
+
39
+ return objMap.get(value) || null;
40
+ }
41
+
42
+ function track(value, metadata) {
43
+ let ret = Object.create(null);
44
+
45
+ if (!value) {
46
+ const err = new Error();
47
+ logger.error({ err, value }, 'tracker.track called with invalid argument: value is falsy');
48
+ return { extern: null };
49
+ }
50
+ if (!metadata) {
51
+ const err = new Error();
52
+ logger.error({ err, metadata }, 'tracker.track called with invalid argument: metadata is falsy');
53
+ return { extern: null };
54
+ }
55
+
56
+ if (!createdEvents.has(metadata)) {
57
+ const err = new Error();
58
+ logger.error({ err, metadata }, 'tracker.track called without validated metadata');
59
+ return { extern: null };
60
+ }
61
+
62
+ if (typeof value === 'string') {
63
+ if (distringuish.getProperties(value)) {
64
+ const err = new Error();
65
+ logger.error({ err, value }, 'tracker.track called with a string value that is already tracked');
66
+ return { extern: null };
67
+ }
68
+
69
+ const extern = distringuish.externalize(value);
70
+ /* c8 ignore next 5 */
71
+ if (!extern) {
72
+ const err = new Error();
73
+ logger.error({ err, value }, 'tracker.track was unable to externalize');
74
+ return { extern: null };
75
+ }
76
+
77
+ const externMetadata = distringuish.getProperties(extern);
78
+ metadata.value = value;
79
+ metadata.extern = extern;
80
+
81
+ ret = Object.assign(externMetadata, metadata);
82
+
83
+ return ret;
84
+ } else if (typeof value === 'object') {
85
+ const objInfo = objMap.get(value);
86
+ if (objInfo) {
87
+ return objInfo;
88
+ }
89
+
90
+ objMap.set(value, metadata);
91
+
92
+ return metadata;
93
+ }
94
+
95
+ const err = new Error();
96
+ logger.error({ err, value }, 'tracker.track called with a value type that is not trackable');
97
+ return null;
98
+ }
99
+
100
+ function untrack(value) {
101
+ if (typeof value === 'string') {
102
+ const props = distringuish.getProperties(value);
103
+ if (props) {
104
+ Object.assign(props, {
105
+ history: [],
106
+ tags: {},
107
+ });
108
+ delete props.resultTracked;
109
+ }
110
+ return distringuish.internalize(value);
111
+ }
112
+
113
+ if (typeof value === 'object') {
114
+ objMap.delete(value);
115
+ return value;
116
+ }
117
+
118
+ return null;
119
+ }
120
+
121
+ return core.assess.dataflow.tracker = {
122
+ track,
123
+ untrack,
124
+ getData,
125
+ getInfo: getData,
126
+ };
127
+ };
@@ -0,0 +1,30 @@
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
+ 'use strict';
16
+
17
+ const SAFE_XSS_CONTENT_TYPES = [
18
+ '/json',
19
+ '/x-json',
20
+ '/javascript',
21
+ '/x-javascript',
22
+ '/pdf',
23
+ '/csv'
24
+ ];
25
+
26
+ function isSafeContentType(contentType) {
27
+ return new RegExp(SAFE_XSS_CONTENT_TYPES.join('|')).test(contentType);
28
+ }
29
+
30
+ module.exports = { isSafeContentType, SAFE_XSS_CONTENT_TYPES };