@korayda4/vibe-security 0.3.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/LICENSE +21 -0
- package/README.md +240 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +333 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine/baseline.d.ts +19 -0
- package/dist/engine/baseline.js +48 -0
- package/dist/engine/baseline.js.map +1 -0
- package/dist/engine/config.d.ts +26 -0
- package/dist/engine/config.js +41 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/fixer.d.ts +22 -0
- package/dist/engine/fixer.js +87 -0
- package/dist/engine/fixer.js.map +1 -0
- package/dist/engine/language.d.ts +27 -0
- package/dist/engine/language.js +364 -0
- package/dist/engine/language.js.map +1 -0
- package/dist/engine/output.d.ts +6 -0
- package/dist/engine/output.js +155 -0
- package/dist/engine/output.js.map +1 -0
- package/dist/engine/reporter.d.ts +3 -0
- package/dist/engine/reporter.js +145 -0
- package/dist/engine/reporter.js.map +1 -0
- package/dist/engine/rules/_helpers.d.ts +9 -0
- package/dist/engine/rules/_helpers.js +64 -0
- package/dist/engine/rules/_helpers.js.map +1 -0
- package/dist/engine/rules/backend/fileUpload.d.ts +3 -0
- package/dist/engine/rules/backend/fileUpload.js +40 -0
- package/dist/engine/rules/backend/fileUpload.js.map +1 -0
- package/dist/engine/rules/backend/hashing.d.ts +3 -0
- package/dist/engine/rules/backend/hashing.js +40 -0
- package/dist/engine/rules/backend/hashing.js.map +1 -0
- package/dist/engine/rules/backend/idor.d.ts +3 -0
- package/dist/engine/rules/backend/idor.js +40 -0
- package/dist/engine/rules/backend/idor.js.map +1 -0
- package/dist/engine/rules/backend/index.d.ts +12 -0
- package/dist/engine/rules/backend/index.js +22 -0
- package/dist/engine/rules/backend/index.js.map +1 -0
- package/dist/engine/rules/backend/inputValidation.d.ts +3 -0
- package/dist/engine/rules/backend/inputValidation.js +37 -0
- package/dist/engine/rules/backend/inputValidation.js.map +1 -0
- package/dist/engine/rules/backend/jwt.d.ts +3 -0
- package/dist/engine/rules/backend/jwt.js +42 -0
- package/dist/engine/rules/backend/jwt.js.map +1 -0
- package/dist/engine/rules/backend/rateLimit.d.ts +3 -0
- package/dist/engine/rules/backend/rateLimit.js +45 -0
- package/dist/engine/rules/backend/rateLimit.js.map +1 -0
- package/dist/engine/rules/backend/sqlInjection.d.ts +3 -0
- package/dist/engine/rules/backend/sqlInjection.js +41 -0
- package/dist/engine/rules/backend/sqlInjection.js.map +1 -0
- package/dist/engine/rules/backend/tokenExpiry.d.ts +3 -0
- package/dist/engine/rules/backend/tokenExpiry.js +85 -0
- package/dist/engine/rules/backend/tokenExpiry.js.map +1 -0
- package/dist/engine/rules/backend/tokenInUrl.d.ts +3 -0
- package/dist/engine/rules/backend/tokenInUrl.js +112 -0
- package/dist/engine/rules/backend/tokenInUrl.js.map +1 -0
- package/dist/engine/rules/cicd/dependencies.d.ts +3 -0
- package/dist/engine/rules/cicd/dependencies.js +44 -0
- package/dist/engine/rules/cicd/dependencies.js.map +1 -0
- package/dist/engine/rules/cicd/dockerfile.d.ts +3 -0
- package/dist/engine/rules/cicd/dockerfile.js +42 -0
- package/dist/engine/rules/cicd/dockerfile.js.map +1 -0
- package/dist/engine/rules/cicd/index.d.ts +6 -0
- package/dist/engine/rules/cicd/index.js +10 -0
- package/dist/engine/rules/cicd/index.js.map +1 -0
- package/dist/engine/rules/cicd/secrets.d.ts +3 -0
- package/dist/engine/rules/cicd/secrets.js +41 -0
- package/dist/engine/rules/cicd/secrets.js.map +1 -0
- package/dist/engine/rules/database/encryption.d.ts +3 -0
- package/dist/engine/rules/database/encryption.js +37 -0
- package/dist/engine/rules/database/encryption.js.map +1 -0
- package/dist/engine/rules/database/index.d.ts +5 -0
- package/dist/engine/rules/database/index.js +8 -0
- package/dist/engine/rules/database/index.js.map +1 -0
- package/dist/engine/rules/database/privileges.d.ts +3 -0
- package/dist/engine/rules/database/privileges.js +40 -0
- package/dist/engine/rules/database/privileges.js.map +1 -0
- package/dist/engine/rules/frontend/clickjacking.d.ts +3 -0
- package/dist/engine/rules/frontend/clickjacking.js +41 -0
- package/dist/engine/rules/frontend/clickjacking.js.map +1 -0
- package/dist/engine/rules/frontend/cookies.d.ts +3 -0
- package/dist/engine/rules/frontend/cookies.js +35 -0
- package/dist/engine/rules/frontend/cookies.js.map +1 -0
- package/dist/engine/rules/frontend/envVars.d.ts +3 -0
- package/dist/engine/rules/frontend/envVars.js +34 -0
- package/dist/engine/rules/frontend/envVars.js.map +1 -0
- package/dist/engine/rules/frontend/eval.d.ts +3 -0
- package/dist/engine/rules/frontend/eval.js +35 -0
- package/dist/engine/rules/frontend/eval.js.map +1 -0
- package/dist/engine/rules/frontend/index.d.ts +9 -0
- package/dist/engine/rules/frontend/index.js +16 -0
- package/dist/engine/rules/frontend/index.js.map +1 -0
- package/dist/engine/rules/frontend/sourceMaps.d.ts +3 -0
- package/dist/engine/rules/frontend/sourceMaps.js +79 -0
- package/dist/engine/rules/frontend/sourceMaps.js.map +1 -0
- package/dist/engine/rules/frontend/xss.d.ts +3 -0
- package/dist/engine/rules/frontend/xss.js +70 -0
- package/dist/engine/rules/frontend/xss.js.map +1 -0
- package/dist/engine/rules/index.d.ts +8 -0
- package/dist/engine/rules/index.js +38 -0
- package/dist/engine/rules/index.js.map +1 -0
- package/dist/engine/rules/kotlin/index.d.ts +5 -0
- package/dist/engine/rules/kotlin/index.js +8 -0
- package/dist/engine/rules/kotlin/index.js.map +1 -0
- package/dist/engine/rules/kotlin/kotlinxHtml.d.ts +3 -0
- package/dist/engine/rules/kotlin/kotlinxHtml.js +54 -0
- package/dist/engine/rules/kotlin/kotlinxHtml.js.map +1 -0
- package/dist/engine/rules/kotlin/sqlInjection.d.ts +3 -0
- package/dist/engine/rules/kotlin/sqlInjection.js +57 -0
- package/dist/engine/rules/kotlin/sqlInjection.js.map +1 -0
- package/dist/engine/rules/network/cors.d.ts +3 -0
- package/dist/engine/rules/network/cors.js +106 -0
- package/dist/engine/rules/network/cors.js.map +1 -0
- package/dist/engine/rules/network/headers.d.ts +3 -0
- package/dist/engine/rules/network/headers.js +51 -0
- package/dist/engine/rules/network/headers.js.map +1 -0
- package/dist/engine/rules/network/index.d.ts +6 -0
- package/dist/engine/rules/network/index.js +10 -0
- package/dist/engine/rules/network/index.js.map +1 -0
- package/dist/engine/rules/network/tls.d.ts +3 -0
- package/dist/engine/rules/network/tls.js +40 -0
- package/dist/engine/rules/network/tls.js.map +1 -0
- package/dist/engine/rules/observability/errorHandler.d.ts +3 -0
- package/dist/engine/rules/observability/errorHandler.js +34 -0
- package/dist/engine/rules/observability/errorHandler.js.map +1 -0
- package/dist/engine/rules/observability/index.d.ts +5 -0
- package/dist/engine/rules/observability/index.js +8 -0
- package/dist/engine/rules/observability/index.js.map +1 -0
- package/dist/engine/rules/observability/logging.d.ts +3 -0
- package/dist/engine/rules/observability/logging.js +38 -0
- package/dist/engine/rules/observability/logging.js.map +1 -0
- package/dist/engine/rules/php/index.d.ts +6 -0
- package/dist/engine/rules/php/index.js +10 -0
- package/dist/engine/rules/php/index.js.map +1 -0
- package/dist/engine/rules/php/sqlInjection.d.ts +3 -0
- package/dist/engine/rules/php/sqlInjection.js +58 -0
- package/dist/engine/rules/php/sqlInjection.js.map +1 -0
- package/dist/engine/rules/php/weakHashing.d.ts +3 -0
- package/dist/engine/rules/php/weakHashing.js +36 -0
- package/dist/engine/rules/php/weakHashing.js.map +1 -0
- package/dist/engine/rules/php/xss.d.ts +3 -0
- package/dist/engine/rules/php/xss.js +56 -0
- package/dist/engine/rules/php/xss.js.map +1 -0
- package/dist/engine/rules/ruby/index.d.ts +6 -0
- package/dist/engine/rules/ruby/index.js +10 -0
- package/dist/engine/rules/ruby/index.js.map +1 -0
- package/dist/engine/rules/ruby/sqlInjection.d.ts +3 -0
- package/dist/engine/rules/ruby/sqlInjection.js +56 -0
- package/dist/engine/rules/ruby/sqlInjection.js.map +1 -0
- package/dist/engine/rules/ruby/strongParams.d.ts +3 -0
- package/dist/engine/rules/ruby/strongParams.js +35 -0
- package/dist/engine/rules/ruby/strongParams.js.map +1 -0
- package/dist/engine/rules/ruby/xss.d.ts +3 -0
- package/dist/engine/rules/ruby/xss.js +55 -0
- package/dist/engine/rules/ruby/xss.js.map +1 -0
- package/dist/engine/rules/rust/index.d.ts +6 -0
- package/dist/engine/rules/rust/index.js +10 -0
- package/dist/engine/rules/rust/index.js.map +1 -0
- package/dist/engine/rules/rust/secretInCode.d.ts +3 -0
- package/dist/engine/rules/rust/secretInCode.js +36 -0
- package/dist/engine/rules/rust/secretInCode.js.map +1 -0
- package/dist/engine/rules/rust/sqlInjection.d.ts +3 -0
- package/dist/engine/rules/rust/sqlInjection.js +55 -0
- package/dist/engine/rules/rust/sqlInjection.js.map +1 -0
- package/dist/engine/rules/rust/unsafeExpect.d.ts +3 -0
- package/dist/engine/rules/rust/unsafeExpect.js +56 -0
- package/dist/engine/rules/rust/unsafeExpect.js.map +1 -0
- package/dist/engine/scanner.d.ts +5 -0
- package/dist/engine/scanner.js +135 -0
- package/dist/engine/scanner.js.map +1 -0
- package/dist/engine/walker.d.ts +11 -0
- package/dist/engine/walker.js +90 -0
- package/dist/engine/walker.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +36 -0
- package/dist/server.js +360 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +92 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +85 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engine/rules/database/index.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,UAAU,MAAM,iBAAiB,CAAC;AAEzC,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,UAAU;IACV,UAAU;CACX,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { grepRule } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'DB-001',
|
|
4
|
+
title: 'Overly privileged database user (DROP, ALTER, GRANT granted to app user)',
|
|
5
|
+
layer: 'database',
|
|
6
|
+
severity: 'high',
|
|
7
|
+
description: 'Uygulama DB baglanti kullanicisi DROP, ALTER, GRANT veya superuser yetkilerine sahipse, SQL injection durumunda saldırıgın tum veritabanini silmesi veya sema degistirmesi mumkun.',
|
|
8
|
+
threat: 'Mass data destruction, ransomware-grade attack, full DB compromise',
|
|
9
|
+
remediation: '1) App DB user\'ina SADECE gereken yetkileri verin (SELECT, INSERT, UPDATE, DELETE). ' +
|
|
10
|
+
'2) DROP, ALTER, GRANT, CREATE yetkilerini vermeyin. ' +
|
|
11
|
+
'3) Migration\'lar icin ayri bir migration user (CI/CD\'de kullanilan, app\'te OLMAYAN). ' +
|
|
12
|
+
'4) Postgres: `ALTER USER app_user NOSUPERUSER NOINHERIT NOCREATEDB NOCREATEROLE NOREPLICATION`. ' +
|
|
13
|
+
'5) Read-only endpointler icin ayri read-only user kullanin. ' +
|
|
14
|
+
'6) Baglanti string\'inde `superuser=yes` veya `root` KULLANMAYIN.',
|
|
15
|
+
references: [
|
|
16
|
+
'https://en.wikipedia.org/wiki/Principle_of_least_privilege',
|
|
17
|
+
'https://www.postgresql.org/docs/current/sql-grant.html',
|
|
18
|
+
'https://cheatsheetseries.owasp.org/cheatsheets/Database_Security_Cheat_Sheet.html',
|
|
19
|
+
],
|
|
20
|
+
cwe: 'CWE-269',
|
|
21
|
+
owasp: 'A01:2021 Broken Access Control',
|
|
22
|
+
languages: ['javascript', 'typescript', 'python', 'java', 'go', 'yaml'],
|
|
23
|
+
check: (ctx) => {
|
|
24
|
+
const patterns = [
|
|
25
|
+
/GRANT\s+(ALL|ALL\s+PRIVILEGES|SUPERUSER|CREATE|DROP|ALTER)\b/gi,
|
|
26
|
+
/SUPERUSER\b/gi,
|
|
27
|
+
/NOSUPERUSER\s*=\s*false/gi,
|
|
28
|
+
/(?:postgres|mysql|mariadb):\/\/[^:]+:([^@]+)@[^\/]+\/(?:[^?]+\?)?[^"\s]*superuser/gi,
|
|
29
|
+
/(?:postgres|mysql|mariadb):\/\/[^:]+:([^@]+)@[^\/]+\/(?:[^?]+\?)?[^"\s]*root/gi,
|
|
30
|
+
/GRANT\s+OPTION/gi,
|
|
31
|
+
];
|
|
32
|
+
const findings = [];
|
|
33
|
+
for (const re of patterns) {
|
|
34
|
+
findings.push(...grepRule(rule, ctx, re));
|
|
35
|
+
}
|
|
36
|
+
return findings;
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
export default rule;
|
|
40
|
+
//# sourceMappingURL=privileges.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"privileges.js","sourceRoot":"","sources":["../../../../src/engine/rules/database/privileges.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,0EAA0E;IACjF,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,MAAM;IAChB,WAAW,EACT,oLAAoL;IACtL,MAAM,EAAE,oEAAoE;IAC5E,WAAW,EACT,uFAAuF;QACvF,sDAAsD;QACtD,0FAA0F;QAC1F,kGAAkG;QAClG,8DAA8D;QAC9D,mEAAmE;IACrE,UAAU,EAAE;QACV,4DAA4D;QAC5D,wDAAwD;QACxD,mFAAmF;KACpF;IACD,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,gCAAgC;IACvC,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC;IACvE,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAa;YAC/B,gEAAgE;YAC1D,eAAe;YACf,2BAA2B;YACjC,qFAAqF;YAC/E,gFAAgF;YAChF,kBAAkB;SACnB,CAAC;QAEF,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { grepRule } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-005',
|
|
4
|
+
title: 'Missing clickjacking protection (X-Frame-Options / CSP frame-ancestors)',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'medium',
|
|
7
|
+
description: 'HTTP response\'unda `X-Frame-Options` veya `Content-Security-Policy: frame-ancestors` direktifi yoksa sayfa saldirgan kontrolundeki bir iframe icinde acilabilir. UI Redressing (clickjacking) ile kullanici istemedigi islemleri yaptirilabilir.',
|
|
8
|
+
threat: 'UI redressing, clickjacking → unauthorized clicks (payment, settings change)',
|
|
9
|
+
remediation: 'Tum HTML response\'larina sunlardan birini ekleyin: ' +
|
|
10
|
+
'`Content-Security-Policy: frame-ancestors \'none\'` (en guclu, modern), veya ' +
|
|
11
|
+
'`X-Frame-Options: DENY` (eski browser destegi). ' +
|
|
12
|
+
'Sadece kendi origin\'inizden iframe kabul edecekseniz: `frame-ancestors \'self\'`. ' +
|
|
13
|
+
'Next.js: middleware veya `next.config.js` headers\'tan ayarlayin. Nginx: `add_header X-Frame-Options "DENY" always;`.',
|
|
14
|
+
references: [
|
|
15
|
+
'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options',
|
|
16
|
+
'https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/frame-ancestors',
|
|
17
|
+
'https://owasp.org/www-community/attacks/Clickjacking',
|
|
18
|
+
],
|
|
19
|
+
cwe: 'CWE-1021',
|
|
20
|
+
owasp: 'A05:2021 Security Misconfiguration',
|
|
21
|
+
languages: ['javascript', 'typescript'],
|
|
22
|
+
check: (ctx) => {
|
|
23
|
+
const isConfig = /next\.config\.(js|mjs|ts)$/i.test(ctx.relativePath) ||
|
|
24
|
+
/vercel\.json$/i.test(ctx.relativePath) ||
|
|
25
|
+
/_headers$/i.test(ctx.relativePath) ||
|
|
26
|
+
/nginx\.conf$/i.test(ctx.relativePath) ||
|
|
27
|
+
/server\.ts$/i.test(ctx.relativePath) ||
|
|
28
|
+
/server\.js$/i.test(ctx.relativePath);
|
|
29
|
+
if (!isConfig)
|
|
30
|
+
return [];
|
|
31
|
+
const hasFrameAncestors = /frame-ancestors\s+/i.test(ctx.source);
|
|
32
|
+
const hasXFrameOptions = /X-Frame-Options\s*:/i.test(ctx.source);
|
|
33
|
+
if (hasFrameAncestors || hasXFrameOptions)
|
|
34
|
+
return [];
|
|
35
|
+
const findings = [];
|
|
36
|
+
findings.push(...grepRule(rule, ctx, /\basync\s+headers\s*\(/g));
|
|
37
|
+
return findings;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export default rule;
|
|
41
|
+
//# sourceMappingURL=clickjacking.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clickjacking.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/clickjacking.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,yEAAyE;IAChF,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,QAAQ;IAClB,WAAW,EACT,mPAAmP;IACrP,MAAM,EAAE,8EAA8E;IACtF,WAAW,EACT,sDAAsD;QACtD,+EAA+E;QAC/E,kDAAkD;QAClD,qFAAqF;QACrF,uHAAuH;IACzH,UAAU,EAAE;QACV,2EAA2E;QAC3E,mGAAmG;QACnG,sDAAsD;KACvD;IACD,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,oCAAoC;IAC3C,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GACZ,6BAA6B,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACpD,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACvC,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACnC,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACtC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC;YACrC,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAExC,IAAI,CAAC,QAAQ;YAAE,OAAO,EAAE,CAAC;QAEzB,MAAM,iBAAiB,GAAG,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACjE,MAAM,gBAAgB,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEjE,IAAI,iBAAiB,IAAI,gBAAgB;YAAE,OAAO,EAAE,CAAC;QAErD,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,yBAAyB,CAAC,CAAC,CAAC;QACjE,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { grepRule } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-002',
|
|
4
|
+
title: 'Insecure cookie attributes (HttpOnly / Secure / SameSite)',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'high',
|
|
7
|
+
description: 'JWT, session, or sensitive tokens set via document.cookie or via Set-Cookie without HttpOnly / Secure / SameSite flags can be stolen via XSS or MITM.',
|
|
8
|
+
threat: 'Session hijacking, token theft, account takeover',
|
|
9
|
+
remediation: 'Set sensitive cookies server-side with HttpOnly (blocks JS), Secure (HTTPS-only), SameSite=Lax or Strict (CSRF mitigation). ' +
|
|
10
|
+
'Store tokens in server-side cookies, not localStorage or sessionStorage. ' +
|
|
11
|
+
"Avoid document.cookie for reading or writing tokens in the frontend.",
|
|
12
|
+
references: [
|
|
13
|
+
'https://owasp.org/www-community/HttpOnly',
|
|
14
|
+
'https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies',
|
|
15
|
+
'https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html',
|
|
16
|
+
],
|
|
17
|
+
cwe: 'CWE-1004',
|
|
18
|
+
owasp: 'A07:2021 Identification and Authentication Failures',
|
|
19
|
+
languages: ['javascript', 'typescript'],
|
|
20
|
+
check: (ctx) => {
|
|
21
|
+
const findings = [];
|
|
22
|
+
const patterns = [
|
|
23
|
+
/document\.cookie\s*=/g,
|
|
24
|
+
/Set-Cookie\s*:\s*[^;\n]+(?![;\n]*HttpOnly)/gi,
|
|
25
|
+
/localStorage\.setItem\s*\(\s*['"][^'"]*(token|jwt|session|auth|api[_-]?key)/gi,
|
|
26
|
+
/sessionStorage\.setItem\s*\(\s*['"][^'"]*(token|jwt|session|auth|api[_-]?key)/gi,
|
|
27
|
+
];
|
|
28
|
+
for (const re of patterns) {
|
|
29
|
+
findings.push(...grepRule(rule, ctx, re));
|
|
30
|
+
}
|
|
31
|
+
return findings;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export default rule;
|
|
35
|
+
//# sourceMappingURL=cookies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cookies.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/cookies.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,2DAA2D;IAClE,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,MAAM;IAChB,WAAW,EACT,uJAAuJ;IACzJ,MAAM,EAAE,kDAAkD;IAC1D,WAAW,EACT,8HAA8H;QAC9H,2EAA2E;QAC3E,sEAAsE;IACxE,UAAU,EAAE;QACV,0CAA0C;QAC1C,2DAA2D;QAC3D,oFAAoF;KACrF;IACD,GAAG,EAAE,UAAU;IACf,KAAK,EAAE,qDAAqD;IAC5D,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAa;YACzB,uBAAuB;YACvB,8CAA8C;YAC9C,+EAA+E;YAC/E,iFAAiF;SAClF,CAAC;QAEF,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { grepRule } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-003',
|
|
4
|
+
title: 'Sensitive value exposed via public env prefix',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'critical',
|
|
7
|
+
description: 'Public env prefixes (NEXT_PUBLIC_, REACT_APP_, VITE_, GATSBY_, EXPO_PUBLIC_) are bundled into the client at build time. Any secret read via these prefixes (API key, DB connection string) is exposed to every visitor.',
|
|
8
|
+
threat: 'Secret / API key disclosure, full backend compromise',
|
|
9
|
+
remediation: '1) Do NOT define sensitive keys with public prefixes — use API_KEY instead of NEXT_PUBLIC_API_KEY. ' +
|
|
10
|
+
'2) Public env should only carry public data (API URL, feature flags). ' +
|
|
11
|
+
"3) Read secrets server-side via process.env.SECRET_NAME in route / API handlers. " +
|
|
12
|
+
'4) Wire gitleaks or trufflehog as a pre-commit hook in CI.',
|
|
13
|
+
references: [
|
|
14
|
+
'https://nextjs.org/docs/basic-features/environment-variables#exposing-environment-variables-to-the-browser',
|
|
15
|
+
'https://create-react-app.dev/docs/adding-custom-environment-variables/',
|
|
16
|
+
'https://vitejs.dev/guide/env-and-mode.html',
|
|
17
|
+
],
|
|
18
|
+
cwe: 'CWE-200',
|
|
19
|
+
owasp: 'A05:2021 Security Misconfiguration',
|
|
20
|
+
languages: ['javascript', 'typescript'],
|
|
21
|
+
check: (ctx) => {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const patterns = [
|
|
24
|
+
/^(NEXT_PUBLIC|REACT_APP|VITE_|GATSBY_|EXPO_PUBLIC_)[A-Z0-9_]*(SECRET|KEY|TOKEN|PASSWORD|API[_-]?KEY|PRIVATE|CREDENTIAL|AUTH)/gim,
|
|
25
|
+
/process\.env\.(NEXT_PUBLIC|REACT_APP|VITE_|GATSBY_|EXPO_PUBLIC_)[A-Z0-9_]*(SECRET|KEY|TOKEN|PASSWORD|API[_-]?KEY|PRIVATE|CREDENTIAL|AUTH)/g,
|
|
26
|
+
];
|
|
27
|
+
for (const re of patterns) {
|
|
28
|
+
findings.push(...grepRule(rule, ctx, re));
|
|
29
|
+
}
|
|
30
|
+
return findings;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
export default rule;
|
|
34
|
+
//# sourceMappingURL=envVars.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"envVars.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/envVars.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,+CAA+C;IACtD,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,UAAU;IACpB,WAAW,EACT,yNAAyN;IAC3N,MAAM,EAAE,sDAAsD;IAC9D,WAAW,EACT,qGAAqG;QACrG,wEAAwE;QACxE,mFAAmF;QACnF,4DAA4D;IAC9D,UAAU,EAAE;QACV,4GAA4G;QAC5G,wEAAwE;QACxE,4CAA4C;KAC7C;IACD,GAAG,EAAE,SAAS;IACd,KAAK,EAAE,oCAAoC;IAC3C,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAa;YACzB,iIAAiI;YACjI,4IAA4I;SAC7I,CAAC;QAEF,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { grepRule } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-006',
|
|
4
|
+
title: 'Dangerous dynamic code execution (eval / Function / setTimeout string)',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'high',
|
|
7
|
+
description: '`eval()`, `new Function()`, `setTimeout(string, ...)` veya `setInterval(string, ...)` ile dinamik kod calistirmak, kullanici girdisi kontrolsuzce yurutulurse RCE veya XSS ile sonuclanabilir.',
|
|
8
|
+
threat: 'Remote code execution, XSS, injection chain',
|
|
9
|
+
remediation: 'Dinamik koda ihtiyac varsa JSON.parse veya guvenli bir DSL parser kullanin. ' +
|
|
10
|
+
'`eval`, `Function` constructor\'dan tamamen kacinin. ' +
|
|
11
|
+
'`setTimeout` / `setInterval`\'a her zaman fonksiyon referansi verin, string vermeyin. ' +
|
|
12
|
+
'CSP header\'larinda `unsafe-eval` eklemeyin.',
|
|
13
|
+
references: [
|
|
14
|
+
'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!',
|
|
15
|
+
'https://owasp.org/www-community/attacks/Code_Injection',
|
|
16
|
+
],
|
|
17
|
+
cwe: 'CWE-95',
|
|
18
|
+
owasp: 'A03:2021 Injection',
|
|
19
|
+
languages: ['javascript', 'typescript'],
|
|
20
|
+
check: (ctx) => {
|
|
21
|
+
const patterns = [
|
|
22
|
+
/\beval\s*\(/g,
|
|
23
|
+
/\bnew\s+Function\s*\(/g,
|
|
24
|
+
/setTimeout\s*\(\s*['"`]/g,
|
|
25
|
+
/setInterval\s*\(\s*['"`]/g,
|
|
26
|
+
];
|
|
27
|
+
const findings = [];
|
|
28
|
+
for (const re of patterns) {
|
|
29
|
+
findings.push(...grepRule(rule, ctx, re));
|
|
30
|
+
}
|
|
31
|
+
return findings;
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export default rule;
|
|
35
|
+
//# sourceMappingURL=eval.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eval.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/eval.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAE1C,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,wEAAwE;IAC/E,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,MAAM;IAChB,WAAW,EACT,gMAAgM;IAClM,MAAM,EAAE,6CAA6C;IACrD,WAAW,EACT,8EAA8E;QAC9E,uDAAuD;QACvD,wFAAwF;QACxF,8CAA8C;IAChD,UAAU,EAAE;QACV,uGAAuG;QACvG,wDAAwD;KACzD;IACD,GAAG,EAAE,QAAQ;IACb,KAAK,EAAE,oBAAoB;IAC3B,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAa;YACzB,cAAc;YACd,wBAAwB;YACxB,0BAA0B;YAC1B,2BAA2B;SAC5B,CAAC;QAEF,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,QAAQ,CAAC,IAAI,CAAC,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const rules: {
|
|
2
|
+
xss: import("../../../types.js").Rule;
|
|
3
|
+
cookies: import("../../../types.js").Rule;
|
|
4
|
+
envVars: import("../../../types.js").Rule;
|
|
5
|
+
sourceMaps: import("../../../types.js").Rule;
|
|
6
|
+
clickjacking: import("../../../types.js").Rule;
|
|
7
|
+
eval: import("../../../types.js").Rule;
|
|
8
|
+
};
|
|
9
|
+
export default rules;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import xss from './xss.js';
|
|
2
|
+
import cookies from './cookies.js';
|
|
3
|
+
import envVars from './envVars.js';
|
|
4
|
+
import sourceMaps from './sourceMaps.js';
|
|
5
|
+
import clickjacking from './clickjacking.js';
|
|
6
|
+
import evalRule from './eval.js';
|
|
7
|
+
export const rules = {
|
|
8
|
+
xss,
|
|
9
|
+
cookies,
|
|
10
|
+
envVars,
|
|
11
|
+
sourceMaps,
|
|
12
|
+
clickjacking,
|
|
13
|
+
eval: evalRule,
|
|
14
|
+
};
|
|
15
|
+
export default rules;
|
|
16
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/index.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,UAAU,CAAC;AAC3B,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,UAAU,MAAM,iBAAiB,CAAC;AACzC,OAAO,YAAY,MAAM,mBAAmB,CAAC;AAC7C,OAAO,QAAQ,MAAM,WAAW,CAAC;AAEjC,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,GAAG;IACH,OAAO;IACP,OAAO;IACP,UAAU;IACV,YAAY;IACZ,IAAI,EAAE,QAAQ;CACf,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { lineColumnFromIndex, snippetAt } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-004',
|
|
4
|
+
title: 'Source maps published in production',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'medium',
|
|
7
|
+
description: 'Publishing .js.map files in production output exposes source code (component names, comments, internal architecture) to anyone. Attackers can reverse engineer and discover sensitive internals (URLs, developer notes).',
|
|
8
|
+
threat: 'Source code disclosure, architecture reconnaissance, intellectual property leak',
|
|
9
|
+
remediation: '1) Next.js: set productionBrowserSourceMaps: false. ' +
|
|
10
|
+
'2) Vite: set build.sourcemap = false or upload to Sentry / Datadog. ' +
|
|
11
|
+
'3) Webpack: use devtool: false or hidden-source-map, then upload to Sentry. ' +
|
|
12
|
+
'4) Block *.map access at CDN/Nginx: location ~* \\.map$ { deny all; }',
|
|
13
|
+
references: [
|
|
14
|
+
'https://nextjs.org/docs/advanced-features/source-maps',
|
|
15
|
+
'https://webpack.js.org/configuration/devtool/',
|
|
16
|
+
'https://docs.sentry.io/platforms/javascript/sourcemaps/',
|
|
17
|
+
],
|
|
18
|
+
cwe: 'CWE-540',
|
|
19
|
+
owasp: 'A05:2021 Security Misconfiguration',
|
|
20
|
+
languages: ['javascript', 'typescript'],
|
|
21
|
+
check: (ctx) => {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const nextPattern = /productionBrowserSourceMaps\s*:\s*true/g;
|
|
24
|
+
for (const m of ctx.source.matchAll(nextPattern)) {
|
|
25
|
+
if (m.index === undefined)
|
|
26
|
+
continue;
|
|
27
|
+
const { line } = lineColumnFromIndex(ctx.source, m.index);
|
|
28
|
+
findings.push({
|
|
29
|
+
id: `${rule.id}-${ctx.relativePath}-${line}`,
|
|
30
|
+
ruleId: rule.id,
|
|
31
|
+
title: rule.title,
|
|
32
|
+
layer: rule.layer,
|
|
33
|
+
severity: rule.severity,
|
|
34
|
+
file: ctx.relativePath,
|
|
35
|
+
match: { snippet: snippetAt(ctx.source, line), line, column: m.index },
|
|
36
|
+
description: rule.description,
|
|
37
|
+
impact: rule.threat,
|
|
38
|
+
remediation: rule.remediation,
|
|
39
|
+
references: rule.references,
|
|
40
|
+
cwe: rule.cwe,
|
|
41
|
+
owasp: rule.owasp,
|
|
42
|
+
fix: {
|
|
43
|
+
find: 'productionBrowserSourceMaps: true',
|
|
44
|
+
replace: 'productionBrowserSourceMaps: false',
|
|
45
|
+
description: 'Disable production source maps',
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
const vitePattern = /build\.sourcemap\s*=\s*['"]?true/g;
|
|
50
|
+
for (const m of ctx.source.matchAll(vitePattern)) {
|
|
51
|
+
if (m.index === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
const { line } = lineColumnFromIndex(ctx.source, m.index);
|
|
54
|
+
findings.push({
|
|
55
|
+
id: `${rule.id}-${ctx.relativePath}-${line}-vite`,
|
|
56
|
+
ruleId: rule.id,
|
|
57
|
+
title: rule.title,
|
|
58
|
+
layer: rule.layer,
|
|
59
|
+
severity: rule.severity,
|
|
60
|
+
file: ctx.relativePath,
|
|
61
|
+
match: { snippet: snippetAt(ctx.source, line), line, column: m.index },
|
|
62
|
+
description: rule.description,
|
|
63
|
+
impact: rule.threat,
|
|
64
|
+
remediation: rule.remediation,
|
|
65
|
+
references: rule.references,
|
|
66
|
+
cwe: rule.cwe,
|
|
67
|
+
owasp: rule.owasp,
|
|
68
|
+
fix: {
|
|
69
|
+
find: 'build.sourcemap = true',
|
|
70
|
+
replace: 'build.sourcemap = false',
|
|
71
|
+
description: 'Disable Vite sourcemap generation',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
return findings;
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
export default rule;
|
|
79
|
+
//# sourceMappingURL=sourceMaps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sourceMaps.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/sourceMaps.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { lineColumnFromIndex, snippetAt } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'FE-001',
|
|
4
|
+
title: 'Unsafe DOM injection (innerHTML / dangerouslySetInnerHTML)',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'high',
|
|
7
|
+
description: 'User-controlled or external data rendered via element.innerHTML or React dangerouslySetInnerHTML enables XSS (Cross-Site Scripting). Attackers inject HTML/JS to steal sessions, exfiltrate data, or take over the page.',
|
|
8
|
+
threat: 'Stored / Reflected XSS → account takeover, session hijack, data exfiltration, malware distribution',
|
|
9
|
+
remediation: "1) Use textContent or the framework's safe binding (React JSX, Vue {{ }}, Angular interpolation). " +
|
|
10
|
+
'2) If rich text is required, sanitize with DOMPurify.sanitize(). ' +
|
|
11
|
+
"3) If user HTML must render, enforce server-side CSP to block inline scripts. " +
|
|
12
|
+
'4) Avoid eval(), document.write(), Function() constructor.',
|
|
13
|
+
references: [
|
|
14
|
+
'https://owasp.org/www-community/attacks/xss/',
|
|
15
|
+
'https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html',
|
|
16
|
+
'https://github.com/cure53/DOMPurify',
|
|
17
|
+
],
|
|
18
|
+
cwe: 'CWE-79',
|
|
19
|
+
owasp: 'A03:2021 Injection',
|
|
20
|
+
languages: ['javascript', 'typescript'],
|
|
21
|
+
check: (ctx) => {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const patterns = [
|
|
24
|
+
/\.innerHTML\s*=/g,
|
|
25
|
+
/dangerouslySetInnerHTML\s*=\s*\{/g,
|
|
26
|
+
/document\.write\s*\(/g,
|
|
27
|
+
/\.outerHTML\s*=/g,
|
|
28
|
+
/insertAdjacentHTML\s*\(/g,
|
|
29
|
+
];
|
|
30
|
+
for (const re of patterns) {
|
|
31
|
+
for (const m of ctx.source.matchAll(re)) {
|
|
32
|
+
if (m.index === undefined)
|
|
33
|
+
continue;
|
|
34
|
+
const { line } = lineColumnFromIndex(ctx.source, m.index);
|
|
35
|
+
let fix;
|
|
36
|
+
if (re.source.includes('dangerouslySetInnerHTML')) {
|
|
37
|
+
const snippet = snippetAt(ctx.source, line, 3);
|
|
38
|
+
const dsihMatch = snippet.match(/dangerouslySetInnerHTML\s*=\s*\{\{\s*__html:\s*([^}]+)\s*\}\s*\}/);
|
|
39
|
+
if (dsihMatch) {
|
|
40
|
+
const expr = dsihMatch[1].trim();
|
|
41
|
+
fix = {
|
|
42
|
+
find: `dangerouslySetInnerHTML={{ __html: ${expr} }}`,
|
|
43
|
+
replace: `dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(${expr}) }}`,
|
|
44
|
+
description: 'Sanitize with DOMPurify (npm i dompurify @types/dompurify)',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
findings.push({
|
|
49
|
+
id: `${rule.id}-${ctx.relativePath}-${line}-${m[0].length}`,
|
|
50
|
+
ruleId: rule.id,
|
|
51
|
+
title: rule.title,
|
|
52
|
+
layer: rule.layer,
|
|
53
|
+
severity: rule.severity,
|
|
54
|
+
file: ctx.relativePath,
|
|
55
|
+
match: { snippet: snippetAt(ctx.source, line), line, column: m.index },
|
|
56
|
+
description: rule.description,
|
|
57
|
+
impact: rule.threat,
|
|
58
|
+
remediation: rule.remediation,
|
|
59
|
+
references: rule.references,
|
|
60
|
+
cwe: rule.cwe,
|
|
61
|
+
owasp: rule.owasp,
|
|
62
|
+
fix,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return findings;
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
export default rule;
|
|
70
|
+
//# sourceMappingURL=xss.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"xss.js","sourceRoot":"","sources":["../../../../src/engine/rules/frontend/xss.ts"],"names":[],"mappings":"AACA,OAAO,EAAY,mBAAmB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE1E,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,4DAA4D;IACnE,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,MAAM;IAChB,WAAW,EACT,0NAA0N;IAC5N,MAAM,EAAE,oGAAoG;IAC5G,WAAW,EACT,oGAAoG;QACpG,mEAAmE;QACnE,gFAAgF;QAChF,4DAA4D;IAC9D,UAAU,EAAE;QACV,8CAA8C;QAC9C,iGAAiG;QACjG,qCAAqC;KACtC;IACD,GAAG,EAAE,QAAQ;IACb,KAAK,EAAE,oBAAoB;IAC3B,SAAS,EAAE,CAAC,YAAY,EAAE,YAAY,CAAC;IACvC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAa;YACzB,kBAAkB;YAClB,mCAAmC;YACnC,uBAAuB;YACvB,kBAAkB;YAClB,0BAA0B;SAC3B,CAAC;QAEF,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;oBAAE,SAAS;gBACpC,MAAM,EAAE,IAAI,EAAE,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBAE1D,IAAI,GAAG,CAAC;gBACR,IAAI,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,yBAAyB,CAAC,EAAE,CAAC;oBAClD,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;oBAC/C,MAAM,SAAS,GAAG,OAAO,CAAC,KAAK,CAC7B,kEAAkE,CACnE,CAAC;oBACF,IAAI,SAAS,EAAE,CAAC;wBACd,MAAM,IAAI,GAAG,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;wBACjC,GAAG,GAAG;4BACJ,IAAI,EAAE,sCAAsC,IAAI,KAAK;4BACrD,OAAO,EAAE,yDAAyD,IAAI,MAAM;4BAC5E,WAAW,EAAE,4DAA4D;yBAC1E,CAAC;oBACJ,CAAC;gBACH,CAAC;gBAED,QAAQ,CAAC,IAAI,CAAC;oBACZ,EAAE,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE;oBAC3D,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,IAAI,EAAE,GAAG,CAAC,YAAY;oBACtB,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE;oBACtE,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,GAAG;iBACJ,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Language, Rule } from '../../types.js';
|
|
2
|
+
export interface RegisteredRule extends Rule {
|
|
3
|
+
}
|
|
4
|
+
export declare function getAllRules(): readonly RegisteredRule[];
|
|
5
|
+
export declare function getRuleById(id: string): RegisteredRule | undefined;
|
|
6
|
+
export declare function getRulesForLanguage(language: Language): readonly RegisteredRule[];
|
|
7
|
+
export declare function getRulesByLayer(layer: RegisteredRule['layer']): readonly RegisteredRule[];
|
|
8
|
+
export declare function getRulesByLanguages(languages: readonly Language[]): readonly RegisteredRule[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { rules as frontendRules } from './frontend/index.js';
|
|
2
|
+
import { rules as backendRules } from './backend/index.js';
|
|
3
|
+
import { rules as networkRules } from './network/index.js';
|
|
4
|
+
import { rules as databaseRules } from './database/index.js';
|
|
5
|
+
import { rules as cicdRules } from './cicd/index.js';
|
|
6
|
+
import { rules as observabilityRules } from './observability/index.js';
|
|
7
|
+
import { rules as rustRules } from './rust/index.js';
|
|
8
|
+
import { rules as rubyRules } from './ruby/index.js';
|
|
9
|
+
import { rules as phpRules } from './php/index.js';
|
|
10
|
+
import { rules as kotlinRules } from './kotlin/index.js';
|
|
11
|
+
const registry = [
|
|
12
|
+
...Object.values(frontendRules),
|
|
13
|
+
...Object.values(backendRules),
|
|
14
|
+
...Object.values(networkRules),
|
|
15
|
+
...Object.values(databaseRules),
|
|
16
|
+
...Object.values(cicdRules),
|
|
17
|
+
...Object.values(observabilityRules),
|
|
18
|
+
...Object.values(rustRules),
|
|
19
|
+
...Object.values(rubyRules),
|
|
20
|
+
...Object.values(phpRules),
|
|
21
|
+
...Object.values(kotlinRules),
|
|
22
|
+
];
|
|
23
|
+
export function getAllRules() {
|
|
24
|
+
return registry;
|
|
25
|
+
}
|
|
26
|
+
export function getRuleById(id) {
|
|
27
|
+
return registry.find((r) => r.id === id);
|
|
28
|
+
}
|
|
29
|
+
export function getRulesForLanguage(language) {
|
|
30
|
+
return registry.filter((r) => r.languages.includes(language));
|
|
31
|
+
}
|
|
32
|
+
export function getRulesByLayer(layer) {
|
|
33
|
+
return registry.filter((r) => r.layer === layer);
|
|
34
|
+
}
|
|
35
|
+
export function getRulesByLanguages(languages) {
|
|
36
|
+
return registry.filter((r) => r.languages.some((l) => languages.includes(l)));
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/engine/rules/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,KAAK,IAAI,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,KAAK,IAAI,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7D,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,IAAI,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACvE,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,KAAK,IAAI,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAIzD,MAAM,QAAQ,GAAqB;IACjC,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;IAC/B,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IAC9B,GAAG,MAAM,CAAC,MAAM,CAAC,YAAY,CAAC;IAC9B,GAAG,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC;IAC/B,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;IAC3B,GAAG,MAAM,CAAC,MAAM,CAAC,kBAAkB,CAAC;IACpC,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;IAC3B,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC;IAC3B,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC1B,GAAG,MAAM,CAAC,MAAM,CAAC,WAAW,CAAC;CAC9B,CAAC;AAEF,MAAM,UAAU,WAAW;IACzB,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,EAAU;IACpC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;AAC3C,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,QAAkB;IACpD,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAA8B;IAC5D,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC;AACnD,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,SAA8B;IAChE,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAChF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/engine/rules/kotlin/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,mBAAmB,CAAC;AAC7C,OAAO,WAAW,MAAM,kBAAkB,CAAC;AAE3C,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,YAAY;IACZ,WAAW;CACZ,CAAC;AAEF,eAAe,KAAK,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { lineColumnFromIndex, snippetAt } from '../_helpers.js';
|
|
2
|
+
const rule = {
|
|
3
|
+
id: 'KT-002',
|
|
4
|
+
title: 'Kotlin: XSS via kotlinx.html raw text or unsafe HTML',
|
|
5
|
+
layer: 'frontend',
|
|
6
|
+
severity: 'high',
|
|
7
|
+
description: '` kotlinx.html` DSL escapes HTML by default, but `unsafe { +userInput }` (the unary plus operator on TextBuilder) or `rawText(userInput)` inserts raw HTML — XSS.',
|
|
8
|
+
threat: 'Reflected / Stored XSS → session hijack, account takeover',
|
|
9
|
+
remediation: '1) Use plain DSL: `p { text(userInput) }` — auto-escapes. ' +
|
|
10
|
+
'2) NEVER use `unsafe { +userInput }` or `rawText(userInput)` with user-controlled input. ' +
|
|
11
|
+
'3) For rich text, sanitize first via OWASP Java HTML Sanitizer. ' +
|
|
12
|
+
'4) Set Content-Security-Policy header for defense in depth. ' +
|
|
13
|
+
'5) Server-side render: always escape on output.',
|
|
14
|
+
references: [
|
|
15
|
+
'https://github.com/Kotlin/kotlinx.html',
|
|
16
|
+
'https://owasp.org/www-community/attacks/xss/',
|
|
17
|
+
],
|
|
18
|
+
cwe: 'CWE-79',
|
|
19
|
+
owasp: 'A03:2021 Injection',
|
|
20
|
+
languages: ['kotlin'],
|
|
21
|
+
check: (ctx) => {
|
|
22
|
+
const findings = [];
|
|
23
|
+
const patterns = [
|
|
24
|
+
/unsafe\s*\{\s*\+/g,
|
|
25
|
+
/rawText\s*\(\s*[^)]*request/gi,
|
|
26
|
+
/rawText\s*\(\s*[^)]*input/gi,
|
|
27
|
+
];
|
|
28
|
+
for (const re of patterns) {
|
|
29
|
+
for (const m of ctx.source.matchAll(re)) {
|
|
30
|
+
if (m.index === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
const { line } = lineColumnFromIndex(ctx.source, m.index);
|
|
33
|
+
findings.push({
|
|
34
|
+
id: `${rule.id}-${ctx.relativePath}-${line}`,
|
|
35
|
+
ruleId: rule.id,
|
|
36
|
+
title: rule.title,
|
|
37
|
+
layer: rule.layer,
|
|
38
|
+
severity: rule.severity,
|
|
39
|
+
file: ctx.relativePath,
|
|
40
|
+
match: { snippet: snippetAt(ctx.source, line), line, column: m.index },
|
|
41
|
+
description: rule.description,
|
|
42
|
+
impact: rule.threat,
|
|
43
|
+
remediation: rule.remediation,
|
|
44
|
+
references: rule.references,
|
|
45
|
+
cwe: rule.cwe,
|
|
46
|
+
owasp: rule.owasp,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return findings;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
export default rule;
|
|
54
|
+
//# sourceMappingURL=kotlinxHtml.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kotlinxHtml.js","sourceRoot":"","sources":["../../../../src/engine/rules/kotlin/kotlinxHtml.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhE,MAAM,IAAI,GAAS;IACjB,EAAE,EAAE,QAAQ;IACZ,KAAK,EAAE,sDAAsD;IAC7D,KAAK,EAAE,UAAU;IACjB,QAAQ,EAAE,MAAM;IAChB,WAAW,EACT,mKAAmK;IACrK,MAAM,EAAE,2DAA2D;IACnE,WAAW,EACT,4DAA4D;QAC5D,2FAA2F;QAC3F,kEAAkE;QAClE,8DAA8D;QAC9D,iDAAiD;IACnD,UAAU,EAAE;QACV,wCAAwC;QACxC,8CAA8C;KAC/C;IACD,GAAG,EAAE,QAAQ;IACb,KAAK,EAAE,oBAAoB;IAC3B,SAAS,EAAE,CAAC,QAAQ,CAAC;IACrB,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE;QACb,MAAM,QAAQ,GAAG,EAAE,CAAC;QACpB,MAAM,QAAQ,GAAG;YACf,mBAAmB;YACnB,+BAA+B;YAC/B,6BAA6B;SAC9B,CAAC;QACF,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,KAAK,MAAM,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC;gBACxC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;oBAAE,SAAS;gBACpC,MAAM,EAAE,IAAI,EAAE,GAAG,mBAAmB,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC;gBAC1D,QAAQ,CAAC,IAAI,CAAC;oBACZ,EAAE,EAAE,GAAG,IAAI,CAAC,EAAE,IAAI,GAAG,CAAC,YAAY,IAAI,IAAI,EAAE;oBAC5C,MAAM,EAAE,IAAI,CAAC,EAAE;oBACf,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,KAAK,EAAE,IAAI,CAAC,KAAK;oBACjB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,IAAI,EAAE,GAAG,CAAC,YAAY;oBACtB,KAAK,EAAE,EAAE,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,KAAK,EAAE;oBACtE,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,MAAM,EAAE,IAAI,CAAC,MAAM;oBACnB,WAAW,EAAE,IAAI,CAAC,WAAW;oBAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;oBAC3B,GAAG,EAAE,IAAI,CAAC,GAAG;oBACb,KAAK,EAAE,IAAI,CAAC,KAAK;iBAClB,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QACD,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF,CAAC;AAEF,eAAe,IAAI,CAAC"}
|