@contrast/core 1.56.0 → 1.57.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.
package/lib/index.d.ts CHANGED
@@ -44,9 +44,25 @@ export interface Core {
44
44
 
45
45
  messages: Messages;
46
46
 
47
- sensitiveDataMasking: any;
47
+ sensitiveDataMasking: SensitiveDataMasking;
48
48
 
49
49
  getSystemInfo(): Promise<SystemInfo>;
50
50
  getBuildId(): Promse<number | void>;
51
51
  Perf: any;
52
52
  }
53
+
54
+ export interface SensitiveDataMasking {
55
+ policy: any;
56
+ idMap: Map<Set, string>;
57
+ keywordSets: Set<string>[];
58
+ maskHttpBody: boolean;
59
+ maskAttackVector: boolean;
60
+ createMasker(): Masker;
61
+ getRedactedText(key: string, value: string): string;
62
+ traverseAndMask(target: Record<string, string>, p: Masker | Set<string>): void;
63
+ }
64
+
65
+ export interface Masker {
66
+ unmasked?: Set<string>;
67
+ getMaskedValue(key: string, value: string): string;
68
+ }
@@ -22,29 +22,44 @@ module.exports = function(core) {
22
22
  const idMap = new Map();
23
23
  const keywordSets = [];
24
24
 
25
- function getRedactedText(key) {
25
+ function getRedactedText(key, value) {
26
26
  key = StringPrototypeToLowerCase.call(key);
27
27
  for (const set of keywordSets) {
28
28
  if (set.has(key)) {
29
29
  return `${CONTRAST_REDACTED}-${idMap.get(set)}`;
30
30
  }
31
31
  }
32
+ return value;
32
33
  }
33
34
 
34
- function traverseAndMask(target, unmasked) {
35
+ function traverseAndMask1(target, unmasked) {
35
36
  let redactedText;
36
37
  if (!target) return;
37
38
 
38
39
  traverseKeys(target, (path, type, value, obj) => {
39
40
  redactedText = getRedactedText(value);
40
41
  if (redactedText) {
41
- if (unmasked) unmasked.add(obj[value]);
42
+ unmasked?.add?.(obj[value]);
42
43
  obj[value] = redactedText;
43
44
  redactedText = undefined;
44
45
  }
45
46
  });
46
47
  }
47
48
 
49
+ function traverseAndMask2(target, masker) {
50
+ if (!target) return;
51
+ traverseKeys(target, (path, type, key, obj) => {
52
+ obj[key] = masker.getMaskedValue(key, obj[key]);
53
+ });
54
+ }
55
+
56
+ function traverseAndMask(target, param) {
57
+ if (param instanceof Masker) {
58
+ return traverseAndMask2(target, param);
59
+ }
60
+ return traverseAndMask1(target, param);
61
+ }
62
+
48
63
  const sensitiveDataMasking = core.sensitiveDataMasking = {
49
64
  policy: {
50
65
  idMap,
@@ -53,11 +68,24 @@ module.exports = function(core) {
53
68
  maskAttackVector: false,
54
69
  },
55
70
  getRedactedText,
56
- traverseAndMask
71
+ traverseAndMask,
72
+ createMasker() {
73
+ return new Masker();
74
+ },
57
75
  };
58
76
 
77
+ class Masker {
78
+ getMaskedValue(key, value) {
79
+ const redacted = getRedactedText(key, value);
80
+ if (value !== redacted) {
81
+ !this.unmasked && (this.unmasked = new Set());
82
+ this.unmasked.add(value);
83
+ }
84
+ return redacted;
85
+ }
86
+ }
87
+
59
88
  require('./server-settings-listener')(core);
60
- require('./protect-listener')(core);
61
89
 
62
90
  return sensitiveDataMasking;
63
91
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/core",
3
- "version": "1.56.0",
3
+ "version": "1.57.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)",
@@ -19,12 +19,12 @@
19
19
  "test": "bash ../scripts/test.sh"
20
20
  },
21
21
  "dependencies": {
22
- "@contrast/common": "1.36.0",
23
- "@contrast/config": "1.51.0",
22
+ "@contrast/common": "1.37.0",
23
+ "@contrast/config": "1.52.0",
24
24
  "@contrast/find-package-json": "^1.1.0",
25
25
  "@contrast/fn-inspect": "^5.0.2",
26
- "@contrast/logger": "1.29.0",
27
- "@contrast/patcher": "1.28.0",
26
+ "@contrast/logger": "1.30.0",
27
+ "@contrast/patcher": "1.29.0",
28
28
  "@contrast/perf": "1.4.0",
29
29
  "@tsxper/crc32": "^2.1.3",
30
30
  "axios": "^1.11.0",
@@ -1,111 +0,0 @@
1
- /*
2
- * Copyright: 2025 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, primordials: { StringPrototypeReplace } } = require('@contrast/common');
20
-
21
- const { CONTRAST_REDACTED } = require('./constants');
22
-
23
- module.exports = function (core) {
24
- const {
25
- messages,
26
- logger,
27
- sensitiveDataMasking: { policy, getRedactedText, traverseAndMask },
28
- } = core;
29
-
30
- messages.on(Event.PROTECT, (store) => {
31
- if (!store.protect || !policy.keywordSets.length || !store.sourceInfo) {
32
- return;
33
- }
34
-
35
- logger.trace('masking sensitive fields in %s message', Event.PROTECT);
36
-
37
- const unmasked = policy.maskAttackVector ? new Set() : undefined;
38
- if (policy.maskHttpBody) {
39
- store.protect.parsedBody = `${CONTRAST_REDACTED}-body`;
40
- } else {
41
- traverseAndMask(store.protect?.parsedBody, unmasked);
42
- }
43
-
44
- traverseAndMask(store.protect?.parsedCookies, unmasked);
45
- traverseAndMask(store.protect?.parsedQuery, unmasked);
46
-
47
- // Do parsed URL path params and urlPath together
48
- const params = store.protect?.parsedParams;
49
- if (params) {
50
- for (const [key, value] of Object.entries(params)) {
51
- const redactedText = getRedactedText(key);
52
- if (redactedText) {
53
- const encoded = encodeURIComponent(value);
54
- store.sourceInfo.uriPath = StringPrototypeReplace.call(
55
- store.sourceInfo.uriPath,
56
- encoded,
57
- redactedText
58
- );
59
- store.protect.parsedParams[key] = redactedText;
60
- }
61
- }
62
- }
63
-
64
- // raw headers
65
- const headers = store.sourceInfo.rawHeaders;
66
- for (let i = 0; i <= headers.length - 2; i += 2) {
67
- const key = headers[i];
68
-
69
- const redactedText = getRedactedText(key);
70
- if (redactedText) {
71
- headers[i + 1] = redactedText;
72
- }
73
- }
74
-
75
- // raw queries
76
- if (store.sourceInfo?.queries) {
77
- const searchParams = new URLSearchParams(store.sourceInfo.queries);
78
- for (const [key] of searchParams) {
79
- const redactedText = getRedactedText(key);
80
- if (redactedText) {
81
- searchParams.set(key, redactedText);
82
- }
83
- }
84
- store.sourceInfo.queries = searchParams.toString();
85
- }
86
-
87
- if (policy.maskAttackVector) {
88
- // attack values
89
- const inputAnalysis = Object.entries(store.protect?.resultsMap);
90
- for (const [, results] of inputAnalysis) {
91
- for (const result of results) {
92
- const redactedText = getRedactedText(result.key);
93
- if (result.exploitMetadata.length) {
94
- result.exploitMetadata.forEach((exploit) => {
95
- unmasked.forEach((val) => {
96
- exploit.sinkContext.value = StringPrototypeReplace.call(
97
- exploit.sinkContext.value,
98
- val,
99
- 'contrast-redacted-vector'
100
- );
101
- });
102
- });
103
- }
104
- if (redactedText) {
105
- result.value = redactedText;
106
- }
107
- }
108
- }
109
- }
110
- });
111
- };