nestjs-doctor 0.3.0 → 0.3.2
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 +45 -71
- package/dist/api/index.d.cts.map +1 -1
- package/dist/api/index.d.mts.map +1 -1
- package/dist/api/index.mjs +45 -71
- package/dist/api/index.mjs.map +1 -1
- package/dist/cli/index.mjs +55 -78
- package/package.json +1 -1
- package/skill/SKILL.md +2 -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
|
@@ -467,6 +467,18 @@ const noGodService = {
|
|
|
467
467
|
|
|
468
468
|
//#endregion
|
|
469
469
|
//#region src/rules/architecture/no-manual-instantiation.ts
|
|
470
|
+
const DI_ONLY_SUFFIXES = [
|
|
471
|
+
"Service",
|
|
472
|
+
"Repository",
|
|
473
|
+
"Gateway",
|
|
474
|
+
"Resolver"
|
|
475
|
+
];
|
|
476
|
+
const CONTEXT_AWARE_SUFFIXES = [
|
|
477
|
+
"Guard",
|
|
478
|
+
"Interceptor",
|
|
479
|
+
"Pipe",
|
|
480
|
+
"Filter"
|
|
481
|
+
];
|
|
470
482
|
const noManualInstantiation = {
|
|
471
483
|
meta: {
|
|
472
484
|
id: "architecture/no-manual-instantiation",
|
|
@@ -479,7 +491,16 @@ const noManualInstantiation = {
|
|
|
479
491
|
const newExpressions = context.sourceFile.getDescendantsOfKind(ts_morph.SyntaxKind.NewExpression);
|
|
480
492
|
for (const expr of newExpressions) {
|
|
481
493
|
const exprText = expr.getExpression().getText();
|
|
482
|
-
|
|
494
|
+
const isDiOnly = DI_ONLY_SUFFIXES.some((s) => exprText.endsWith(s));
|
|
495
|
+
const isContextAware = CONTEXT_AWARE_SUFFIXES.some((s) => exprText.endsWith(s));
|
|
496
|
+
if (!(isDiOnly || isContextAware)) continue;
|
|
497
|
+
if (isContextAware) {
|
|
498
|
+
if (expr.getFirstAncestorByKind(ts_morph.SyntaxKind.Decorator)) continue;
|
|
499
|
+
const inMethod = expr.getFirstAncestorByKind(ts_morph.SyntaxKind.MethodDeclaration);
|
|
500
|
+
const inConstructor = expr.getFirstAncestorByKind(ts_morph.SyntaxKind.Constructor);
|
|
501
|
+
if (!(inMethod || inConstructor)) continue;
|
|
502
|
+
}
|
|
503
|
+
context.report({
|
|
483
504
|
filePath: context.filePath,
|
|
484
505
|
message: `Manual instantiation of '${exprText}' detected. Use dependency injection instead.`,
|
|
485
506
|
help: this.meta.help,
|
|
@@ -905,7 +926,7 @@ const noDuplicateRoutes = {
|
|
|
905
926
|
id: "correctness/no-duplicate-routes",
|
|
906
927
|
category: "correctness",
|
|
907
928
|
severity: "error",
|
|
908
|
-
description: "Same HTTP method + route path should not appear twice in a single controller",
|
|
929
|
+
description: "Same HTTP method + route path + version should not appear twice in a single controller",
|
|
909
930
|
help: "Remove or rename one of the duplicate route handlers."
|
|
910
931
|
},
|
|
911
932
|
check(context) {
|
|
@@ -917,7 +938,8 @@ const noDuplicateRoutes = {
|
|
|
917
938
|
if (!HTTP_METHOD_DECORATORS.has(decoratorName)) continue;
|
|
918
939
|
const args = decorator.getArguments();
|
|
919
940
|
const path = args.length > 0 ? args[0].getText() : "\"\"";
|
|
920
|
-
const
|
|
941
|
+
const versionDecorator = method.getDecorator("Version");
|
|
942
|
+
const routeKey = `${decoratorName}:${path}:${versionDecorator ? versionDecorator.getArguments()[0]?.getText() ?? "" : ""}`;
|
|
921
943
|
const existing = routeMap.get(routeKey);
|
|
922
944
|
if (existing) context.report({
|
|
923
945
|
filePath: context.filePath,
|
|
@@ -984,6 +1006,7 @@ const noMissingFilterCatch = {
|
|
|
984
1006
|
check(context) {
|
|
985
1007
|
for (const cls of context.sourceFile.getClasses()) {
|
|
986
1008
|
if (!hasDecorator(cls, "Catch")) continue;
|
|
1009
|
+
if (cls.getExtends()) continue;
|
|
987
1010
|
if (!cls.getMethods().some((m) => m.getName() === "catch")) context.report({
|
|
988
1011
|
filePath: context.filePath,
|
|
989
1012
|
message: `Exception filter '${cls.getName()}' has @Catch() but is missing the 'catch()' method.`,
|
|
@@ -1010,6 +1033,7 @@ const noMissingGuardMethod = {
|
|
|
1010
1033
|
const name = cls.getName() ?? "";
|
|
1011
1034
|
if (!name.endsWith("Guard")) continue;
|
|
1012
1035
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1036
|
+
if (cls.getExtends()) continue;
|
|
1013
1037
|
if (!cls.getMethods().some((m) => m.getName() === "canActivate")) context.report({
|
|
1014
1038
|
filePath: context.filePath,
|
|
1015
1039
|
message: `Guard '${name}' is missing the 'canActivate()' method.`,
|
|
@@ -1080,6 +1104,7 @@ const noMissingInterceptorMethod = {
|
|
|
1080
1104
|
const name = cls.getName() ?? "";
|
|
1081
1105
|
if (!name.endsWith("Interceptor")) continue;
|
|
1082
1106
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1107
|
+
if (cls.getExtends()) continue;
|
|
1083
1108
|
if (!cls.getMethods().some((m) => m.getName() === "intercept")) context.report({
|
|
1084
1109
|
filePath: context.filePath,
|
|
1085
1110
|
message: `Interceptor '${name}' is missing the 'intercept()' method.`,
|
|
@@ -1132,6 +1157,7 @@ const noMissingPipeMethod = {
|
|
|
1132
1157
|
const name = cls.getName() ?? "";
|
|
1133
1158
|
if (!name.endsWith("Pipe")) continue;
|
|
1134
1159
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1160
|
+
if (cls.getExtends()) continue;
|
|
1135
1161
|
if (!cls.getMethods().some((m) => m.getName() === "transform")) context.report({
|
|
1136
1162
|
filePath: context.filePath,
|
|
1137
1163
|
message: `Pipe '${name}' is missing the 'transform()' method.`,
|
|
@@ -1825,7 +1851,7 @@ const noExposedStackTrace = {
|
|
|
1825
1851
|
//#region src/rules/security/no-hardcoded-secrets.ts
|
|
1826
1852
|
const SECRET_PATTERNS = [
|
|
1827
1853
|
{
|
|
1828
|
-
pattern: /^[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1854
|
+
pattern: /^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1829
1855
|
name: "Base64 key"
|
|
1830
1856
|
},
|
|
1831
1857
|
{
|
|
@@ -1880,8 +1906,15 @@ const PLACEHOLDER_VALUES = new Set([
|
|
|
1880
1906
|
"changeme",
|
|
1881
1907
|
"password"
|
|
1882
1908
|
]);
|
|
1909
|
+
const DOT_SEPARATED_CONSTANT = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
1883
1910
|
function isSuspiciousValue(value) {
|
|
1884
|
-
|
|
1911
|
+
if (value.length < 8) return false;
|
|
1912
|
+
if (value.includes("${")) return false;
|
|
1913
|
+
if (value.startsWith("process.env")) return false;
|
|
1914
|
+
if (PLACEHOLDER_VALUES.has(value)) return false;
|
|
1915
|
+
if (value.includes(" ")) return false;
|
|
1916
|
+
if (DOT_SEPARATED_CONSTANT.test(value)) return false;
|
|
1917
|
+
return true;
|
|
1885
1918
|
}
|
|
1886
1919
|
function hasSuspiciousName(name) {
|
|
1887
1920
|
return VARIABLE_NAME_PATTERNS.some((p) => p.test(name));
|
|
@@ -1942,41 +1975,6 @@ const noHardcodedSecrets = {
|
|
|
1942
1975
|
}
|
|
1943
1976
|
};
|
|
1944
1977
|
|
|
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
1978
|
//#endregion
|
|
1981
1979
|
//#region src/rules/security/no-weak-crypto.ts
|
|
1982
1980
|
const WEAK_ALGORITHMS = new Set(["md5", "sha1"]);
|
|
@@ -2008,34 +2006,6 @@ const noWeakCrypto = {
|
|
|
2008
2006
|
}
|
|
2009
2007
|
};
|
|
2010
2008
|
|
|
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
2009
|
//#endregion
|
|
2040
2010
|
//#region src/rules/security/require-auth-guard.ts
|
|
2041
2011
|
const requireAuthGuard = {
|
|
@@ -2122,8 +2092,6 @@ const allRules = [
|
|
|
2122
2092
|
requireInjectDecorator,
|
|
2123
2093
|
noMissingInjectable,
|
|
2124
2094
|
noHardcodedSecrets,
|
|
2125
|
-
noWildcardCors,
|
|
2126
|
-
noUnsafeRawQuery,
|
|
2127
2095
|
requireAuthGuard,
|
|
2128
2096
|
noEval,
|
|
2129
2097
|
noWeakCrypto,
|
|
@@ -2224,7 +2192,13 @@ const DEFAULT_CONFIG = {
|
|
|
2224
2192
|
"**/*.spec.ts",
|
|
2225
2193
|
"**/*.test.ts",
|
|
2226
2194
|
"**/*.e2e-spec.ts",
|
|
2227
|
-
"**/*.
|
|
2195
|
+
"**/*.e2e-test.ts",
|
|
2196
|
+
"**/*.d.ts",
|
|
2197
|
+
"**/test/**",
|
|
2198
|
+
"**/tests/**",
|
|
2199
|
+
"**/__tests__/**",
|
|
2200
|
+
"**/__mocks__/**",
|
|
2201
|
+
"**/__fixtures__/**"
|
|
2228
2202
|
]
|
|
2229
2203
|
};
|
|
2230
2204
|
|
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":";;;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;;;iBCkEb,QAAA,CAAA,GAAY,OAAA;;;cC5Gf,iBAAA,SAA0B,KAAA;cAC1B,OAAA;AAAA;AAAA,cAMA,kBAAA,SAA2B,iBAAA;cAC3B,OAAA;AAAA;AAAA,cAMA,SAAA,SAAkB,iBAAA;cAClB,OAAA;AAAA;AAAA,cAMA,eAAA,SAAwB,iBAAA;cACxB,OAAA;AAAA;;;UCpBI,KAAA;EAChB,KAAA;EACA,KAAA;AAAA;AAAA,UAGgB,WAAA;EAChB,SAAA;EACA,SAAA;EACA,WAAA;EACA,IAAA;EACA,WAAA;EACA,GAAA;AAAA;AAAA,UAGgB,eAAA;EAChB,UAAA,EAAY,MAAA,CAAO,QAAA;EACnB,MAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;AAAA;AAAA,UAGgB,aAAA;EAChB,KAAA;EACA,MAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,WAAA,EAAa,UAAA;EACb,SAAA;EACA,OAAA,EAAS,WAAA;EACT,UAAA,EAAY,aAAA;EACZ,KAAA,EAAO,KAAA;EACP,OAAA,EAAS,eAAA;AAAA;AAAA,UAGO,gBAAA;EAChB,IAAA;EACA,MAAA,EAAQ,cAAA;AAAA;AAAA,UAGQ,cAAA;EAChB,QAAA,EAAU,cAAA;EACV,SAAA;EACA,UAAA;EACA,WAAA,EAAa,gBAAA;AAAA;;;;;;;;;;;iBCiBQ,QAAA,CACrB,IAAA,UACA,OAAA;EAAW,MAAA;AAAA,IAAsB,OAAA,CAFJ,cAAA;AR7C9B;;;;;;;;;;;AAAA,iBQgEsB,gBAAA,CACrB,IAAA,UACA,OAAA;EAAW,MAAA;AAAA,IAAsB,OAAA,CAFI,cAAA"}
|
package/dist/api/index.mjs
CHANGED
|
@@ -438,6 +438,18 @@ const noGodService = {
|
|
|
438
438
|
|
|
439
439
|
//#endregion
|
|
440
440
|
//#region src/rules/architecture/no-manual-instantiation.ts
|
|
441
|
+
const DI_ONLY_SUFFIXES = [
|
|
442
|
+
"Service",
|
|
443
|
+
"Repository",
|
|
444
|
+
"Gateway",
|
|
445
|
+
"Resolver"
|
|
446
|
+
];
|
|
447
|
+
const CONTEXT_AWARE_SUFFIXES = [
|
|
448
|
+
"Guard",
|
|
449
|
+
"Interceptor",
|
|
450
|
+
"Pipe",
|
|
451
|
+
"Filter"
|
|
452
|
+
];
|
|
441
453
|
const noManualInstantiation = {
|
|
442
454
|
meta: {
|
|
443
455
|
id: "architecture/no-manual-instantiation",
|
|
@@ -450,7 +462,16 @@ const noManualInstantiation = {
|
|
|
450
462
|
const newExpressions = context.sourceFile.getDescendantsOfKind(SyntaxKind.NewExpression);
|
|
451
463
|
for (const expr of newExpressions) {
|
|
452
464
|
const exprText = expr.getExpression().getText();
|
|
453
|
-
|
|
465
|
+
const isDiOnly = DI_ONLY_SUFFIXES.some((s) => exprText.endsWith(s));
|
|
466
|
+
const isContextAware = CONTEXT_AWARE_SUFFIXES.some((s) => exprText.endsWith(s));
|
|
467
|
+
if (!(isDiOnly || isContextAware)) continue;
|
|
468
|
+
if (isContextAware) {
|
|
469
|
+
if (expr.getFirstAncestorByKind(SyntaxKind.Decorator)) continue;
|
|
470
|
+
const inMethod = expr.getFirstAncestorByKind(SyntaxKind.MethodDeclaration);
|
|
471
|
+
const inConstructor = expr.getFirstAncestorByKind(SyntaxKind.Constructor);
|
|
472
|
+
if (!(inMethod || inConstructor)) continue;
|
|
473
|
+
}
|
|
474
|
+
context.report({
|
|
454
475
|
filePath: context.filePath,
|
|
455
476
|
message: `Manual instantiation of '${exprText}' detected. Use dependency injection instead.`,
|
|
456
477
|
help: this.meta.help,
|
|
@@ -876,7 +897,7 @@ const noDuplicateRoutes = {
|
|
|
876
897
|
id: "correctness/no-duplicate-routes",
|
|
877
898
|
category: "correctness",
|
|
878
899
|
severity: "error",
|
|
879
|
-
description: "Same HTTP method + route path should not appear twice in a single controller",
|
|
900
|
+
description: "Same HTTP method + route path + version should not appear twice in a single controller",
|
|
880
901
|
help: "Remove or rename one of the duplicate route handlers."
|
|
881
902
|
},
|
|
882
903
|
check(context) {
|
|
@@ -888,7 +909,8 @@ const noDuplicateRoutes = {
|
|
|
888
909
|
if (!HTTP_METHOD_DECORATORS.has(decoratorName)) continue;
|
|
889
910
|
const args = decorator.getArguments();
|
|
890
911
|
const path = args.length > 0 ? args[0].getText() : "\"\"";
|
|
891
|
-
const
|
|
912
|
+
const versionDecorator = method.getDecorator("Version");
|
|
913
|
+
const routeKey = `${decoratorName}:${path}:${versionDecorator ? versionDecorator.getArguments()[0]?.getText() ?? "" : ""}`;
|
|
892
914
|
const existing = routeMap.get(routeKey);
|
|
893
915
|
if (existing) context.report({
|
|
894
916
|
filePath: context.filePath,
|
|
@@ -955,6 +977,7 @@ const noMissingFilterCatch = {
|
|
|
955
977
|
check(context) {
|
|
956
978
|
for (const cls of context.sourceFile.getClasses()) {
|
|
957
979
|
if (!hasDecorator(cls, "Catch")) continue;
|
|
980
|
+
if (cls.getExtends()) continue;
|
|
958
981
|
if (!cls.getMethods().some((m) => m.getName() === "catch")) context.report({
|
|
959
982
|
filePath: context.filePath,
|
|
960
983
|
message: `Exception filter '${cls.getName()}' has @Catch() but is missing the 'catch()' method.`,
|
|
@@ -981,6 +1004,7 @@ const noMissingGuardMethod = {
|
|
|
981
1004
|
const name = cls.getName() ?? "";
|
|
982
1005
|
if (!name.endsWith("Guard")) continue;
|
|
983
1006
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1007
|
+
if (cls.getExtends()) continue;
|
|
984
1008
|
if (!cls.getMethods().some((m) => m.getName() === "canActivate")) context.report({
|
|
985
1009
|
filePath: context.filePath,
|
|
986
1010
|
message: `Guard '${name}' is missing the 'canActivate()' method.`,
|
|
@@ -1051,6 +1075,7 @@ const noMissingInterceptorMethod = {
|
|
|
1051
1075
|
const name = cls.getName() ?? "";
|
|
1052
1076
|
if (!name.endsWith("Interceptor")) continue;
|
|
1053
1077
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1078
|
+
if (cls.getExtends()) continue;
|
|
1054
1079
|
if (!cls.getMethods().some((m) => m.getName() === "intercept")) context.report({
|
|
1055
1080
|
filePath: context.filePath,
|
|
1056
1081
|
message: `Interceptor '${name}' is missing the 'intercept()' method.`,
|
|
@@ -1103,6 +1128,7 @@ const noMissingPipeMethod = {
|
|
|
1103
1128
|
const name = cls.getName() ?? "";
|
|
1104
1129
|
if (!name.endsWith("Pipe")) continue;
|
|
1105
1130
|
if (!hasDecorator(cls, "Injectable")) continue;
|
|
1131
|
+
if (cls.getExtends()) continue;
|
|
1106
1132
|
if (!cls.getMethods().some((m) => m.getName() === "transform")) context.report({
|
|
1107
1133
|
filePath: context.filePath,
|
|
1108
1134
|
message: `Pipe '${name}' is missing the 'transform()' method.`,
|
|
@@ -1796,7 +1822,7 @@ const noExposedStackTrace = {
|
|
|
1796
1822
|
//#region src/rules/security/no-hardcoded-secrets.ts
|
|
1797
1823
|
const SECRET_PATTERNS = [
|
|
1798
1824
|
{
|
|
1799
|
-
pattern: /^[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1825
|
+
pattern: /^(?=.*\d)[A-Za-z0-9+/]{40,}={0,2}$/,
|
|
1800
1826
|
name: "Base64 key"
|
|
1801
1827
|
},
|
|
1802
1828
|
{
|
|
@@ -1851,8 +1877,15 @@ const PLACEHOLDER_VALUES = new Set([
|
|
|
1851
1877
|
"changeme",
|
|
1852
1878
|
"password"
|
|
1853
1879
|
]);
|
|
1880
|
+
const DOT_SEPARATED_CONSTANT = /^[A-Za-z][A-Za-z0-9_]*(\.[A-Za-z][A-Za-z0-9_]*)+$/;
|
|
1854
1881
|
function isSuspiciousValue(value) {
|
|
1855
|
-
|
|
1882
|
+
if (value.length < 8) return false;
|
|
1883
|
+
if (value.includes("${")) return false;
|
|
1884
|
+
if (value.startsWith("process.env")) return false;
|
|
1885
|
+
if (PLACEHOLDER_VALUES.has(value)) return false;
|
|
1886
|
+
if (value.includes(" ")) return false;
|
|
1887
|
+
if (DOT_SEPARATED_CONSTANT.test(value)) return false;
|
|
1888
|
+
return true;
|
|
1856
1889
|
}
|
|
1857
1890
|
function hasSuspiciousName(name) {
|
|
1858
1891
|
return VARIABLE_NAME_PATTERNS.some((p) => p.test(name));
|
|
@@ -1913,41 +1946,6 @@ const noHardcodedSecrets = {
|
|
|
1913
1946
|
}
|
|
1914
1947
|
};
|
|
1915
1948
|
|
|
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
1949
|
//#endregion
|
|
1952
1950
|
//#region src/rules/security/no-weak-crypto.ts
|
|
1953
1951
|
const WEAK_ALGORITHMS = new Set(["md5", "sha1"]);
|
|
@@ -1979,34 +1977,6 @@ const noWeakCrypto = {
|
|
|
1979
1977
|
}
|
|
1980
1978
|
};
|
|
1981
1979
|
|
|
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
1980
|
//#endregion
|
|
2011
1981
|
//#region src/rules/security/require-auth-guard.ts
|
|
2012
1982
|
const requireAuthGuard = {
|
|
@@ -2093,8 +2063,6 @@ const allRules = [
|
|
|
2093
2063
|
requireInjectDecorator,
|
|
2094
2064
|
noMissingInjectable,
|
|
2095
2065
|
noHardcodedSecrets,
|
|
2096
|
-
noWildcardCors,
|
|
2097
|
-
noUnsafeRawQuery,
|
|
2098
2066
|
requireAuthGuard,
|
|
2099
2067
|
noEval,
|
|
2100
2068
|
noWeakCrypto,
|
|
@@ -2195,7 +2163,13 @@ const DEFAULT_CONFIG = {
|
|
|
2195
2163
|
"**/*.spec.ts",
|
|
2196
2164
|
"**/*.test.ts",
|
|
2197
2165
|
"**/*.e2e-spec.ts",
|
|
2198
|
-
"**/*.
|
|
2166
|
+
"**/*.e2e-test.ts",
|
|
2167
|
+
"**/*.d.ts",
|
|
2168
|
+
"**/test/**",
|
|
2169
|
+
"**/tests/**",
|
|
2170
|
+
"**/__tests__/**",
|
|
2171
|
+
"**/__mocks__/**",
|
|
2172
|
+
"**/__fixtures__/**"
|
|
2199
2173
|
]
|
|
2200
2174
|
};
|
|
2201
2175
|
|