@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,99 @@
1
+ 'use strict';
2
+
3
+ const DEFAULT_OPTIONS = {
4
+ contentSecurityPolicy: {
5
+ directives: {
6
+ defaultSrc: ["'self'"]
7
+ }
8
+ },
9
+ xContentTypeOptions: 'nosniff',
10
+ xFrameOptions: 'DENY',
11
+ xXssProtection: '0',
12
+ hsts: { maxAge: 15552000, includeSubDomains: true },
13
+ referrerPolicy: 'no-referrer',
14
+ permissionsPolicy: 'camera=(), microphone=(), geolocation=()',
15
+ xDnsPrefetchControl: 'off',
16
+ xPermittedCrossDomainPolicies: 'none',
17
+ crossOriginOpenerPolicy: 'same-origin',
18
+ crossOriginResourcePolicy: 'same-origin',
19
+ crossOriginEmbedderPolicy: 'require-corp'
20
+ };
21
+
22
+ function buildCspString(directives) {
23
+ if (!directives || typeof directives !== 'object') return '';
24
+ const parts = [];
25
+ for (const [key, value] of Object.entries(directives)) {
26
+ const directiveName = key.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
27
+ const directiveValue = Array.isArray(value) ? value.join(' ') : value;
28
+ parts.push(`${directiveName} ${directiveValue}`);
29
+ }
30
+ return parts.join('; ');
31
+ }
32
+
33
+ function buildHstsString(options) {
34
+ if (typeof options === 'string') return options;
35
+ let str = `max-age=${options.maxAge || 15552000}`;
36
+ if (options.includeSubDomains) str += '; includeSubDomains';
37
+ if (options.preload) str += '; preload';
38
+ return str;
39
+ }
40
+
41
+ /**
42
+ * Creates the Security Headers shield middleware
43
+ * @param {Object} options - Shield configuration
44
+ * @returns {Function} middleware(req, res, next)
45
+ */
46
+ module.exports = function createHeadersShield(options = {}) {
47
+ const config = { ...DEFAULT_OPTIONS, ...options };
48
+
49
+ let cspString = '';
50
+ if (config.contentSecurityPolicy !== false) {
51
+ cspString = buildCspString(config.contentSecurityPolicy?.directives || config.contentSecurityPolicy);
52
+ }
53
+
54
+ let hstsString = '';
55
+ if (config.hsts !== false) {
56
+ hstsString = buildHstsString(config.hsts);
57
+ }
58
+
59
+ return function headersShield(req, res, next) {
60
+ if (config.contentSecurityPolicy !== false && cspString) {
61
+ res.setHeader('Content-Security-Policy', cspString);
62
+ }
63
+ if (config.xContentTypeOptions !== false) {
64
+ res.setHeader('X-Content-Type-Options', config.xContentTypeOptions || DEFAULT_OPTIONS.xContentTypeOptions);
65
+ }
66
+ if (config.xFrameOptions !== false) {
67
+ res.setHeader('X-Frame-Options', config.frameguard || config.xFrameOptions || DEFAULT_OPTIONS.xFrameOptions);
68
+ }
69
+ if (config.xXssProtection !== false) {
70
+ res.setHeader('X-XSS-Protection', config.xXssProtection || DEFAULT_OPTIONS.xXssProtection);
71
+ }
72
+ if (config.hsts !== false && hstsString) {
73
+ res.setHeader('Strict-Transport-Security', hstsString);
74
+ }
75
+ if (config.referrerPolicy !== false) {
76
+ res.setHeader('Referrer-Policy', config.referrerPolicy || DEFAULT_OPTIONS.referrerPolicy);
77
+ }
78
+ if (config.permissionsPolicy !== false) {
79
+ res.setHeader('Permissions-Policy', config.permissionsPolicy || DEFAULT_OPTIONS.permissionsPolicy);
80
+ }
81
+ if (config.xDnsPrefetchControl !== false) {
82
+ res.setHeader('X-DNS-Prefetch-Control', config.xDnsPrefetchControl || DEFAULT_OPTIONS.xDnsPrefetchControl);
83
+ }
84
+ if (config.xPermittedCrossDomainPolicies !== false) {
85
+ res.setHeader('X-Permitted-Cross-Domain-Policies', config.xPermittedCrossDomainPolicies || DEFAULT_OPTIONS.xPermittedCrossDomainPolicies);
86
+ }
87
+ if (config.crossOriginOpenerPolicy !== false) {
88
+ res.setHeader('Cross-Origin-Opener-Policy', config.crossOriginOpenerPolicy || DEFAULT_OPTIONS.crossOriginOpenerPolicy);
89
+ }
90
+ if (config.crossOriginResourcePolicy !== false) {
91
+ res.setHeader('Cross-Origin-Resource-Policy', config.crossOriginResourcePolicy || DEFAULT_OPTIONS.crossOriginResourcePolicy);
92
+ }
93
+ if (config.crossOriginEmbedderPolicy !== false) {
94
+ res.setHeader('Cross-Origin-Embedder-Policy', config.crossOriginEmbedderPolicy || DEFAULT_OPTIONS.crossOriginEmbedderPolicy);
95
+ }
96
+
97
+ next();
98
+ };
99
+ };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+ class IPRateLimiter {
3
+ constructor(windowMs = 300000, maxCapacity = 10000, maxEventsPerKey = 1000) {
4
+ this.windowMs = Number.isFinite(windowMs) && windowMs > 0 ? windowMs : 300000;
5
+ this.maxCapacity = Number.isFinite(maxCapacity) && maxCapacity > 0 ? maxCapacity : 10000;
6
+ this.maxEventsPerKey = Number.isFinite(maxEventsPerKey) && maxEventsPerKey > 0 ? maxEventsPerKey : 1000;
7
+ this.ips = new Map();
8
+ }
9
+
10
+ pruneExpired(now) {
11
+ for (const [ip, timestamps] of this.ips) {
12
+ const validTimestamps = timestamps.filter(t => now - t < this.windowMs);
13
+ if (validTimestamps.length === 0) {
14
+ this.ips.delete(ip);
15
+ } else {
16
+ this.ips.set(ip, validTimestamps);
17
+ }
18
+ }
19
+ }
20
+
21
+ recordSuspicious(ip) {
22
+ const now = Date.now();
23
+ if (!this.ips.has(ip)) {
24
+ if (this.ips.size >= this.maxCapacity) {
25
+ this.pruneExpired(now);
26
+ }
27
+ if (this.ips.size >= this.maxCapacity) {
28
+ this.ips.delete(this.ips.keys().next().value);
29
+ }
30
+ this.ips.set(ip, []);
31
+ }
32
+ const timestamps = this.ips.get(ip);
33
+
34
+ // Cleanup old timestamps for this IP
35
+ const validTimestamps = timestamps.filter(t => now - t < this.windowMs);
36
+ validTimestamps.push(now);
37
+ if (validTimestamps.length > this.maxEventsPerKey) {
38
+ validTimestamps.splice(0, validTimestamps.length - this.maxEventsPerKey);
39
+ }
40
+ this.ips.delete(ip);
41
+ this.ips.set(ip, validTimestamps);
42
+ return validTimestamps.length;
43
+ }
44
+
45
+ }
46
+
47
+ function rateLimiterFactory(options = {}) {
48
+ const windowMs = options.windowMs || 15 * 60 * 1000;
49
+ const max = options.max || 100;
50
+ const limiter = new IPRateLimiter(windowMs, 10000, max);
51
+
52
+ return function rateLimitMiddleware(req, res, next) {
53
+ const ip = req.ip || (req.connection && req.connection.remoteAddress) || 'unknown';
54
+ const currentHits = limiter.recordSuspicious(ip);
55
+
56
+ if (currentHits >= max) {
57
+ if (res.status && res.json) {
58
+ res.status(429).json({ error: 'Too many requests' });
59
+ } else {
60
+ // Fallback for non-Express adapters if needed
61
+ res.statusCode = 429;
62
+ res.end(JSON.stringify({ error: 'Too many requests' }));
63
+ }
64
+ return;
65
+ }
66
+ next();
67
+ };
68
+ }
69
+
70
+ module.exports = { IPRateLimiter, rateLimiterFactory };