@chiranthmoger/fortifyjs 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 (50) hide show
  1. package/LICENSE +9 -0
  2. package/README.md +186 -0
  3. package/bin/fortifyjs.js +135 -0
  4. package/examples/fastify.js +18 -0
  5. package/examples/hono.js +13 -0
  6. package/examples/kitchen-sink.js +50 -0
  7. package/examples/koa.js +14 -0
  8. package/examples/minimal-express.js +13 -0
  9. package/examples/production-express.js +20 -0
  10. package/index.d.ts +101 -0
  11. package/package.json +70 -0
  12. package/src/adapters/express.js +1027 -0
  13. package/src/adapters/fastify.js +56 -0
  14. package/src/adapters/generic.js +15 -0
  15. package/src/adapters/hono.js +77 -0
  16. package/src/adapters/koa.js +58 -0
  17. package/src/adapters/nestjs.js +15 -0
  18. package/src/adapters/nextjs.js +84 -0
  19. package/src/analyzers/adaptive.js +92 -0
  20. package/src/analyzers/behavioral.js +264 -0
  21. package/src/core/confidence.js +23 -0
  22. package/src/core/engine.js +162 -0
  23. package/src/core/normalizer.js +149 -0
  24. package/src/core/whitelist.js +40 -0
  25. package/src/dashboard/handler.js +307 -0
  26. package/src/detectors/cmdi.js +82 -0
  27. package/src/detectors/crlf.js +33 -0
  28. package/src/detectors/graphql.js +56 -0
  29. package/src/detectors/hpp.js +38 -0
  30. package/src/detectors/ldap.js +50 -0
  31. package/src/detectors/nosqli.js +134 -0
  32. package/src/detectors/open-redirect.js +42 -0
  33. package/src/detectors/path-traversal.js +55 -0
  34. package/src/detectors/prototype-pollution.js +65 -0
  35. package/src/detectors/sqli.js +447 -0
  36. package/src/detectors/sqli.js.bak +446 -0
  37. package/src/detectors/ssrf.js +64 -0
  38. package/src/detectors/template-injection.js +34 -0
  39. package/src/detectors/xss.js +191 -0
  40. package/src/detectors/xxe.js +45 -0
  41. package/src/forensics/reporter.js +74 -0
  42. package/src/index.js +132 -0
  43. package/src/logger.js +110 -0
  44. package/src/presets.js +183 -0
  45. package/src/shields/bot-detector.js +88 -0
  46. package/src/shields/cors.js +95 -0
  47. package/src/shields/csrf.js +120 -0
  48. package/src/shields/file-upload.js +160 -0
  49. package/src/shields/headers.js +99 -0
  50. package/src/shields/rate-limiter.js +70 -0
@@ -0,0 +1,82 @@
1
+ 'use strict';
2
+
3
+ const DANGEROUS_COMMANDS = 'rm|cat|wget|curl|nc|bash|sh|cmd|powershell|python|perl|ruby|node|php';
4
+
5
+ module.exports = {
6
+ name: 'cmdi',
7
+ label: 'cmdi',
8
+ getSignals() {
9
+ return [
10
+ {
11
+ id: 'shell-command-chain',
12
+ confidence: 0.80,
13
+ pattern: new RegExp(`(?:;|&&|\\|\\|)\\s*(?:${DANGEROUS_COMMANDS})\\b`, 'i')
14
+ },
15
+ {
16
+ id: 'backtick-execution',
17
+ confidence: 0.85,
18
+ pattern: /`[^`]+`/
19
+ },
20
+ {
21
+ id: 'subshell-execution',
22
+ confidence: 0.85,
23
+ pattern: /\$\([^\)]+\)/
24
+ },
25
+ {
26
+ id: 'pipe-to-shell',
27
+ confidence: 0.75,
28
+ pattern: /\|\s*(?:sh|bash|cmd)\b/i
29
+ },
30
+ {
31
+ id: 'shell-binary-path',
32
+ confidence: 0.80,
33
+ pattern: /(?:\/bin\/sh|\/bin\/bash|\/usr\/bin\/env|cmd\.exe|powershell\.exe)\b/i
34
+ },
35
+ {
36
+ id: 'reverse-shell-pattern',
37
+ confidence: 0.90,
38
+ pattern: /(?:bash\s+-i\s+>&|nc\s+-e\s+\/bin\/sh|python\s+-c\s+['"]import\s+socket)/i
39
+ },
40
+ {
41
+ id: 'curl-wget-pipe',
42
+ confidence: 0.70,
43
+ pattern: /(?:curl|wget).*\|\s*(?:bash|sh)\b/i
44
+ },
45
+ {
46
+ id: 'windows-cmd-injection',
47
+ confidence: 0.80,
48
+ pattern: /(?:cmd(?:\.exe)?\s+\/c|powershell(?:\.exe)?\s+-(?:enc|e))\b/i
49
+ },
50
+ {
51
+ id: 'powershell-iex',
52
+ confidence: 0.85,
53
+ pattern: /(?:invoke-expression|iex)\b/i
54
+ },
55
+ {
56
+ id: 'windows-builtins',
57
+ confidence: 0.70,
58
+ pattern: /(?:^|[;&|])\s*(?:type|dir)(?:\s+[^\s]|$)/i
59
+ },
60
+ {
61
+ id: 'newline-injection',
62
+ confidence: 0.80,
63
+ pattern: /(?:%0[ad]|\r|\n)\s*(?:rm|cat|wget|curl|nc|bash|sh|cmd|powershell|python|perl|ruby|node|php)\b/i
64
+ },
65
+ {
66
+ id: 'env-variable-exfil',
67
+ confidence: 0.60,
68
+ pattern: /(?:\$(?:PATH|HOME|USER|ENV\{)|%(?:SYSTEMROOT|APPDATA|USERPROFILE|USERNAME)%)/i
69
+ },
70
+ {
71
+ id: 'process-substitution',
72
+ confidence: 0.75,
73
+ pattern: /[<>]\([^\)]+\)/
74
+ },
75
+ {
76
+ id: 'pipe-operator-command',
77
+ confidence: 0.80,
78
+ pattern: /\|\s*(?:ls|cat|whoami|id|pwd|dir|type)\b/i
79
+ }
80
+ ];
81
+ }
82
+ };
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const HEADER_PATTERN = '(?:Set-Cookie|Location|Bcc|Cc|Content-Type|Content-Length|X-[A-Za-z0-9-]+|Host|Authorization)\\s*:';
4
+
5
+ module.exports = {
6
+ name: 'crlf',
7
+ label: 'crlf',
8
+
9
+ getSignals() {
10
+ return [
11
+ {
12
+ id: 'crlf-header-injection',
13
+ confidence: 0.85,
14
+ pattern: new RegExp(`(?:\\r\\n|\\r|\\n|%0d%0a|%0a)[ \\t]*${HEADER_PATTERN}`, 'i')
15
+ },
16
+ {
17
+ id: 'crlf-encoded-header-injection',
18
+ confidence: 0.80,
19
+ pattern: new RegExp(`(?:\\u560a\\u560d|%E5%98%8A%E5%98%8D|\\\\u000d\\\\u000a)[ \\t]*${HEADER_PATTERN}`, 'i')
20
+ },
21
+ {
22
+ id: 'crlf-log-injection',
23
+ confidence: 0.75,
24
+ pattern: /(?:\r\n|\r|\n|%0d%0a|%0a)[ \t]*(?:\[?(?:info|error|warn|debug|trace)\]?|\[?\d{4}-\d{2}-\d{2}|\[?\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/i
25
+ },
26
+ {
27
+ id: 'crlf-newline-alone',
28
+ confidence: 0.40,
29
+ pattern: /(?:\r\n|%0d%0a)/i
30
+ }
31
+ ];
32
+ }
33
+ };
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ function hasDeepNesting(value) {
4
+ let depth = 0;
5
+ let maxDepth = 0;
6
+ for (let i = 0; i < value.length; i++) {
7
+ if (value[i] === '{') {
8
+ depth++;
9
+ if (depth > maxDepth) maxDepth = depth;
10
+ } else if (value[i] === '}') {
11
+ depth--;
12
+ }
13
+ }
14
+ return maxDepth > 10;
15
+ }
16
+
17
+ function hasAliasBatching(value) {
18
+ const aliasPattern = /[a-zA-Z_][a-zA-Z0-9_]*\s*:\s*[a-zA-Z_][a-zA-Z0-9_]*/g;
19
+ const matches = value.match(aliasPattern);
20
+ return matches !== null && matches.length >= 100;
21
+ }
22
+
23
+ function hasFragmentSpreadAbuse(value) {
24
+ const spreadPattern = /\.\.\.[a-zA-Z0-9_]+/g;
25
+ const matches = value.match(spreadPattern);
26
+ return matches !== null && matches.length >= 10;
27
+ }
28
+
29
+ module.exports = {
30
+ name: 'graphql',
31
+ label: 'graphql',
32
+ getSignals() {
33
+ return [
34
+ {
35
+ id: 'graphql-introspection',
36
+ confidence: 0.9,
37
+ pattern: /\b(__schema|__type)\b/i
38
+ },
39
+ {
40
+ id: 'graphql-deep-nesting',
41
+ confidence: 0.8,
42
+ test: hasDeepNesting
43
+ },
44
+ {
45
+ id: 'graphql-alias-batching',
46
+ confidence: 0.85,
47
+ test: hasAliasBatching
48
+ },
49
+ {
50
+ id: 'graphql-fragment-spread-abuse',
51
+ confidence: 0.7,
52
+ test: hasFragmentSpreadAbuse
53
+ }
54
+ ];
55
+ }
56
+ };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'hpp',
5
+ label: 'hpp',
6
+
7
+ detectQuery(query, allowedArrayParams = []) {
8
+ const signals = [];
9
+ if (!query || typeof query !== 'object') return signals;
10
+
11
+ for (const [key, value] of Object.entries(query)) {
12
+ if (Array.isArray(value) && !allowedArrayParams.includes(key)) {
13
+ signals.push({
14
+ id: 'array-param-unexpected',
15
+ confidence: 0.55,
16
+ label: 'hpp'
17
+ });
18
+
19
+ signals.push({
20
+ id: 'duplicate-query-param',
21
+ confidence: 0.60,
22
+ label: 'hpp'
23
+ });
24
+ }
25
+ }
26
+ return signals;
27
+ },
28
+
29
+ getSignals() {
30
+ return [
31
+ {
32
+ id: 'duplicate-query-param-string',
33
+ confidence: 0.60,
34
+ pattern: /(?:\?|&)([^=&]+)=.*&\1=/
35
+ }
36
+ ];
37
+ }
38
+ };
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ function hasUnbalancedParenthesesWithLdapKeywords(value) {
4
+ const ldapKeywords = /\b(cn|uid|objectclass|memberof)\b/i;
5
+ if (!ldapKeywords.test(value)) {
6
+ return false;
7
+ }
8
+
9
+ let depth = 0;
10
+ for (let i = 0; i < value.length; i++) {
11
+ if (value[i] === '(') depth++;
12
+ if (value[i] === ')') depth--;
13
+ if (depth < 0) return true;
14
+ }
15
+ return depth !== 0;
16
+ }
17
+
18
+ module.exports = {
19
+ name: 'ldap',
20
+ label: 'ldap',
21
+ getSignals() {
22
+ return [
23
+ {
24
+ id: 'ldap-filter-concatenation',
25
+ confidence: 0.8,
26
+ pattern: /\)\s*\(/
27
+ },
28
+ {
29
+ id: 'ldap-wildcard-bypass',
30
+ confidence: 0.9,
31
+ pattern: /\*\)\s*\(\s*objectClass\s*=\s*\*/i
32
+ },
33
+ {
34
+ id: 'ldap-or-injection',
35
+ confidence: 0.85,
36
+ pattern: /\)\s*\(\s*\|/
37
+ },
38
+ {
39
+ id: 'ldap-null-byte',
40
+ confidence: 0.9,
41
+ pattern: /(?:\x00|%00|\\00)/i
42
+ },
43
+ {
44
+ id: 'ldap-unbalanced-parentheses',
45
+ confidence: 0.7,
46
+ test: hasUnbalancedParenthesesWithLdapKeywords
47
+ }
48
+ ];
49
+ }
50
+ };
@@ -0,0 +1,134 @@
1
+ 'use strict';
2
+
3
+ const MONGO_OPERATORS = [
4
+ '\\$gt', '\\$gte', '\\$lt', '\\$lte', '\\$ne', '\\$in', '\\$nin',
5
+ '\\$or', '\\$and', '\\$not', '\\$nor', '\\$exists', '\\$type',
6
+ '\\$expr', '\\$regex', '\\$options', '\\$text', '\\$search',
7
+ '\\$where', '\\$all', '\\$elemMatch', '\\$size', '\\$mod',
8
+ '\\$jsonSchema', '\\$accumulator', '\\$function'
9
+ ].join('|');
10
+
11
+ const ES_OPERATORS = [
12
+ 'script', 'inline', 'painless', 'mvel', 'ctx\\._source'
13
+ ].join('|');
14
+
15
+ const COUCH_OPERATORS = [
16
+ '_design', '_view', '_find', 'map', 'reduce'
17
+ ].join('|');
18
+
19
+ module.exports = {
20
+ name: 'nosqli',
21
+ label: 'nosqli',
22
+
23
+ detectObject(obj, maxDepth = 20) {
24
+ const signals = [];
25
+ if (!obj || typeof obj !== 'object') return signals;
26
+
27
+ function walk(node, depth) {
28
+ if (depth > maxDepth) return;
29
+ if (!node || typeof node !== 'object') return;
30
+
31
+ if (Array.isArray(node)) {
32
+ for (const item of node) {
33
+ walk(item, depth + 1);
34
+ }
35
+ return;
36
+ }
37
+
38
+ for (const key of Object.keys(node)) {
39
+ if (key.startsWith('$')) {
40
+ const validOperators = [
41
+ '$where', '$ne', '$gt', '$lt', '$gte', '$lte', '$in', '$nin',
42
+ '$regex', '$expr', '$or', '$and', '$jsonSchema', '$function',
43
+ '$accumulator', '$not', '$nor', '$exists', '$type', '$all',
44
+ '$elemMatch', '$size', '$mod', '$options', '$text', '$search'
45
+ ];
46
+
47
+ if (validOperators.includes(key)) {
48
+ signals.push({
49
+ id: 'mongo-operator-key',
50
+ confidence: 0.80,
51
+ label: 'nosqli'
52
+ });
53
+ }
54
+ }
55
+
56
+ if (key.startsWith('_design/') || key === '_view' || key === 'map' || key === 'reduce') {
57
+ signals.push({
58
+ id: 'couchdb-injection',
59
+ confidence: 0.70,
60
+ label: 'nosqli'
61
+ });
62
+ }
63
+
64
+ if (key === 'script' && node[key] && typeof node[key] === 'object' && (node[key].inline || node[key].source)) {
65
+ signals.push({
66
+ id: 'es-script-injection',
67
+ confidence: 0.80,
68
+ label: 'nosqli'
69
+ });
70
+ }
71
+
72
+ walk(node[key], depth + 1);
73
+ }
74
+ }
75
+
76
+ walk(obj, 0);
77
+ return signals;
78
+ },
79
+
80
+ getSignals() {
81
+ return [
82
+ {
83
+ id: 'mongo-operator-value',
84
+ confidence: 0.85,
85
+ pattern: new RegExp(`[{,]\\s*["']?\\$(?:where|ne|gt|lt|gte|lte|in|nin|regex|expr|or|and|jsonSchema|function|accumulator)["']?\\s*:\\s*{\\s*["']?(?:${MONGO_OPERATORS})["']?\\s*:`, 'i')
86
+ },
87
+ {
88
+ id: 'mongo-operator-in-string',
89
+ confidence: 0.70,
90
+ pattern: new RegExp(`[{,]\\s*["'](?:${MONGO_OPERATORS})["']\\s*:`, 'i')
91
+ },
92
+ {
93
+ id: 'mongo-where-injection',
94
+ confidence: 0.80,
95
+ pattern: /(?:\$where["']?\s*:\s*["']|where\s*\(\s*["']|\$?where["']?\s*:\s*(?:function|=>))/i
96
+ },
97
+ {
98
+ id: 'mongo-regex-redos',
99
+ confidence: 0.75,
100
+ pattern: /\$regex["']?\s*:\s*["'].*(?:\(\.\*\)|\(\.\+\)|\(\.\*|\.\+\)|\(\?.*\)|\.\*.*(?:\.\*|\.\+)|\.\+.*(?:\.\*|\.\+)|\([a-zA-Z0-9]+\+\)\+).*["']/i
101
+ },
102
+ {
103
+ id: 'mongo-regex-general',
104
+ confidence: 0.65,
105
+ pattern: /\$regex["']?\s*:\s*["'].*[\+\*\{].*["']/i
106
+ },
107
+ {
108
+ id: 'mongo-mapreduce',
109
+ confidence: 0.75,
110
+ pattern: /(?:mapReduce|\$function|\$accumulator)["']?\s*:\s*(?:["']?function\s*\(|{)/i
111
+ },
112
+ {
113
+ id: 'mongo-aggregation-abuse',
114
+ confidence: 0.70,
115
+ pattern: /(?:\$lookup|\$graphLookup|\$out|\$merge|\$addFields)["']?\s*:/i
116
+ },
117
+ {
118
+ id: 'mongo-jsonschema',
119
+ confidence: 0.75,
120
+ pattern: /(?:\$jsonSchema)["']?\s*:\s*{/i
121
+ },
122
+ {
123
+ id: 'couchdb-injection',
124
+ confidence: 0.75,
125
+ pattern: /["']_design\/[^"']*["']\s*:\s*{|["']_view["']\s*:\s*["']/i
126
+ },
127
+ {
128
+ id: 'es-script-injection',
129
+ confidence: 0.75,
130
+ pattern: /["']script["']\s*:\s*(?:{|["'](?:painless|mvel|inline|ctx\._source))/i
131
+ }
132
+ ];
133
+ }
134
+ };
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const REDIRECT_PARAMS = [
4
+ 'redirect', 'url', 'next', 'return', 'returnUrl', 'continue', 'dest',
5
+ 'destination', 'goto', 'target', 'link', 'out', 'view', 'rurl',
6
+ 'return_url', 'callback', 'forward'
7
+ ].join('|');
8
+
9
+ module.exports = {
10
+ name: 'open-redirect',
11
+ label: 'open-redirect',
12
+
13
+ getSignals() {
14
+ return [
15
+ {
16
+ id: 'redirect-param-external-url',
17
+ confidence: 0.75,
18
+ pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=(?:https?:)?\\/\\/[^&]+`, 'i')
19
+ },
20
+ {
21
+ id: 'protocol-relative-redirect',
22
+ confidence: 0.70,
23
+ pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=\\/\\/[a-zA-Z0-9]`, 'i')
24
+ },
25
+ {
26
+ id: 'backslash-redirect',
27
+ confidence: 0.75,
28
+ pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=(?:\\\\\\/|\\/\\\\)[a-zA-Z0-9]`, 'i')
29
+ },
30
+ {
31
+ id: 'data-uri-redirect',
32
+ confidence: 0.80,
33
+ pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=data:`, 'i')
34
+ },
35
+ {
36
+ id: 'javascript-uri-redirect',
37
+ confidence: 0.85,
38
+ pattern: new RegExp(`(?:\\?|&)(?:${REDIRECT_PARAMS})=javascript:`, 'i')
39
+ }
40
+ ];
41
+ }
42
+ };
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'path-traversal',
5
+ label: 'path-traversal',
6
+ getSignals() {
7
+ return [
8
+ {
9
+ id: 'dot-dot-slash',
10
+ confidence: 0.75,
11
+ pattern: /(?:\.\.[\\\/]){2,}/
12
+ },
13
+ {
14
+ id: 'windows-backslash-traversal',
15
+ confidence: 0.75,
16
+ pattern: /(?:\.\.\\){2,}/
17
+ },
18
+ {
19
+ id: 'url-encoded-traversal',
20
+ confidence: 0.80,
21
+ pattern: /(?:%2e%2e%2f|%252e%252e%252f|%2e%2e%5c|%252e%252e%255c){2,}/i
22
+ },
23
+ {
24
+ id: 'overlong-utf8-traversal',
25
+ confidence: 0.85,
26
+ pattern: /(?:%c0%ae%c0%ae%c0%af|%e0%40%ae%e0%40%ae%e0%40%af){2,}/i
27
+ },
28
+ {
29
+ id: 'double-dot-slash',
30
+ confidence: 0.75,
31
+ pattern: /(?:\.\.\.\.\/\/)+/
32
+ },
33
+ {
34
+ id: 'null-byte-injection',
35
+ confidence: 0.85,
36
+ pattern: /\x00/
37
+ },
38
+ {
39
+ id: 'sensitive-unix-file',
40
+ confidence: 0.80,
41
+ pattern: /(?:\/etc\/passwd|\/etc\/shadow|\/proc\/self\/environ|\/proc\/self\/fd\/|\/var\/log\/)/i
42
+ },
43
+ {
44
+ id: 'sensitive-windows-file',
45
+ confidence: 0.80,
46
+ pattern: /(?:\\SAM|\\boot\.ini|\\win\.ini|\\system32\\config)/i
47
+ },
48
+ {
49
+ id: 'dotfile-access',
50
+ confidence: 0.65,
51
+ pattern: /(?:\.env|\.git\/|\.htaccess|\.DS_Store|\.ssh\/|\.aws\/credentials)/i
52
+ }
53
+ ];
54
+ }
55
+ };
@@ -0,0 +1,65 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ name: 'prototype-pollution',
5
+ label: 'prototype-pollution',
6
+
7
+ detectObject(obj, maxDepth = 20) {
8
+ const signals = [];
9
+ if (!obj || typeof obj !== 'object') return signals;
10
+
11
+ function walk(node, depth) {
12
+ if (depth > maxDepth) return;
13
+ if (!node || typeof node !== 'object') return;
14
+
15
+ if (Array.isArray(node)) {
16
+ for (const item of node) {
17
+ walk(item, depth + 1);
18
+ }
19
+ return;
20
+ }
21
+
22
+ for (const key of Object.keys(node)) {
23
+ if (key === '__proto__') {
24
+ signals.push({ id: 'proto-key', confidence: 0.90, label: 'prototype-pollution' });
25
+ } else if (key === 'constructor') {
26
+ signals.push({ id: 'constructor-key', confidence: 0.70, label: 'prototype-pollution' });
27
+ }
28
+ walk(node[key], depth + 1);
29
+ }
30
+ }
31
+
32
+ walk(obj, 0);
33
+ return signals;
34
+ },
35
+
36
+ getSignals() {
37
+ return [
38
+ {
39
+ id: 'proto-key',
40
+ confidence: 0.90,
41
+ pattern: /"__proto__"\s*:/
42
+ },
43
+ {
44
+ id: 'constructor-prototype',
45
+ confidence: 0.85,
46
+ pattern: /constructor\.prototype/
47
+ },
48
+ {
49
+ id: 'constructor-key',
50
+ confidence: 0.70,
51
+ pattern: /"constructor"\s*:/
52
+ },
53
+ {
54
+ id: 'proto-in-url',
55
+ confidence: 0.80,
56
+ pattern: /(?:\?|&)[^=]*__proto__/
57
+ },
58
+ {
59
+ id: 'prototype-in-brackets',
60
+ confidence: 0.80,
61
+ pattern: /\[(?:'|")?__proto__(?:'|")?\]|\[(?:'|")?constructor(?:'|")?\]\s*\[(?:'|")?prototype(?:'|")?\]/
62
+ }
63
+ ];
64
+ }
65
+ };