@contrast/protect 1.54.2 → 1.56.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.
@@ -27,32 +27,35 @@ module.exports = function(core) {
27
27
  const blocked = new WeakSet();
28
28
  const { patcher, logger } = core;
29
29
 
30
- function makeResponseBlocker(res) {
31
- return function block(mode, ruleId) {
32
- if (blocked.has(res)) return;
30
+ class Blocker {
31
+ #res;
33
32
 
34
- blocked.add(res);
33
+ constructor(response) {
34
+ this.#res = response;
35
+ this._blocked = blocked; //expose for testing
36
+ }
37
+
38
+ block(mode, ruleId) {
39
+ if (blocked.has(this.#res)) return;
40
+
41
+ blocked.add(this.#res);
35
42
  mode = StringPrototypeToUpperCase.call(mode);
36
- const end = patcher.unwrap(res.end);
37
- const writeHead = patcher.unwrap(res.writeHead);
43
+ const end = patcher.unwrap(this.#res.end);
44
+ const writeHead = patcher.unwrap(this.#res.writeHead);
38
45
 
39
46
  try {
40
- clearTimeout(res[kMetrics]?.timeout);
41
- delete res[kMetrics];
47
+ clearTimeout(this.#res[kMetrics]?.timeout);
48
+ delete this.#res[kMetrics];
42
49
 
43
- if (!res.headersSent) writeHead.call(res, 403);
44
- end.call(res, '');
50
+ if (!this.#res.headersSent) writeHead.call(this.#res, 403);
51
+ end.call(this.#res, '');
45
52
  logger.info({ mode, ruleId }, 'Request blocked');
46
53
  } catch (err) {
47
54
  logger.error({ err, mode, ruleId }, 'Error blocking request');
48
55
  }
49
- };
56
+ }
50
57
  }
58
+ core.protect.Blocker = Blocker;
51
59
 
52
- // expose. for testing?
53
- makeResponseBlocker.blocked = blocked;
54
-
55
- core.protect.makeResponseBlocker = makeResponseBlocker;
56
-
57
- return makeResponseBlocker;
60
+ return Blocker;
58
61
  };
@@ -35,6 +35,6 @@ module.exports = function (core) {
35
35
  }
36
36
 
37
37
  const blockInfo = sourceContext.securityException;
38
- sourceContext.block(...blockInfo);
38
+ sourceContext.blocker.block(...blockInfo);
39
39
  };
40
40
  };
@@ -35,7 +35,7 @@ module.exports = function (core) {
35
35
  if (isSecurityException && sourceContext) {
36
36
  const blockInfo = sourceContext.securityException;
37
37
 
38
- sourceContext.block(...blockInfo);
38
+ sourceContext.blocker.block(...blockInfo);
39
39
  return;
40
40
  }
41
41
 
@@ -63,7 +63,7 @@ module.exports = function (core) {
63
63
  normalHandler.call(this, err, request, reply);
64
64
  } else {
65
65
  const blockInfo = sourceContext.securityException;
66
- sourceContext.block(...blockInfo);
66
+ sourceContext.blocker.block(...blockInfo);
67
67
  }
68
68
  } else {
69
69
  normalHandler.call(this, err, request, reply);
@@ -47,7 +47,7 @@ module.exports = function (core) {
47
47
  if (isSecurityException && sourceContext) {
48
48
  data.obj.body = '';
49
49
  const blockInfo = sourceContext.securityException;
50
- sourceContext.block(...blockInfo);
50
+ sourceContext.blocker.block(...blockInfo);
51
51
  return;
52
52
  }
53
53
 
@@ -38,7 +38,7 @@ module.exports = function init(core) {
38
38
  return;
39
39
  }
40
40
 
41
- sourceContext.block(...sourceContext.securityException);
41
+ sourceContext.blocker.block(...sourceContext.securityException);
42
42
  return;
43
43
  }
44
44
 
package/lib/index.d.ts CHANGED
@@ -13,7 +13,7 @@
13
13
  * way not consistent with the End User License Agreement.
14
14
  */
15
15
 
16
- import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode } from '@contrast/common';
16
+ import { ReqData, ProtectMessage, ResultMap, ProtectRuleMode, Blocker } from '@contrast/common';
17
17
  import { IncomingMessage, ServerResponse } from 'node:http';
18
18
  import * as http from 'node:http';
19
19
  import * as https from 'node:https';
@@ -25,7 +25,7 @@ type Https = typeof https;
25
25
  export type Block = (mode: string, ruleId: string) => void;
26
26
  export interface ProtectRequestStore {
27
27
  reqData: ReqData;
28
- block: Block;
28
+ blocker: Blocker;
29
29
  rules: Record<Rule, { mode: ProtectRuleMode }>;
30
30
  exclusions: any[]; // TODO
31
31
  virtualPatches: any[]; // TODO
@@ -56,7 +56,7 @@ export interface ExclusionPolicy {
56
56
  export type ProtectPolicy = ExclusionPolicy & Record<rule, ProtectRuleMode> & { rulesMask: number };
57
57
 
58
58
  export interface Protect {
59
- makeResponseBlocker: (res: ServerResponse) => Block,
59
+ Blocker: Blocker,
60
60
  makeSourceContext: (req: IncomingMessage, res: ServerResponse) => ProtectRequestStore,
61
61
  throwSecurityException: (sourceContext: ProtectRequestStore) => void,
62
62
  policy: ProtectPolicy,
package/lib/index.js CHANGED
@@ -26,7 +26,7 @@ module.exports = function(core) {
26
26
 
27
27
  require('./policy')(core);
28
28
  require('./throw-security-exception')(core);
29
- require('./make-response-blocker')(core);
29
+ require('./blocker')(core);
30
30
  require('./make-source-context')(core);
31
31
  require('./get-source-context')(core);
32
32
  require('./error-handlers')(core);
@@ -43,6 +43,10 @@ module.exports = function(core) {
43
43
  require('./install/hapi')(core);
44
44
  require('./install/restify')(core);
45
45
 
46
+ // no-instrumentation scope wrappers for false positive prevention
47
+ require('./install/fastify-send')(core);
48
+ require('./install/send')(core);
49
+
46
50
  // virtual patches
47
51
  require('./virtual-patches')(core);
48
52
  require('./ip-analysis')(core);
@@ -0,0 +1,52 @@
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 { patchType } = require('../constants');
19
+
20
+ module.exports = function (core) {
21
+ const { depHooks, patcher, scopes: { instrumentation } } = core;
22
+
23
+ return core.protect.inputAnalysis.fastifySendInstrumentation = {
24
+ install() {
25
+ const store = { lock: true, name: 'protect:input-analysis:fastify-send' };
26
+
27
+ const around = (next) => {
28
+ if (!instrumentation.isLocked()) {
29
+ return instrumentation.run(store, next);
30
+ }
31
+
32
+ return next();
33
+ };
34
+
35
+ depHooks.resolve({ name: '@fastify/send', version: '<3', file: 'lib/SendStream.js' }, (SendStream) => {
36
+ patcher.patch(SendStream.prototype, 'sendFile', {
37
+ name: '@fastify/send/lib/SendStream.js',
38
+ patchType,
39
+ around,
40
+ });
41
+ });
42
+
43
+ depHooks.resolve({ name: '@fastify/send', version: '>=3 <5', file: 'lib/send.js' }, (send) => {
44
+ patcher.patch(send, 'send', {
45
+ name: '@fastify/send/lib/send.js',
46
+ patchType,
47
+ around,
48
+ });
49
+ });
50
+ }
51
+ };
52
+ };
@@ -112,7 +112,7 @@ module.exports = function (core) {
112
112
  if (!block) {
113
113
  callNext();
114
114
  } else {
115
- store.protect.block(...block);
115
+ store.protect.blocker.block(...block);
116
116
  logger.debug({ block, funcKey: data.funcKey }, 'request blocked by not emitting request event');
117
117
  }
118
118
  }
@@ -0,0 +1,45 @@
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 { patchType } = require('../constants');
19
+
20
+ module.exports = function (core) {
21
+ const { depHooks, patcher, scopes: { instrumentation } } = core;
22
+
23
+ return core.protect.inputAnalysis.sendInstrumentation = {
24
+ install() {
25
+ const store = { lock: true, name: 'protect:input-analysis:send' };
26
+
27
+ const around = (next) => {
28
+ if (!instrumentation.isLocked()) {
29
+ return instrumentation.run(store, next);
30
+ }
31
+
32
+ return next();
33
+ };
34
+
35
+ depHooks.resolve(
36
+ { name: 'send', version: '<2' },
37
+ (send) => patcher.patch(send, {
38
+ name: 'send',
39
+ patchType,
40
+ around,
41
+ })
42
+ );
43
+ }
44
+ };
45
+ };
@@ -82,7 +82,7 @@ module.exports = function(core) {
82
82
  statusCode: null,
83
83
  },
84
84
  // block closure captures res so it isn't exposed to beyond here
85
- block: core.protect.makeResponseBlocker(res),
85
+ blocker: new core.protect.Blocker(res),
86
86
  policy,
87
87
  exclusions: [],
88
88
  virtualPatchesEvaluators: [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@contrast/protect",
3
- "version": "1.54.2",
3
+ "version": "1.56.0",
4
4
  "description": "Contrast service providing framework-agnostic Protect support",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "Contrast Security <nodejs@contrastsecurity.com> (https://www.contrastsecurity.com)",
@@ -20,17 +20,17 @@
20
20
  "test": "../scripts/test.sh"
21
21
  },
22
22
  "dependencies": {
23
- "@contrast/agent-lib": "^9.0.0",
24
- "@contrast/common": "1.29.1",
25
- "@contrast/config": "1.40.2",
26
- "@contrast/core": "1.45.2",
27
- "@contrast/dep-hooks": "1.14.2",
28
- "@contrast/esm-hooks": "2.19.4",
29
- "@contrast/instrumentation": "1.24.2",
30
- "@contrast/logger": "1.18.2",
31
- "@contrast/patcher": "1.17.2",
32
- "@contrast/rewriter": "1.21.4",
33
- "@contrast/scopes": "1.15.2",
23
+ "@contrast/agent-lib": "^9.1.0",
24
+ "@contrast/common": "1.31.0",
25
+ "@contrast/config": "1.42.0",
26
+ "@contrast/core": "1.47.0",
27
+ "@contrast/dep-hooks": "1.16.0",
28
+ "@contrast/esm-hooks": "2.21.0",
29
+ "@contrast/instrumentation": "1.26.0",
30
+ "@contrast/logger": "1.20.0",
31
+ "@contrast/patcher": "1.19.0",
32
+ "@contrast/rewriter": "1.23.0",
33
+ "@contrast/scopes": "1.17.0",
34
34
  "async-hook-domain": "^4.0.1",
35
35
  "ipaddr.js": "^2.0.1",
36
36
  "on-finished": "^2.4.1",