@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.
- package/lib/{make-response-blocker.js → blocker.js} +20 -17
- package/lib/error-handlers/common-handler.js +1 -1
- package/lib/error-handlers/install/express.js +1 -1
- package/lib/error-handlers/install/fastify.js +1 -1
- package/lib/error-handlers/install/koa2.js +1 -1
- package/lib/error-handlers/install/restify.js +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.js +1 -1
- package/lib/input-analysis/index.js +4 -0
- package/lib/input-analysis/install/fastify-send.js +52 -0
- package/lib/input-analysis/install/http.js +1 -1
- package/lib/input-analysis/install/send.js +45 -0
- package/lib/make-source-context.js +1 -1
- package/package.json +12 -12
|
@@ -27,32 +27,35 @@ module.exports = function(core) {
|
|
|
27
27
|
const blocked = new WeakSet();
|
|
28
28
|
const { patcher, logger } = core;
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (blocked.has(res)) return;
|
|
30
|
+
class Blocker {
|
|
31
|
+
#res;
|
|
33
32
|
|
|
34
|
-
|
|
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
|
-
|
|
53
|
-
makeResponseBlocker.blocked = blocked;
|
|
54
|
-
|
|
55
|
-
core.protect.makeResponseBlocker = makeResponseBlocker;
|
|
56
|
-
|
|
57
|
-
return makeResponseBlocker;
|
|
60
|
+
return Blocker;
|
|
58
61
|
};
|
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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('./
|
|
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
|
-
|
|
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.
|
|
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.
|
|
24
|
-
"@contrast/common": "1.
|
|
25
|
-
"@contrast/config": "1.
|
|
26
|
-
"@contrast/core": "1.
|
|
27
|
-
"@contrast/dep-hooks": "1.
|
|
28
|
-
"@contrast/esm-hooks": "2.
|
|
29
|
-
"@contrast/instrumentation": "1.
|
|
30
|
-
"@contrast/logger": "1.
|
|
31
|
-
"@contrast/patcher": "1.
|
|
32
|
-
"@contrast/rewriter": "1.
|
|
33
|
-
"@contrast/scopes": "1.
|
|
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",
|