@apiposture/cli 1.0.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.
Files changed (110) hide show
  1. package/.apiposture.json.example +56 -0
  2. package/.github/workflows/publish.yml +38 -0
  3. package/.github/workflows/test.yml +42 -0
  4. package/LICENSE +21 -0
  5. package/README.md +156 -0
  6. package/dist/cli/commands/license/activate.d.ts +3 -0
  7. package/dist/cli/commands/license/activate.js +35 -0
  8. package/dist/cli/commands/license/deactivate.d.ts +3 -0
  9. package/dist/cli/commands/license/deactivate.js +28 -0
  10. package/dist/cli/commands/license/status.d.ts +3 -0
  11. package/dist/cli/commands/license/status.js +36 -0
  12. package/dist/cli/commands/scan.d.ts +3 -0
  13. package/dist/cli/commands/scan.js +211 -0
  14. package/dist/cli/options.d.ts +27 -0
  15. package/dist/cli/options.js +30 -0
  16. package/dist/core/analysis/project-analyzer.d.ts +16 -0
  17. package/dist/core/analysis/project-analyzer.js +54 -0
  18. package/dist/core/analysis/source-file-loader.d.ts +32 -0
  19. package/dist/core/analysis/source-file-loader.js +155 -0
  20. package/dist/core/authorization/authorization-extractor.d.ts +11 -0
  21. package/dist/core/authorization/authorization-extractor.js +2 -0
  22. package/dist/core/authorization/express-auth-extractor.d.ts +10 -0
  23. package/dist/core/authorization/express-auth-extractor.js +106 -0
  24. package/dist/core/authorization/global-auth-analyzer.d.ts +12 -0
  25. package/dist/core/authorization/global-auth-analyzer.js +74 -0
  26. package/dist/core/authorization/nestjs-auth-extractor.d.ts +13 -0
  27. package/dist/core/authorization/nestjs-auth-extractor.js +142 -0
  28. package/dist/core/configuration/config-loader.d.ts +27 -0
  29. package/dist/core/configuration/config-loader.js +72 -0
  30. package/dist/core/configuration/suppression-matcher.d.ts +14 -0
  31. package/dist/core/configuration/suppression-matcher.js +79 -0
  32. package/dist/core/discovery/discoverer-interface.d.ts +7 -0
  33. package/dist/core/discovery/discoverer-interface.js +2 -0
  34. package/dist/core/discovery/express-discoverer.d.ts +20 -0
  35. package/dist/core/discovery/express-discoverer.js +223 -0
  36. package/dist/core/discovery/fastify-discoverer.d.ts +19 -0
  37. package/dist/core/discovery/fastify-discoverer.js +249 -0
  38. package/dist/core/discovery/framework-detector.d.ts +9 -0
  39. package/dist/core/discovery/framework-detector.js +61 -0
  40. package/dist/core/discovery/index.d.ts +8 -0
  41. package/dist/core/discovery/index.js +8 -0
  42. package/dist/core/discovery/koa-discoverer.d.ts +16 -0
  43. package/dist/core/discovery/koa-discoverer.js +151 -0
  44. package/dist/core/discovery/nestjs-discoverer.d.ts +16 -0
  45. package/dist/core/discovery/nestjs-discoverer.js +180 -0
  46. package/dist/core/discovery/route-group-registry.d.ts +18 -0
  47. package/dist/core/discovery/route-group-registry.js +50 -0
  48. package/dist/core/licensing/license-context.d.ts +17 -0
  49. package/dist/core/licensing/license-context.js +15 -0
  50. package/dist/core/licensing/license-features.d.ts +14 -0
  51. package/dist/core/licensing/license-features.js +47 -0
  52. package/dist/core/models/authorization-info.d.ts +13 -0
  53. package/dist/core/models/authorization-info.js +25 -0
  54. package/dist/core/models/endpoint-type.d.ts +8 -0
  55. package/dist/core/models/endpoint-type.js +12 -0
  56. package/dist/core/models/endpoint.d.ts +16 -0
  57. package/dist/core/models/endpoint.js +16 -0
  58. package/dist/core/models/finding.d.ts +19 -0
  59. package/dist/core/models/finding.js +8 -0
  60. package/dist/core/models/http-method.d.ts +14 -0
  61. package/dist/core/models/http-method.js +25 -0
  62. package/dist/core/models/index.d.ts +10 -0
  63. package/dist/core/models/index.js +10 -0
  64. package/dist/core/models/scan-result.d.ts +21 -0
  65. package/dist/core/models/scan-result.js +35 -0
  66. package/dist/core/models/security-classification.d.ts +8 -0
  67. package/dist/core/models/security-classification.js +12 -0
  68. package/dist/core/models/severity.d.ts +11 -0
  69. package/dist/core/models/severity.js +23 -0
  70. package/dist/core/models/source-location.d.ts +7 -0
  71. package/dist/core/models/source-location.js +4 -0
  72. package/dist/index.d.ts +3 -0
  73. package/dist/index.js +23 -0
  74. package/dist/licensing/license-manager.d.ts +38 -0
  75. package/dist/licensing/license-manager.js +184 -0
  76. package/dist/output/accessibility-helper.d.ts +22 -0
  77. package/dist/output/accessibility-helper.js +98 -0
  78. package/dist/output/formatter-interface.d.ts +11 -0
  79. package/dist/output/formatter-interface.js +2 -0
  80. package/dist/output/index.d.ts +6 -0
  81. package/dist/output/index.js +6 -0
  82. package/dist/output/json-formatter.d.ts +7 -0
  83. package/dist/output/json-formatter.js +72 -0
  84. package/dist/output/markdown-formatter.d.ts +10 -0
  85. package/dist/output/markdown-formatter.js +114 -0
  86. package/dist/output/terminal-formatter.d.ts +12 -0
  87. package/dist/output/terminal-formatter.js +82 -0
  88. package/dist/rules/consistency/controller-action-conflict.d.ts +19 -0
  89. package/dist/rules/consistency/controller-action-conflict.js +40 -0
  90. package/dist/rules/consistency/missing-auth-on-writes.d.ts +21 -0
  91. package/dist/rules/consistency/missing-auth-on-writes.js +59 -0
  92. package/dist/rules/exposure/allow-anonymous-on-write.d.ts +20 -0
  93. package/dist/rules/exposure/allow-anonymous-on-write.js +42 -0
  94. package/dist/rules/exposure/public-without-explicit-intent.d.ts +20 -0
  95. package/dist/rules/exposure/public-without-explicit-intent.js +58 -0
  96. package/dist/rules/index.d.ts +11 -0
  97. package/dist/rules/index.js +11 -0
  98. package/dist/rules/privilege/excessive-role-access.d.ts +20 -0
  99. package/dist/rules/privilege/excessive-role-access.js +36 -0
  100. package/dist/rules/privilege/weak-role-naming.d.ts +20 -0
  101. package/dist/rules/privilege/weak-role-naming.js +50 -0
  102. package/dist/rules/rule-engine.d.ts +15 -0
  103. package/dist/rules/rule-engine.js +52 -0
  104. package/dist/rules/rule-interface.d.ts +16 -0
  105. package/dist/rules/rule-interface.js +2 -0
  106. package/dist/rules/surface/sensitive-route-keywords.d.ts +20 -0
  107. package/dist/rules/surface/sensitive-route-keywords.js +63 -0
  108. package/dist/rules/surface/unprotected-endpoint.d.ts +20 -0
  109. package/dist/rules/surface/unprotected-endpoint.js +61 -0
  110. package/package.json +60 -0
@@ -0,0 +1,63 @@
1
+ import { createFinding } from '../../core/models/finding.js';
2
+ import { Severity } from '../../core/models/severity.js';
3
+ import { SecurityClassification } from '../../core/models/security-classification.js';
4
+ /**
5
+ * AP007: Sensitive route keywords
6
+ *
7
+ * Detects publicly accessible routes that contain sensitive keywords
8
+ * like 'admin', 'debug', 'internal', 'export', etc. These routes
9
+ * typically should not be publicly accessible.
10
+ */
11
+ export class SensitiveRouteKeywords {
12
+ id = 'AP007';
13
+ name = 'Sensitive route keywords';
14
+ description = 'Public route contains sensitive keywords';
15
+ severity = Severity.Medium;
16
+ sensitiveKeywords = [
17
+ 'admin',
18
+ 'debug',
19
+ 'internal',
20
+ 'export',
21
+ 'import',
22
+ 'backup',
23
+ 'config',
24
+ 'settings',
25
+ 'system',
26
+ 'management',
27
+ 'dashboard',
28
+ 'metrics',
29
+ 'logs',
30
+ 'audit',
31
+ 'secret',
32
+ 'private',
33
+ 'hidden',
34
+ 'test',
35
+ 'dev',
36
+ 'staging',
37
+ ];
38
+ evaluate(endpoint) {
39
+ const findings = [];
40
+ const auth = endpoint.authorization;
41
+ // Only check public endpoints
42
+ if (auth.classification !== SecurityClassification.Public) {
43
+ return findings;
44
+ }
45
+ const routeLower = endpoint.route.toLowerCase();
46
+ const foundKeywords = this.sensitiveKeywords.filter((keyword) => routeLower.includes(keyword));
47
+ if (foundKeywords.length > 0) {
48
+ findings.push(createFinding({
49
+ ruleId: this.id,
50
+ ruleName: this.name,
51
+ severity: this.severity,
52
+ message: `${endpoint.method} ${endpoint.route} is public but contains sensitive keywords: ${foundKeywords.join(', ')}`,
53
+ endpoint,
54
+ location: endpoint.location,
55
+ recommendation: `Routes containing "${foundKeywords[0]}" typically indicate sensitive functionality ` +
56
+ 'that should require authentication. Add authentication middleware or guards, ' +
57
+ 'or rename the route if it is truly meant to be public.',
58
+ }));
59
+ }
60
+ return findings;
61
+ }
62
+ }
63
+ //# sourceMappingURL=sensitive-route-keywords.js.map
@@ -0,0 +1,20 @@
1
+ import { Endpoint } from '../../core/models/endpoint.js';
2
+ import { Finding } from '../../core/models/finding.js';
3
+ import { Severity } from '../../core/models/severity.js';
4
+ import { SecurityRule } from '../rule-interface.js';
5
+ /**
6
+ * AP008: Unprotected endpoint
7
+ *
8
+ * Detects endpoints with no authentication middleware chain at all.
9
+ * This is different from AP001 in that it specifically checks for
10
+ * the absence of any middleware, not just auth middleware.
11
+ */
12
+ export declare class UnprotectedEndpoint implements SecurityRule {
13
+ readonly id = "AP008";
14
+ readonly name = "Unprotected endpoint";
15
+ readonly description = "Endpoint has no middleware chain for protection";
16
+ readonly severity = Severity.High;
17
+ evaluate(endpoint: Endpoint): Finding[];
18
+ private getRecommendation;
19
+ }
20
+ //# sourceMappingURL=unprotected-endpoint.d.ts.map
@@ -0,0 +1,61 @@
1
+ import { createFinding } from '../../core/models/finding.js';
2
+ import { Severity } from '../../core/models/severity.js';
3
+ import { SecurityClassification } from '../../core/models/security-classification.js';
4
+ /**
5
+ * AP008: Unprotected endpoint
6
+ *
7
+ * Detects endpoints with no authentication middleware chain at all.
8
+ * This is different from AP001 in that it specifically checks for
9
+ * the absence of any middleware, not just auth middleware.
10
+ */
11
+ export class UnprotectedEndpoint {
12
+ id = 'AP008';
13
+ name = 'Unprotected endpoint';
14
+ description = 'Endpoint has no middleware chain for protection';
15
+ severity = Severity.High;
16
+ evaluate(endpoint) {
17
+ const findings = [];
18
+ const auth = endpoint.authorization;
19
+ // Only flag if:
20
+ // 1. It's public (no auth)
21
+ // 2. No middleware chain at all
22
+ // 3. No guards
23
+ // 4. Not explicitly marked as public
24
+ if (auth.classification === SecurityClassification.Public &&
25
+ auth.middlewareChain.length === 0 &&
26
+ auth.guardNames.length === 0 &&
27
+ !auth.isExplicitlyPublic) {
28
+ findings.push(createFinding({
29
+ ruleId: this.id,
30
+ ruleName: this.name,
31
+ severity: this.severity,
32
+ message: `${endpoint.method} ${endpoint.route} has no middleware chain`,
33
+ endpoint,
34
+ location: endpoint.location,
35
+ recommendation: this.getRecommendation(endpoint),
36
+ }));
37
+ }
38
+ return findings;
39
+ }
40
+ getRecommendation(endpoint) {
41
+ switch (endpoint.type) {
42
+ case 'express':
43
+ return ('This endpoint has no middleware. Consider adding: ' +
44
+ '(1) Authentication middleware, (2) Rate limiting, (3) Input validation, ' +
45
+ '(4) Request logging. Example: app.get("/path", auth, validate, handler)');
46
+ case 'nestjs':
47
+ return ('This endpoint has no guards or interceptors. Consider adding: ' +
48
+ '(1) @UseGuards() for authentication, (2) @UseInterceptors() for logging, ' +
49
+ '(3) @UsePipes() for validation.');
50
+ case 'fastify':
51
+ return ('This endpoint has no hooks. Consider adding preHandler hooks for ' +
52
+ 'authentication, validation, and logging.');
53
+ case 'koa':
54
+ return ('This endpoint has no middleware chain. Consider adding middleware for ' +
55
+ 'authentication, validation, and error handling.');
56
+ default:
57
+ return 'Add middleware for authentication, validation, and security.';
58
+ }
59
+ }
60
+ }
61
+ //# sourceMappingURL=unprotected-endpoint.js.map
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@apiposture/cli",
3
+ "version": "1.0.1",
4
+ "description": "Static source-code analysis CLI for Node.js API frameworks to identify authorization misconfigurations and security risks",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "apiposture": "dist/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsx src/index.ts",
13
+ "start": "node dist/index.js",
14
+ "test": "vitest run",
15
+ "test:watch": "vitest",
16
+ "lint": "eslint src --ext .ts",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "security",
21
+ "api",
22
+ "authorization",
23
+ "express",
24
+ "nestjs",
25
+ "fastify",
26
+ "koa",
27
+ "cli",
28
+ "static-analysis"
29
+ ],
30
+ "author": "",
31
+ "license": "MIT",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/BlagoCuljak/ApiPosture.Node.js"
35
+ },
36
+ "bugs": {
37
+ "url": "https://github.com/BlagoCuljak/ApiPosture.Node.js/issues"
38
+ },
39
+ "homepage": "https://github.com/BlagoCuljak/ApiPosture.Node.js#readme",
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ },
43
+ "dependencies": {
44
+ "axios": "^1.6.0",
45
+ "chalk": "^5.3.0",
46
+ "cli-table3": "^0.6.3",
47
+ "commander": "^12.0.0",
48
+ "glob": "^10.3.0",
49
+ "ora": "^8.0.1"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^20.10.0",
53
+ "@typescript-eslint/eslint-plugin": "^6.18.0",
54
+ "@typescript-eslint/parser": "^6.18.0",
55
+ "eslint": "^8.56.0",
56
+ "tsx": "^4.7.0",
57
+ "typescript": "^5.3.0",
58
+ "vitest": "^1.0.0"
59
+ }
60
+ }