penthera 1.6.1 → 1.7.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/CHANGELOG.md +58 -0
- package/README.md +158 -301
- package/bin/scan.js +2 -2
- package/docs/METHODOLOGY.md +236 -182
- package/docs/commands/README.md +2 -1
- package/docs/commands/init.md +114 -0
- package/docs/commands/scan.md +1 -1
- package/docs/compare/github.md +1 -1
- package/docs/dashboard.md +95 -152
- package/docs/examples/github-actions-scan.yml +1 -1
- package/docs/internal-audit.md +1 -1
- package/docs/plan.md +3 -2
- package/docs/references.bib +43 -33
- package/docs/references.md +92 -51
- package/lib/whitebox/frameworks.js +36 -0
- package/package.json +1 -1
- package/skills/penthera/SKILL.md +19 -5
- package/src/cli/deps.js +1 -1
- package/src/cli/detect.js +125 -0
- package/src/cli/onboarding.js +428 -6
- package/src/cli/prompt.js +42 -0
- package/src/cli.js +18 -5
- package/src/ui/api/data.js +30 -0
- package/src/ui/api/demo.js +13 -0
- package/src/ui/api/routes.js +1 -0
- package/src/ui/static/app.css +286 -560
- package/src/ui/static/index.html +8 -39
- package/src/ui/static/js/app.js +105 -107
- package/src/ui/static/js/center.js +388 -0
- package/src/ui/static/js/copy.js +36 -62
- package/src/ui/static/js/dom.js +0 -38
- package/src/ui/static/js/left.js +83 -0
- package/src/ui/static/js/right.js +270 -0
- package/src/ui/static/js/state.js +35 -0
- package/src/ui/static/js/views/ai.js +0 -68
- package/src/ui/static/js/views/deps.js +0 -96
- package/src/ui/static/js/views/evidence.js +0 -115
- package/src/ui/static/js/views/findings.js +0 -165
- package/src/ui/static/js/views/history.js +0 -92
- package/src/ui/static/js/views/overview.js +0 -184
- package/src/ui/static/js/views/run.js +0 -349
- package/src/ui/static/js/views/settings.js +0 -67
package/src/cli.js
CHANGED
|
@@ -51,10 +51,11 @@ function shouldRunOnboarding(positionals, opts) {
|
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
const HELP = `
|
|
54
|
-
${bold("penthera")} v${pkg.version}
|
|
54
|
+
${bold("penthera")} v${pkg.version}: Cyber Security Agent. Open-source security for the apps and AI systems you ship
|
|
55
55
|
|
|
56
56
|
${dim("Usage")}
|
|
57
|
-
$ penthera
|
|
57
|
+
$ penthera Setup wizard: detect, check, set up (TTY, no args)
|
|
58
|
+
$ penthera init [--yes] [--dry-run] The same wizard, also non-interactive
|
|
58
59
|
$ penthera <url> Scan a live URL (black-box)
|
|
59
60
|
$ penthera --repo <path> Scan a local repo (white-box)
|
|
60
61
|
$ penthera <url> --repo <path> Full scan (both)
|
|
@@ -168,7 +169,20 @@ const HELP = `
|
|
|
168
169
|
function bold(s) { return `\x1b[1m${s}\x1b[0m`; }
|
|
169
170
|
function dim(s) { return `\x1b[2m${s}\x1b[0m`; }
|
|
170
171
|
|
|
172
|
+
/** `penthera init ...`, or bare `penthera --yes` / `--dry-run`: the setup wizard. */
|
|
173
|
+
function initArgs(argv) {
|
|
174
|
+
if (argv[0] === "init") return argv.slice(1);
|
|
175
|
+
if (argv.length && argv.every((a) => ["--yes", "-y", "--dry-run"].includes(a))) return argv;
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
|
|
171
179
|
export async function run() {
|
|
180
|
+
const init = initArgs(process.argv.slice(2));
|
|
181
|
+
if (init) {
|
|
182
|
+
const { runInit } = await import("./cli/onboarding.js");
|
|
183
|
+
process.exit(await runInit(init, { version: pkg.version }));
|
|
184
|
+
}
|
|
185
|
+
|
|
172
186
|
if (process.argv[2] === "memory") {
|
|
173
187
|
const { runMemoryCommand } = await import("./cli/memory.js");
|
|
174
188
|
process.exit(await runMemoryCommand(process.argv.slice(3)));
|
|
@@ -389,9 +403,8 @@ export async function run() {
|
|
|
389
403
|
}
|
|
390
404
|
|
|
391
405
|
if (shouldRunOnboarding(positionals, opts)) {
|
|
392
|
-
const {
|
|
393
|
-
await
|
|
394
|
-
return;
|
|
406
|
+
const { runInit } = await import("./cli/onboarding.js");
|
|
407
|
+
process.exit(await runInit([], { version: pkg.version }));
|
|
395
408
|
}
|
|
396
409
|
|
|
397
410
|
// `penthera agents [path]`: offline audit of skills, MCP servers, hooks, plugins.
|
package/src/ui/api/data.js
CHANGED
|
@@ -15,6 +15,7 @@ import { referencesFor } from "../../../lib/references.js";
|
|
|
15
15
|
import { planFixes, redactPlan } from "../../../lib/fix/index.js";
|
|
16
16
|
import { verifyEvidence, MANIFEST, SIGNATURE, SCHEMA as EVIDENCE_SCHEMA } from "../../report/evidence.js";
|
|
17
17
|
import { writeEvidence } from "../../cli/report-outputs.js";
|
|
18
|
+
import { createGit } from "../../agent/git.js";
|
|
18
19
|
import { formatAudienceReport, AUDIENCES } from "../../report/audience.js";
|
|
19
20
|
import {
|
|
20
21
|
classify, buildOverview, buildFindings, locateFinding, findingDetail, buildDeps, buildAi, memoryStats, loopSummary,
|
|
@@ -216,6 +217,35 @@ export function createDataSource({ projects, reportsDir, home, memoryRoot, setti
|
|
|
216
217
|
return { ...detail, id, project, sources, fix, file: hit.report.name, explain: explainFinding(hit.finding, hit.report.data) };
|
|
217
218
|
},
|
|
218
219
|
|
|
220
|
+
/**
|
|
221
|
+
* The Environment card: which folder a project maps to, its git branch and
|
|
222
|
+
* HEAD, local penthera/fix-* branches, and the hosts its latest scan
|
|
223
|
+
* contacted. Git is read through the agent's hardened runner
|
|
224
|
+
* (src/agent/git.js: allow-listed subcommands, hooks and fsmonitor off).
|
|
225
|
+
*/
|
|
226
|
+
env(project) {
|
|
227
|
+
let folder = null;
|
|
228
|
+
if (typeof project === "string" && project.startsWith("/")) {
|
|
229
|
+
try { folder = confineDir(project, projectRoots); } catch { folder = null; }
|
|
230
|
+
}
|
|
231
|
+
const out = { folder, branch: null, head: null, fixBranches: [], hosts: [], blocked: [] };
|
|
232
|
+
if (folder) {
|
|
233
|
+
const git = createGit(folder);
|
|
234
|
+
out.branch = git.tryRun("symbolic-ref", "--quiet", "--short", "HEAD")?.trim() || null;
|
|
235
|
+
out.head = git.tryRun("rev-parse", "--short", "HEAD")?.trim() || null;
|
|
236
|
+
const refs = git.tryRun("show-ref", "--heads") || "";
|
|
237
|
+
out.fixBranches = refs.split("\n").map((l) => l.split(" ")[1] || "").filter((r) => r.startsWith("refs/heads/penthera/fix-"))
|
|
238
|
+
.map((r) => r.replace("refs/heads/", "")).slice(-10).reverse();
|
|
239
|
+
}
|
|
240
|
+
const latest = latestScan(scan().scans, project);
|
|
241
|
+
const e = latest?.data?.egress;
|
|
242
|
+
if (e) {
|
|
243
|
+
out.hosts = (e.hosts || []).slice(0, 12).map((h) => ({ host: String(h.host).slice(0, 255), requests: h.requests || 0, thirdParty: (e.thirdPartyHosts || []).some((t) => t.host === h.host) }));
|
|
244
|
+
out.blocked = (e.blocked || []).slice(0, 8).map((h) => ({ host: String(h.host).slice(0, 255), requests: h.requests || 0 }));
|
|
245
|
+
}
|
|
246
|
+
return out;
|
|
247
|
+
},
|
|
248
|
+
|
|
219
249
|
history(project, a, b) {
|
|
220
250
|
return buildScanHistory(scan().scans, project, a, b);
|
|
221
251
|
},
|
package/src/ui/api/demo.js
CHANGED
|
@@ -307,6 +307,19 @@ export function createDemoSource({ settings, version = "dev" }) {
|
|
|
307
307
|
};
|
|
308
308
|
},
|
|
309
309
|
history: (project, a, b) => buildScanHistory(scans, project, a, b),
|
|
310
|
+
env(project) {
|
|
311
|
+
const latest = latestScan(scans, project);
|
|
312
|
+
const e = latest?.data?.egress;
|
|
313
|
+
const isRepo = String(project).startsWith("/");
|
|
314
|
+
return {
|
|
315
|
+
folder: isRepo ? project : null,
|
|
316
|
+
branch: isRepo ? "main" : null,
|
|
317
|
+
head: isRepo ? "4be9c1f" : null,
|
|
318
|
+
fixBranches: isRepo ? loops.map((l) => l.loopRun.branch.fix) : [],
|
|
319
|
+
hosts: (e?.hosts || []).map((h) => ({ ...h, thirdParty: (e.thirdPartyHosts || []).some((t) => t.host === h.host) })),
|
|
320
|
+
blocked: e?.blocked || [],
|
|
321
|
+
};
|
|
322
|
+
},
|
|
310
323
|
createEvidence(project) {
|
|
311
324
|
const latest = latestScan(scans, project);
|
|
312
325
|
if (!latest) return null;
|
package/src/ui/api/routes.js
CHANGED
|
@@ -75,6 +75,7 @@ export function createApi({ source, runs, version, settings }) {
|
|
|
75
75
|
if (!r) throw new RunError("No scan report for that project yet. Run a scan first.", 404);
|
|
76
76
|
return r;
|
|
77
77
|
}],
|
|
78
|
+
["GET", /^\/api\/env$/, (_m, q) => source.env(String(q.get("project") || ""))],
|
|
78
79
|
["GET", /^\/api\/deps$/, () => source.deps()],
|
|
79
80
|
["GET", /^\/api\/ai$/, () => source.ai()],
|
|
80
81
|
["GET", /^\/api\/memory$/, () => ({ projects: source.memory() })],
|