nestjs-doctor 0.3.0 → 0.3.1
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/README.md +5 -7
- package/dist/api/index.cjs +23 -70
- package/dist/api/index.d.cts.map +1 -1
- package/dist/api/index.d.mts.map +1 -1
- package/dist/api/index.mjs +23 -70
- package/dist/api/index.mjs.map +1 -1
- package/dist/cli/index.mjs +23 -70
- package/package.json +1 -1
- package/skill/SKILL.md +0 -2
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
15
|
<p align="center">
|
|
16
|
-
|
|
16
|
+
45 built-in rules across <b>security</b>, <b>performance</b>, <b>correctness</b>, and <b>architecture</b>. Outputs a <b>0-100 score</b> with actionable diagnostics. Zero config. Monorepo support. Built to catch the anti-patterns that AI-generated code loves to introduce.
|
|
17
17
|
</p>
|
|
18
18
|
|
|
19
19
|
---
|
|
@@ -166,7 +166,7 @@ Or use a `"nestjs-doctor"` key in `package.json`.
|
|
|
166
166
|
| Key | Type | Description |
|
|
167
167
|
|-----|------|-------------|
|
|
168
168
|
| `include` | `string[]` | Glob patterns to scan (default: `["**/*.ts"]`) |
|
|
169
|
-
| `exclude` | `string[]` | Glob patterns to skip (default includes `node_modules`, `dist`, test
|
|
169
|
+
| `exclude` | `string[]` | Glob patterns to skip (default includes `node_modules`, `dist`, `build`, `coverage`, `*.spec.ts`, `*.test.ts`, `*.e2e-spec.ts`, `*.e2e-test.ts`, `*.d.ts`, `test/`, `tests/`, `__tests__/`, `__mocks__/`, `__fixtures__/`) |
|
|
170
170
|
| `minScore` | `number` | Minimum passing score (0-100). Exits with code 1 if below threshold |
|
|
171
171
|
| `ignore.rules` | `string[]` | Rule IDs to suppress |
|
|
172
172
|
| `ignore.files` | `string[]` | Glob patterns for files whose diagnostics are hidden |
|
|
@@ -234,15 +234,13 @@ mono.combined; // Merged DiagnoseResult
|
|
|
234
234
|
|
|
235
235
|
---
|
|
236
236
|
|
|
237
|
-
## Rules (
|
|
237
|
+
## Rules (45)
|
|
238
238
|
|
|
239
|
-
### Security (
|
|
239
|
+
### Security (9)
|
|
240
240
|
|
|
241
241
|
| Rule | Severity | What it catches |
|
|
242
242
|
|------|----------|-----------------|
|
|
243
243
|
| `no-hardcoded-secrets` | error | API keys, tokens, passwords in source code |
|
|
244
|
-
| `no-wildcard-cors` | error | CORS with `origin: '*'` or `origin: true` |
|
|
245
|
-
| `no-unsafe-raw-query` | error | Template literal interpolation in raw SQL |
|
|
246
244
|
| `no-eval` | error | `eval()` or `new Function()` usage |
|
|
247
245
|
| `no-csrf-disabled` | error | Explicitly disabling CSRF protection |
|
|
248
246
|
| `no-dangerous-redirects` | error | Redirects with user-controlled input |
|
|
@@ -257,7 +255,7 @@ mono.combined; // Merged DiagnoseResult
|
|
|
257
255
|
| Rule | Severity | What it catches |
|
|
258
256
|
|------|----------|-----------------|
|
|
259
257
|
| `no-missing-injectable` | error | Provider in module missing `@Injectable()` |
|
|
260
|
-
| `no-duplicate-routes` | error | Same method + path twice in a controller |
|
|
258
|
+
| `no-duplicate-routes` | error | Same method + path + version twice in a controller |
|
|
261
259
|
| `no-missing-guard-method` | error | Guard class missing `canActivate()` |
|
|
262
260
|
| `no-missing-pipe-method` | error | Pipe class missing `transform()` |
|
|
263
261
|
| `no-missing-filter-catch` | error | `@Catch()` class missing `catch()` |
|
package/dist/api/index.cjs
CHANGED
|
@@ -905,7 +905,7 @@ const noDuplicateRoutes = {
|
|
|
905
905
|
id: "correctness/no-duplicate-routes",
|
|
906
906
|
category: "correctness",
|
|
907
907
|
severity: "error",
|
|
908
|
-
description: "Same HTTP method + route path should not appear twice in a single controller",
|
|
908
|
+
description: "Same HTTP method + route path + version should not appear twice in a single controller",
|
|
909
909
|
help: "Remove or rename one of the duplicate route handlers."
|
|
910
910
|
},
|
|
911
911
|
check(context) {
|
|
@@ -917,7 +917,8 @@ const noDuplicateRoutes = {
|
|
|
917
917
|
if (!HTTP_METHOD_DECORATORS.has(decoratorName)) continue;
|
|
918
918
|
const args = decorator.getArguments();
|
|
919
919
|
const path = args.length > 0 ? args[0].getText() : "\"\"";
|
|
920
|
-
const
|
|
920
|
+
const versionDecorator = method.getDecorator("Version");
|
|
921
|
+
const routeKey = `${decoratorName}:${path}:${versionDecorator ? versionDecorator.getArguments()[0]?.getText() ?? "" : ""}`;
|
|
921
922
|
const existing = routeMap.get(routeKey);
|
|
922
923
|
if (existing) context.report({
|
|
923
924
|
filePath: context.filePath,
|
|
@@ -984,6 +985,7 @@ const noMissingFilterCatch = {
|
|
|
984
985
|
check(context) {
|
|
985
986
|
for (const cls of context.sourceFile.getClasses()) {
|
|
986
987
|
if (!hasDecorator(cls, "Catch")) continue;
|
|
988
|
+
if (cls.getExtends()) continue;
|
|
987
989
|
if (!cls.getMethods().some((m) => m.getName() === "catch")) context.report({
|
|
988
990
|
filePath: context.filePath,
|
|
989
991
|
message: `Exception filter '${cls.getName()}' has @Catch() but is missing the 'catch()' method.`,
|
|
@@ -1010,6 +1012,7 @@ const noMissingGuardMethod = {
|
|
|
1010
1012
|
const name = cls.getName() ?? "";
|
|
1011
1013
|
if (!name.endsWith("Guard")) continue;
|
|
1012
1014
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1015
|
+
if (cls.getExtends()) continue;
|
|
1013
1016
|
if (!cls.getMethods().some((m) => m.getName() === "canActivate")) context.report({
|
|
1014
1017
|
filePath: context.filePath,
|
|
1015
1018
|
message: `Guard '${name}' is missing the 'canActivate()' method.`,
|
|
@@ -1080,6 +1083,7 @@ const noMissingInterceptorMethod = {
|
|
|
1080
1083
|
const name = cls.getName() ?? "";
|
|
1081
1084
|
if (!name.endsWith("Interceptor")) continue;
|
|
1082
1085
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1086
|
+
if (cls.getExtends()) continue;
|
|
1083
1087
|
if (!cls.getMethods().some((m) => m.getName() === "intercept")) context.report({
|
|
1084
1088
|
filePath: context.filePath,
|
|
1085
1089
|
message: `Interceptor '${name}' is missing the 'intercept()' method.`,
|
|
@@ -1132,6 +1136,7 @@ const noMissingPipeMethod = {
|
|
|
1132
1136
|
const name = cls.getName() ?? "";
|
|
1133
1137
|
if (!name.endsWith("Pipe")) continue;
|
|
1134
1138
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1139
|
+
if (cls.getExtends()) continue;
|
|
1135
1140
|
if (!cls.getMethods().some((m) => m.getName() === "transform")) context.report({
|
|
1136
1141
|
filePath: context.filePath,
|
|
1137
1142
|
message: `Pipe '${name}' is missing the 'transform()' method.`,
|
|
@@ -1825,7 +1830,7 @@ const noExposedStackTrace = {
|
|
|
1825
1830
|
//#region src/rules/security/no-hardcoded-secrets.ts
|
|
1826
1831
|
const SECRET_PATTERNS = [
|
|
1827
1832
|
{
|
|
1828
|
-
pattern: /^[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1833
|
+
pattern: /^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1829
1834
|
name: "Base64 key"
|
|
1830
1835
|
},
|
|
1831
1836
|
{
|
|
@@ -1880,8 +1885,15 @@ const PLACEHOLDER_VALUES = new Set([
|
|
|
1880
1885
|
"changeme",
|
|
1881
1886
|
"password"
|
|
1882
1887
|
]);
|
|
1888
|
+
const DOT_SEPARATED_CONSTANT = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
1883
1889
|
function isSuspiciousValue(value) {
|
|
1884
|
-
|
|
1890
|
+
if (value.length < 8) return false;
|
|
1891
|
+
if (value.includes("${")) return false;
|
|
1892
|
+
if (value.startsWith("process.env")) return false;
|
|
1893
|
+
if (PLACEHOLDER_VALUES.has(value)) return false;
|
|
1894
|
+
if (value.includes(" ")) return false;
|
|
1895
|
+
if (DOT_SEPARATED_CONSTANT.test(value)) return false;
|
|
1896
|
+
return true;
|
|
1885
1897
|
}
|
|
1886
1898
|
function hasSuspiciousName(name) {
|
|
1887
1899
|
return VARIABLE_NAME_PATTERNS.some((p) => p.test(name));
|
|
@@ -1942,41 +1954,6 @@ const noHardcodedSecrets = {
|
|
|
1942
1954
|
}
|
|
1943
1955
|
};
|
|
1944
1956
|
|
|
1945
|
-
//#endregion
|
|
1946
|
-
//#region src/rules/security/no-unsafe-raw-query.ts
|
|
1947
|
-
const RAW_QUERY_METHODS = new Set([
|
|
1948
|
-
"$executeRaw",
|
|
1949
|
-
"$queryRaw",
|
|
1950
|
-
"$executeRawUnsafe",
|
|
1951
|
-
"$queryRawUnsafe",
|
|
1952
|
-
"query"
|
|
1953
|
-
]);
|
|
1954
|
-
const noUnsafeRawQuery = {
|
|
1955
|
-
meta: {
|
|
1956
|
-
id: "security/no-unsafe-raw-query",
|
|
1957
|
-
category: "security",
|
|
1958
|
-
severity: "error",
|
|
1959
|
-
description: "Raw SQL queries with template literal interpolation are a SQL injection risk",
|
|
1960
|
-
help: "Use parameterized queries or Prisma's tagged template (Prisma.sql`...`) instead of string interpolation."
|
|
1961
|
-
},
|
|
1962
|
-
check(context) {
|
|
1963
|
-
const callExpressions = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.CallExpression);
|
|
1964
|
-
for (const call of callExpressions) {
|
|
1965
|
-
const methodName = call.getExpression().getText().split(".").pop() ?? "";
|
|
1966
|
-
if (!RAW_QUERY_METHODS.has(methodName)) continue;
|
|
1967
|
-
const args = call.getArguments();
|
|
1968
|
-
if (args.length === 0) continue;
|
|
1969
|
-
if (args[0].getKind() === ts_morph.SyntaxKind.TemplateExpression) context.report({
|
|
1970
|
-
filePath: context.filePath,
|
|
1971
|
-
message: `Raw SQL query '${methodName}()' uses template literal interpolation — SQL injection risk.`,
|
|
1972
|
-
help: this.meta.help,
|
|
1973
|
-
line: call.getStartLineNumber(),
|
|
1974
|
-
column: 1
|
|
1975
|
-
});
|
|
1976
|
-
}
|
|
1977
|
-
}
|
|
1978
|
-
};
|
|
1979
|
-
|
|
1980
1957
|
//#endregion
|
|
1981
1958
|
//#region src/rules/security/no-weak-crypto.ts
|
|
1982
1959
|
const WEAK_ALGORITHMS = new Set(["md5", "sha1"]);
|
|
@@ -2008,34 +1985,6 @@ const noWeakCrypto = {
|
|
|
2008
1985
|
}
|
|
2009
1986
|
};
|
|
2010
1987
|
|
|
2011
|
-
//#endregion
|
|
2012
|
-
//#region src/rules/security/no-wildcard-cors.ts
|
|
2013
|
-
const noWildcardCors = {
|
|
2014
|
-
meta: {
|
|
2015
|
-
id: "security/no-wildcard-cors",
|
|
2016
|
-
category: "security",
|
|
2017
|
-
severity: "error",
|
|
2018
|
-
description: "CORS should not be configured with origin: '*' or origin: true — allows any domain",
|
|
2019
|
-
help: "Specify allowed origins explicitly instead of using wildcard CORS."
|
|
2020
|
-
},
|
|
2021
|
-
check(context) {
|
|
2022
|
-
const propertyAssignments = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.PropertyAssignment);
|
|
2023
|
-
for (const prop of propertyAssignments) {
|
|
2024
|
-
if (prop.getName() !== "origin") continue;
|
|
2025
|
-
const initializer = prop.getInitializer();
|
|
2026
|
-
if (!initializer) continue;
|
|
2027
|
-
const text = initializer.getText();
|
|
2028
|
-
if (text === "'*'" || text === "\"*\"" || text === "true") context.report({
|
|
2029
|
-
filePath: context.filePath,
|
|
2030
|
-
message: `CORS configured with origin: ${text} — allows requests from any domain.`,
|
|
2031
|
-
help: this.meta.help,
|
|
2032
|
-
line: prop.getStartLineNumber(),
|
|
2033
|
-
column: 1
|
|
2034
|
-
});
|
|
2035
|
-
}
|
|
2036
|
-
}
|
|
2037
|
-
};
|
|
2038
|
-
|
|
2039
1988
|
//#endregion
|
|
2040
1989
|
//#region src/rules/security/require-auth-guard.ts
|
|
2041
1990
|
const requireAuthGuard = {
|
|
@@ -2122,8 +2071,6 @@ const allRules = [
|
|
|
2122
2071
|
requireInjectDecorator,
|
|
2123
2072
|
noMissingInjectable,
|
|
2124
2073
|
noHardcodedSecrets,
|
|
2125
|
-
noWildcardCors,
|
|
2126
|
-
noUnsafeRawQuery,
|
|
2127
2074
|
requireAuthGuard,
|
|
2128
2075
|
noEval,
|
|
2129
2076
|
noWeakCrypto,
|
|
@@ -2224,7 +2171,13 @@ const DEFAULT_CONFIG = {
|
|
|
2224
2171
|
"**/*.spec.ts",
|
|
2225
2172
|
"**/*.test.ts",
|
|
2226
2173
|
"**/*.e2e-spec.ts",
|
|
2227
|
-
"**/*.
|
|
2174
|
+
"**/*.e2e-test.ts",
|
|
2175
|
+
"**/*.d.ts",
|
|
2176
|
+
"**/test/**",
|
|
2177
|
+
"**/tests/**",
|
|
2178
|
+
"**/__tests__/**",
|
|
2179
|
+
"**/__mocks__/**",
|
|
2180
|
+
"**/__fixtures__/**"
|
|
2228
2181
|
]
|
|
2229
2182
|
};
|
|
2230
2183
|
|
package/dist/api/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":";;;UASiB,UAAA;EAChB,gBAAA,EAAkB,gBAAA;EAClB,WAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,SAAA;AAAA;AAAA,UAGgB,WAAA;EAChB,KAAA,EAAO,GAAA,SAAY,GAAA;EACnB,OAAA,EAAS,GAAA,SAAY,UAAA;EACrB,gBAAA,EAAkB,GAAA,SAAY,UAAA;AAAA;;;UCjBd,YAAA;EAChB,gBAAA,EAAkB,gBAAA;EAClB,YAAA;EACA,QAAA;EACA,IAAA;EACA,iBAAA;AAAA;;;KCVW,QAAA;AAAA,KACA,QAAA;AAAA,UAMK,UAAA;EAChB,QAAA,EAAU,QAAA;EACV,MAAA;EACA,QAAA;EACA,IAAA;EACA,IAAA;EACA,OAAA;EACA,IAAA;EACA,QAAA,EAAU,QAAA;AAAA;;;UCbM,YAAA;EAChB,OAAA;EACA,QAAA,GAAW,QAAA;AAAA;AAAA,UAGK,wBAAA;EAChB,KAAA;EACA,KAAA;AAAA;AAAA,UAGgB,kBAAA;EAChB,UAAA,GAAa,OAAA,CAAQ,MAAA,CAAO,QAAA;EAC5B,OAAA;EACA,MAAA,GAAS,wBAAA;EACT,OAAA;EACA,QAAA;EACA,KAAA,GAAQ,MAAA,SAAe,YAAA;EACvB,UAAA;IACC,kBAAA;IACA,gBAAA;IACA,iBAAA;IACA,cAAA;EAAA;AAAA;;;KCjBU,SAAA;AAAA,UAEK,QAAA;EAChB,QAAA,EAAU,QAAA;EACV,WAAA;EACA,IAAA;EACA,EAAA;EACA,KAAA,GAAQ,SAAA;EACR,QAAA,EAAU,QAAA;AAAA;AAAA,UAGM,WAAA;EAChB,QAAA;EACA,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,UAAA;EACxB,UAAA,EAAY,UAAA;AAAA;AAAA,UAGI,kBAAA;EAChB,MAAA,EAAQ,kBAAA;EACR,KAAA;EACA,WAAA,EAAa,WAAA;EACb,OAAA,EAAS,OAAA;EACT,SAAA,EAAW,GAAA,SAAY,YAAA;EACvB,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,UAAA;AAAA;AAAA,UAGR,IAAA;EAChB,KAAA,CAAM,OAAA,EAAS,WAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,UAGU,WAAA;EAChB,KAAA,CAAM,OAAA,EAAS,kBAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,KAGK,OAAA,GAAU,IAAA,GAAO,WAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}
|
package/dist/api/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":";;;UASiB,UAAA;EAChB,gBAAA,EAAkB,gBAAA;EAClB,WAAA;EACA,OAAA;EACA,QAAA;EACA,OAAA;EACA,IAAA;EACA,SAAA;AAAA;AAAA,UAGgB,WAAA;EAChB,KAAA,EAAO,GAAA,SAAY,GAAA;EACnB,OAAA,EAAS,GAAA,SAAY,UAAA;EACrB,gBAAA,EAAkB,GAAA,SAAY,UAAA;AAAA;;;UCjBd,YAAA;EAChB,gBAAA,EAAkB,gBAAA;EAClB,YAAA;EACA,QAAA;EACA,IAAA;EACA,iBAAA;AAAA;;;KCVW,QAAA;AAAA,KACA,QAAA;AAAA,UAMK,UAAA;EAChB,QAAA,EAAU,QAAA;EACV,MAAA;EACA,QAAA;EACA,IAAA;EACA,IAAA;EACA,OAAA;EACA,IAAA;EACA,QAAA,EAAU,QAAA;AAAA;;;UCbM,YAAA;EAChB,OAAA;EACA,QAAA,GAAW,QAAA;AAAA;AAAA,UAGK,wBAAA;EAChB,KAAA;EACA,KAAA;AAAA;AAAA,UAGgB,kBAAA;EAChB,UAAA,GAAa,OAAA,CAAQ,MAAA,CAAO,QAAA;EAC5B,OAAA;EACA,MAAA,GAAS,wBAAA;EACT,OAAA;EACA,QAAA;EACA,KAAA,GAAQ,MAAA,SAAe,YAAA;EACvB,UAAA;IACC,kBAAA;IACA,gBAAA;IACA,iBAAA;IACA,cAAA;EAAA;AAAA;;;KCjBU,SAAA;AAAA,UAEK,QAAA;EAChB,QAAA,EAAU,QAAA;EACV,WAAA;EACA,IAAA;EACA,EAAA;EACA,KAAA,GAAQ,SAAA;EACR,QAAA,EAAU,QAAA;AAAA;AAAA,UAGM,WAAA;EAChB,QAAA;EACA,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,UAAA;EACxB,UAAA,EAAY,UAAA;AAAA;AAAA,UAGI,kBAAA;EAChB,MAAA,EAAQ,kBAAA;EACR,KAAA;EACA,WAAA,EAAa,WAAA;EACb,OAAA,EAAS,OAAA;EACT,SAAA,EAAW,GAAA,SAAY,YAAA;EACvB,MAAA,CAAO,UAAA,EAAY,IAAA,CAAK,UAAA;AAAA;AAAA,UAGR,IAAA;EAChB,KAAA,CAAM,OAAA,EAAS,WAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,UAGU,WAAA;EAChB,KAAA,CAAM,OAAA,EAAS,kBAAA;EACf,IAAA,EAAM,QAAA;AAAA;AAAA,KAGK,OAAA,GAAU,IAAA,GAAO,WAAA;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/engine/module-graph.ts","../../src/engine/type-resolver.ts","../../src/types/diagnostic.ts","../../src/types/config.ts","../../src/rules/types.ts","../../src/rules/index.ts","../../src/types/errors.ts","../../src/types/result.ts","../../src/api/index.ts"],"mappings":"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"}
|
package/dist/api/index.mjs
CHANGED
|
@@ -876,7 +876,7 @@ const noDuplicateRoutes = {
|
|
|
876
876
|
id: "correctness/no-duplicate-routes",
|
|
877
877
|
category: "correctness",
|
|
878
878
|
severity: "error",
|
|
879
|
-
description: "Same HTTP method + route path should not appear twice in a single controller",
|
|
879
|
+
description: "Same HTTP method + route path + version should not appear twice in a single controller",
|
|
880
880
|
help: "Remove or rename one of the duplicate route handlers."
|
|
881
881
|
},
|
|
882
882
|
check(context) {
|
|
@@ -888,7 +888,8 @@ const noDuplicateRoutes = {
|
|
|
888
888
|
if (!HTTP_METHOD_DECORATORS.has(decoratorName)) continue;
|
|
889
889
|
const args = decorator.getArguments();
|
|
890
890
|
const path = args.length > 0 ? args[0].getText() : "\"\"";
|
|
891
|
-
const
|
|
891
|
+
const versionDecorator = method.getDecorator("Version");
|
|
892
|
+
const routeKey = `${decoratorName}:${path}:${versionDecorator ? versionDecorator.getArguments()[0]?.getText() ?? "" : ""}`;
|
|
892
893
|
const existing = routeMap.get(routeKey);
|
|
893
894
|
if (existing) context.report({
|
|
894
895
|
filePath: context.filePath,
|
|
@@ -955,6 +956,7 @@ const noMissingFilterCatch = {
|
|
|
955
956
|
check(context) {
|
|
956
957
|
for (const cls of context.sourceFile.getClasses()) {
|
|
957
958
|
if (!hasDecorator(cls, "Catch")) continue;
|
|
959
|
+
if (cls.getExtends()) continue;
|
|
958
960
|
if (!cls.getMethods().some((m) => m.getName() === "catch")) context.report({
|
|
959
961
|
filePath: context.filePath,
|
|
960
962
|
message: `Exception filter '${cls.getName()}' has @Catch() but is missing the 'catch()' method.`,
|
|
@@ -981,6 +983,7 @@ const noMissingGuardMethod = {
|
|
|
981
983
|
const name = cls.getName() ?? "";
|
|
982
984
|
if (!name.endsWith("Guard")) continue;
|
|
983
985
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
986
|
+
if (cls.getExtends()) continue;
|
|
984
987
|
if (!cls.getMethods().some((m) => m.getName() === "canActivate")) context.report({
|
|
985
988
|
filePath: context.filePath,
|
|
986
989
|
message: `Guard '${name}' is missing the 'canActivate()' method.`,
|
|
@@ -1051,6 +1054,7 @@ const noMissingInterceptorMethod = {
|
|
|
1051
1054
|
const name = cls.getName() ?? "";
|
|
1052
1055
|
if (!name.endsWith("Interceptor")) continue;
|
|
1053
1056
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1057
|
+
if (cls.getExtends()) continue;
|
|
1054
1058
|
if (!cls.getMethods().some((m) => m.getName() === "intercept")) context.report({
|
|
1055
1059
|
filePath: context.filePath,
|
|
1056
1060
|
message: `Interceptor '${name}' is missing the 'intercept()' method.`,
|
|
@@ -1103,6 +1107,7 @@ const noMissingPipeMethod = {
|
|
|
1103
1107
|
const name = cls.getName() ?? "";
|
|
1104
1108
|
if (!name.endsWith("Pipe")) continue;
|
|
1105
1109
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1110
|
+
if (cls.getExtends()) continue;
|
|
1106
1111
|
if (!cls.getMethods().some((m) => m.getName() === "transform")) context.report({
|
|
1107
1112
|
filePath: context.filePath,
|
|
1108
1113
|
message: `Pipe '${name}' is missing the 'transform()' method.`,
|
|
@@ -1796,7 +1801,7 @@ const noExposedStackTrace = {
|
|
|
1796
1801
|
//#region src/rules/security/no-hardcoded-secrets.ts
|
|
1797
1802
|
const SECRET_PATTERNS = [
|
|
1798
1803
|
{
|
|
1799
|
-
pattern: /^[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1804
|
+
pattern: /^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1800
1805
|
name: "Base64 key"
|
|
1801
1806
|
},
|
|
1802
1807
|
{
|
|
@@ -1851,8 +1856,15 @@ const PLACEHOLDER_VALUES = new Set([
|
|
|
1851
1856
|
"changeme",
|
|
1852
1857
|
"password"
|
|
1853
1858
|
]);
|
|
1859
|
+
const DOT_SEPARATED_CONSTANT = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
1854
1860
|
function isSuspiciousValue(value) {
|
|
1855
|
-
|
|
1861
|
+
if (value.length < 8) return false;
|
|
1862
|
+
if (value.includes("${")) return false;
|
|
1863
|
+
if (value.startsWith("process.env")) return false;
|
|
1864
|
+
if (PLACEHOLDER_VALUES.has(value)) return false;
|
|
1865
|
+
if (value.includes(" ")) return false;
|
|
1866
|
+
if (DOT_SEPARATED_CONSTANT.test(value)) return false;
|
|
1867
|
+
return true;
|
|
1856
1868
|
}
|
|
1857
1869
|
function hasSuspiciousName(name) {
|
|
1858
1870
|
return VARIABLE_NAME_PATTERNS.some((p) => p.test(name));
|
|
@@ -1913,41 +1925,6 @@ const noHardcodedSecrets = {
|
|
|
1913
1925
|
}
|
|
1914
1926
|
};
|
|
1915
1927
|
|
|
1916
|
-
//#endregion
|
|
1917
|
-
//#region src/rules/security/no-unsafe-raw-query.ts
|
|
1918
|
-
const RAW_QUERY_METHODS = new Set([
|
|
1919
|
-
"$executeRaw",
|
|
1920
|
-
"$queryRaw",
|
|
1921
|
-
"$executeRawUnsafe",
|
|
1922
|
-
"$queryRawUnsafe",
|
|
1923
|
-
"query"
|
|
1924
|
-
]);
|
|
1925
|
-
const noUnsafeRawQuery = {
|
|
1926
|
-
meta: {
|
|
1927
|
-
id: "security/no-unsafe-raw-query",
|
|
1928
|
-
category: "security",
|
|
1929
|
-
severity: "error",
|
|
1930
|
-
description: "Raw SQL queries with template literal interpolation are a SQL injection risk",
|
|
1931
|
-
help: "Use parameterized queries or Prisma's tagged template (Prisma.sql`...`) instead of string interpolation."
|
|
1932
|
-
},
|
|
1933
|
-
check(context) {
|
|
1934
|
-
const callExpressions = context.sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
1935
|
-
for (const call of callExpressions) {
|
|
1936
|
-
const methodName = call.getExpression().getText().split(".").pop() ?? "";
|
|
1937
|
-
if (!RAW_QUERY_METHODS.has(methodName)) continue;
|
|
1938
|
-
const args = call.getArguments();
|
|
1939
|
-
if (args.length === 0) continue;
|
|
1940
|
-
if (args[0].getKind() === SyntaxKind.TemplateExpression) context.report({
|
|
1941
|
-
filePath: context.filePath,
|
|
1942
|
-
message: `Raw SQL query '${methodName}()' uses template literal interpolation — SQL injection risk.`,
|
|
1943
|
-
help: this.meta.help,
|
|
1944
|
-
line: call.getStartLineNumber(),
|
|
1945
|
-
column: 1
|
|
1946
|
-
});
|
|
1947
|
-
}
|
|
1948
|
-
}
|
|
1949
|
-
};
|
|
1950
|
-
|
|
1951
1928
|
//#endregion
|
|
1952
1929
|
//#region src/rules/security/no-weak-crypto.ts
|
|
1953
1930
|
const WEAK_ALGORITHMS = new Set(["md5", "sha1"]);
|
|
@@ -1979,34 +1956,6 @@ const noWeakCrypto = {
|
|
|
1979
1956
|
}
|
|
1980
1957
|
};
|
|
1981
1958
|
|
|
1982
|
-
//#endregion
|
|
1983
|
-
//#region src/rules/security/no-wildcard-cors.ts
|
|
1984
|
-
const noWildcardCors = {
|
|
1985
|
-
meta: {
|
|
1986
|
-
id: "security/no-wildcard-cors",
|
|
1987
|
-
category: "security",
|
|
1988
|
-
severity: "error",
|
|
1989
|
-
description: "CORS should not be configured with origin: '*' or origin: true — allows any domain",
|
|
1990
|
-
help: "Specify allowed origins explicitly instead of using wildcard CORS."
|
|
1991
|
-
},
|
|
1992
|
-
check(context) {
|
|
1993
|
-
const propertyAssignments = context.sourceFile.getDescendantsOfKind(SyntaxKind.PropertyAssignment);
|
|
1994
|
-
for (const prop of propertyAssignments) {
|
|
1995
|
-
if (prop.getName() !== "origin") continue;
|
|
1996
|
-
const initializer = prop.getInitializer();
|
|
1997
|
-
if (!initializer) continue;
|
|
1998
|
-
const text = initializer.getText();
|
|
1999
|
-
if (text === "'*'" || text === "\"*\"" || text === "true") context.report({
|
|
2000
|
-
filePath: context.filePath,
|
|
2001
|
-
message: `CORS configured with origin: ${text} — allows requests from any domain.`,
|
|
2002
|
-
help: this.meta.help,
|
|
2003
|
-
line: prop.getStartLineNumber(),
|
|
2004
|
-
column: 1
|
|
2005
|
-
});
|
|
2006
|
-
}
|
|
2007
|
-
}
|
|
2008
|
-
};
|
|
2009
|
-
|
|
2010
1959
|
//#endregion
|
|
2011
1960
|
//#region src/rules/security/require-auth-guard.ts
|
|
2012
1961
|
const requireAuthGuard = {
|
|
@@ -2093,8 +2042,6 @@ const allRules = [
|
|
|
2093
2042
|
requireInjectDecorator,
|
|
2094
2043
|
noMissingInjectable,
|
|
2095
2044
|
noHardcodedSecrets,
|
|
2096
|
-
noWildcardCors,
|
|
2097
|
-
noUnsafeRawQuery,
|
|
2098
2045
|
requireAuthGuard,
|
|
2099
2046
|
noEval,
|
|
2100
2047
|
noWeakCrypto,
|
|
@@ -2195,7 +2142,13 @@ const DEFAULT_CONFIG = {
|
|
|
2195
2142
|
"**/*.spec.ts",
|
|
2196
2143
|
"**/*.test.ts",
|
|
2197
2144
|
"**/*.e2e-spec.ts",
|
|
2198
|
-
"**/*.
|
|
2145
|
+
"**/*.e2e-test.ts",
|
|
2146
|
+
"**/*.d.ts",
|
|
2147
|
+
"**/test/**",
|
|
2148
|
+
"**/tests/**",
|
|
2149
|
+
"**/__tests__/**",
|
|
2150
|
+
"**/__mocks__/**",
|
|
2151
|
+
"**/__fixtures__/**"
|
|
2199
2152
|
]
|
|
2200
2153
|
};
|
|
2201
2154
|
|