penthera 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +168 -0
  2. package/LICENSE +21 -0
  3. package/README.md +561 -0
  4. package/SECURITY.md +31 -0
  5. package/bin/penthera.js +3 -0
  6. package/bin/scan.js +11 -0
  7. package/docs/index.md +60 -0
  8. package/docs/owasp-wstg-coverage.md +79 -0
  9. package/lib/adaptive-probes.js +334 -0
  10. package/lib/attack-surface.js +149 -0
  11. package/lib/audit-loop.js +170 -0
  12. package/lib/blackbox/adaptive-scan.js +137 -0
  13. package/lib/blackbox/client-auth.js +60 -0
  14. package/lib/blackbox/email-dns.js +135 -0
  15. package/lib/blackbox/headers.js +91 -0
  16. package/lib/blackbox/idor.js +113 -0
  17. package/lib/blackbox/jwt.js +73 -0
  18. package/lib/blackbox/oauth.js +50 -0
  19. package/lib/blackbox/openapi.js +227 -0
  20. package/lib/compliance/incident.js +169 -0
  21. package/lib/compliance/index.js +163 -0
  22. package/lib/compliance/policy-pack.js +293 -0
  23. package/lib/compliance/questionnaire.js +113 -0
  24. package/lib/compliance/readiness.js +229 -0
  25. package/lib/compliance/self-assessment.js +66 -0
  26. package/lib/crawler.js +257 -0
  27. package/lib/fuzzer.js +233 -0
  28. package/lib/injections.js +676 -0
  29. package/lib/jurisdictions/nl.json +48 -0
  30. package/lib/knowledge-graph.js +232 -0
  31. package/lib/machine.js +814 -0
  32. package/lib/nuclei-loader.js +283 -0
  33. package/lib/owasp-wstg.js +204 -0
  34. package/lib/params.js +235 -0
  35. package/lib/plugins.js +70 -0
  36. package/lib/provenance.js +82 -0
  37. package/lib/recon.js +193 -0
  38. package/lib/remediation/index.js +219 -0
  39. package/lib/retirejs.js +239 -0
  40. package/lib/supply-chain.js +131 -0
  41. package/lib/templates.js +364 -0
  42. package/lib/tls.js +145 -0
  43. package/lib/whitebox/frameworks.js +139 -0
  44. package/lib/whitebox/sbom.js +144 -0
  45. package/lib/whitebox/secrets.js +111 -0
  46. package/package.json +76 -0
  47. package/pentest.config.example.js +82 -0
  48. package/scripts/package-skill.sh +16 -0
  49. package/skills/penthera/SKILL.md +272 -0
  50. package/skills/penthera/references/authorization.md +36 -0
  51. package/skills/penthera/references/output-and-ci.md +109 -0
  52. package/skills/penthera/references/profiles-and-flags.md +74 -0
  53. package/skills/penthera/references/remediation.md +267 -0
  54. package/skills/penthera/references/reporting.md +113 -0
  55. package/skills/penthera/references/secure-defaults.md +41 -0
  56. package/skills/penthera/references/troubleshooting.md +100 -0
  57. package/skills/penthera/scripts/preflight.sh +66 -0
  58. package/skills/penthera/scripts/validate-report.mjs +53 -0
  59. package/skills/penthera/scripts/validate-report.sh +8 -0
  60. package/src/cli/ansi.js +70 -0
  61. package/src/cli/baseline.js +37 -0
  62. package/src/cli/merge-results.js +71 -0
  63. package/src/cli/onboarding.js +216 -0
  64. package/src/cli/profiles.js +53 -0
  65. package/src/cli/prompt.js +64 -0
  66. package/src/cli/run-scan.js +152 -0
  67. package/src/cli.js +536 -0
  68. package/src/report/markdown.js +182 -0
  69. package/src/reporter.js +512 -0
  70. package/src/scan-repo.js +205 -0
  71. package/src/scan-url.js +563 -0
  72. package/src/utils/auth.js +13 -0
  73. package/src/utils/http.js +38 -0
  74. package/src/utils/url.js +54 -0
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Penthera, Repo Scanner (white-box)
3
+ *
4
+ * Analyzes source code for security patterns:
5
+ * 1. Auto-detect API route directory (Next.js App/Pages Router)
6
+ * 2. Discover attack surface from filesystem
7
+ * 3. Classify trust boundaries
8
+ * 4. Detect risky code patterns
9
+ */
10
+ import { existsSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { discoverAttackSurface, groupByTrustBoundary, findRiskyRoutes } from "../lib/attack-surface.js";
13
+ import { scanSecrets } from "../lib/whitebox/secrets.js";
14
+ import { discoverFrameworkRoutes } from "../lib/whitebox/frameworks.js";
15
+
16
+ /** Auto-detect API routes directory (Next.js, Express-style, etc.). */
17
+ function findApiRoot(repoPath) {
18
+ const candidates = [
19
+ "app/api",
20
+ "src/app/api",
21
+ "pages/api",
22
+ "src/pages/api",
23
+ "routes",
24
+ "src/routes",
25
+ "server/routes",
26
+ "api",
27
+ "src/api",
28
+ "src/server/routes",
29
+ ];
30
+ for (const candidate of candidates) {
31
+ const full = join(repoPath, candidate);
32
+ if (existsSync(full)) return full;
33
+ }
34
+ return null;
35
+ }
36
+
37
+ /**
38
+ * Run a white-box scan against a local repository.
39
+ *
40
+ * @param {string} repoPath - Path to the repository root
41
+ * @param {object} opts - { apiRoot, onPhase }
42
+ * @returns {object} Scan result with findings
43
+ */
44
+ export async function scanRepo(repoPath, opts = {}) {
45
+ const progress = opts.onPhase || (() => {});
46
+ const startTime = Date.now();
47
+
48
+ const result = {
49
+ target: repoPath,
50
+ mode: "repo",
51
+ timestamp: new Date().toISOString(),
52
+ duration: 0,
53
+ apiRoot: null,
54
+ attackSurface: [],
55
+ boundaries: {},
56
+ executedProbes: [],
57
+ findings: [],
58
+ };
59
+
60
+ // ── Phase 1: Secret scanning (whole repo) ─────────────────────────────
61
+ progress("Scanning for hardcoded secrets...");
62
+ result.findings.push(...scanSecrets(repoPath));
63
+ result.executedProbes.push("secret-scanning");
64
+
65
+ // ── Phase 2: Locate API root ───────────────────────────────────────────
66
+ progress("Locating API routes directory...");
67
+ const apiRoot = opts.apiRoot || findApiRoot(repoPath);
68
+ let useFrameworkScan = false;
69
+
70
+ if (!apiRoot) {
71
+ progress("Scanning for Express/Hono/Fastify routes...");
72
+ const fwRoutes = discoverFrameworkRoutes(repoPath);
73
+ if (fwRoutes.length > 0) {
74
+ result.attackSurface = fwRoutes;
75
+ result.boundaries = groupByTrustBoundary(fwRoutes);
76
+ result.findings.push({
77
+ severity: "info",
78
+ title: `Found ${fwRoutes.length} API routes (Express/Hono/Fastify)`,
79
+ description: "Discovered via route pattern matching, not Next.js app/api.",
80
+ category: "config",
81
+ source: "framework-scan",
82
+ });
83
+ useFrameworkScan = true;
84
+ } else {
85
+ result.findings.push({
86
+ severity: "info",
87
+ title: "No API routes directory found",
88
+ description: `Searched Next.js, Express, Hono, and Fastify patterns in ${repoPath}. Use --api-root to specify.`,
89
+ category: "config",
90
+ source: "repo-scan",
91
+ });
92
+ result.duration = Date.now() - startTime;
93
+ return result;
94
+ }
95
+ } else if (!existsSync(apiRoot)) {
96
+ result.findings.push({
97
+ severity: "info",
98
+ title: `API root not found: ${apiRoot}`,
99
+ description: "The specified --api-root path does not exist.",
100
+ category: "config",
101
+ source: "repo-scan",
102
+ });
103
+ result.duration = Date.now() - startTime;
104
+ return result;
105
+ }
106
+
107
+ if (!useFrameworkScan) {
108
+ result.apiRoot = apiRoot;
109
+
110
+ // ── Phase 2: Discover attack surface ───────────────────────────────────
111
+ progress("Discovering attack surface from filesystem...");
112
+ result.attackSurface = discoverAttackSurface(apiRoot);
113
+
114
+ if (result.attackSurface.length === 0) {
115
+ result.findings.push({
116
+ severity: "info",
117
+ title: "No API routes found",
118
+ description: `No route.js/ts files found under ${apiRoot}`,
119
+ category: "config",
120
+ source: "repo-scan",
121
+ });
122
+ result.duration = Date.now() - startTime;
123
+ return result;
124
+ }
125
+
126
+ // ── Phase 3: Trust boundary analysis ───────────────────────────────────
127
+ progress("Classifying trust boundaries...");
128
+ result.boundaries = groupByTrustBoundary(result.attackSurface);
129
+ }
130
+
131
+ // Flag public routes that call external services without rate limiting
132
+ for (const route of result.boundaries.public || []) {
133
+ if (route.externalCalls.length > 0 && !route.auth.includes("rate-limited")) {
134
+ result.findings.push({
135
+ severity: "medium",
136
+ title: `Public endpoint without rate limiting: ${route.url}`,
137
+ description: `Calls ${route.externalCalls.join(", ")} \u2014 abuse/cost vector`,
138
+ url: route.url,
139
+ category: "rate-limiting",
140
+ source: "trust-boundary",
141
+ });
142
+ }
143
+ }
144
+
145
+ // Flag routes with no auth that probably need it (heuristic: has user inputs)
146
+ for (const route of result.boundaries.public || []) {
147
+ if (route.inputs.length > 3) {
148
+ result.findings.push({
149
+ severity: "low",
150
+ title: `Public route accepts many inputs: ${route.url}`,
151
+ description: `Inputs: ${route.inputs.join(", ")} \u2014 verify auth is intentionally absent`,
152
+ url: route.url,
153
+ category: "auth",
154
+ source: "trust-boundary",
155
+ });
156
+ }
157
+ }
158
+
159
+ // ── Phase 4: Risky pattern detection ───────────────────────────────────
160
+ progress("Detecting risky code patterns...");
161
+ const risky = findRiskyRoutes(result.attackSurface);
162
+
163
+ for (const route of risky) {
164
+ const risks = route.securityPatterns.filter((p) => p.startsWith("RISK:"));
165
+ for (const risk of risks) {
166
+ const name = risk.replace("RISK:", "");
167
+ const sevMap = {
168
+ eval: "high",
169
+ innerHTML: "high",
170
+ "command-injection": "critical",
171
+ "error-leak": "low",
172
+ "cors-wildcard": "medium",
173
+ "json-parse-unguarded": "low",
174
+ };
175
+ result.findings.push({
176
+ severity: sevMap[name] || "medium",
177
+ title: `${name} in ${route.url}`,
178
+ description: `Detected in ${route.filePath}`,
179
+ url: route.url,
180
+ category: "code-pattern",
181
+ source: "static-analysis",
182
+ });
183
+ }
184
+ }
185
+
186
+ // Check: more public than admin routes is a smell
187
+ const pub = (result.boundaries.public || []).length;
188
+ const adm = (result.boundaries.adminAuth || []).length;
189
+ if (pub > adm && adm > 0) {
190
+ result.findings.push({
191
+ severity: "info",
192
+ title: `More public routes (${pub}) than admin routes (${adm})`,
193
+ description: "Review if all public endpoints should truly be unauthenticated",
194
+ category: "auth",
195
+ source: "trust-boundary",
196
+ });
197
+ }
198
+
199
+ // Route analysis and risky-pattern detection ran (this point is only reached
200
+ // when an API surface was found; the no-routes paths return earlier).
201
+ result.executedProbes.push("trust-boundary", "risky-patterns");
202
+
203
+ result.duration = Date.now() - startTime;
204
+ return result;
205
+ }