penthera 1.4.0 → 1.6.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 (114) hide show
  1. package/CHANGELOG.md +227 -0
  2. package/README.md +148 -575
  3. package/bin/scan.js +19 -0
  4. package/docs/commands/README.md +23 -0
  5. package/docs/commands/action.md +21 -0
  6. package/docs/commands/agents.md +38 -0
  7. package/docs/commands/deps.md +115 -0
  8. package/docs/commands/fix.md +53 -0
  9. package/docs/commands/hook.md +69 -0
  10. package/docs/commands/install.md +53 -0
  11. package/docs/commands/scan.md +100 -0
  12. package/docs/commands/skill.md +58 -0
  13. package/docs/compare/README.md +25 -0
  14. package/docs/compare/github.md +60 -0
  15. package/docs/compare/semgrep.md +54 -0
  16. package/docs/compare/snyk.md +62 -0
  17. package/docs/compare/socket.md +78 -0
  18. package/docs/compare/zap.md +59 -0
  19. package/docs/compliance-cra.md +141 -0
  20. package/docs/compliance-nis2.md +3 -1
  21. package/docs/dashboard.md +244 -0
  22. package/docs/evidence.md +8 -4
  23. package/docs/examples/github-actions-deps.yml +4 -4
  24. package/docs/examples/github-actions-scan.yml +4 -4
  25. package/docs/index.md +11 -2
  26. package/docs/internal-audit.md +83 -0
  27. package/docs/marketing.md +151 -0
  28. package/docs/plan.md +101 -0
  29. package/docs/privacy.md +38 -0
  30. package/docs/references.bib +10 -0
  31. package/docs/references.md +1 -0
  32. package/docs/research/socket-positioning-notes.md +183 -0
  33. package/docs/use-cases/README.md +14 -0
  34. package/docs/use-cases/agencies.md +71 -0
  35. package/docs/use-cases/ai-coding-teams.md +81 -0
  36. package/docs/use-cases/eu-sme-compliance.md +68 -0
  37. package/docs/use-cases/open-source-maintainers.md +75 -0
  38. package/docs/use-cases/solo-founder.md +65 -0
  39. package/lib/blackbox/email-dns.js +5 -0
  40. package/lib/compliance/cra.js +525 -0
  41. package/lib/compliance/questionnaire.js +4 -2
  42. package/lib/deps/capabilities.js +49 -12
  43. package/lib/deps/index.js +314 -30
  44. package/lib/deps/license.js +329 -0
  45. package/lib/deps/lockfile.js +29 -3
  46. package/lib/deps/popular-pypi.json +1 -0
  47. package/lib/deps/python/capabilities.js +447 -0
  48. package/lib/deps/python/index.js +234 -0
  49. package/lib/deps/python/lockfiles.js +359 -0
  50. package/lib/deps/python/source.js +396 -0
  51. package/lib/deps/python/toml.js +219 -0
  52. package/lib/deps/report.js +54 -6
  53. package/lib/deps/source.js +149 -24
  54. package/lib/deps/spdx.json +1 -0
  55. package/lib/deps/typosquat.js +24 -13
  56. package/lib/fix/apply.js +83 -19
  57. package/lib/hook/index.js +345 -0
  58. package/lib/hook/policy.js +62 -0
  59. package/lib/install/index.js +59 -18
  60. package/lib/install/policy.js +27 -7
  61. package/lib/jurisdictions/nl.json +9 -0
  62. package/lib/memory/repo-suppress.js +11 -3
  63. package/lib/references.json +77 -0
  64. package/lib/remediation/index.js +9 -0
  65. package/lib/retirejs.js +69 -17
  66. package/lib/rules/regex-guard.js +368 -0
  67. package/lib/templates.js +13 -6
  68. package/lib/tls.js +7 -0
  69. package/lib/whitebox/aibom.js +384 -0
  70. package/lib/whitebox/frameworks.js +8 -5
  71. package/lib/whitebox/sbom.js +45 -10
  72. package/lib/whitebox/secrets.js +31 -8
  73. package/package.json +8 -3
  74. package/skills/penthera/SKILL.md +55 -8
  75. package/src/cli/deps.js +81 -6
  76. package/src/cli/evidence.js +14 -4
  77. package/src/cli/hook.js +131 -0
  78. package/src/cli/install.js +14 -5
  79. package/src/cli/report-outputs.js +25 -2
  80. package/src/cli/ui.js +89 -0
  81. package/src/cli.js +81 -11
  82. package/src/report/audience.js +10 -10
  83. package/src/report/evidence.js +33 -14
  84. package/src/report/footer.js +11 -0
  85. package/src/report/markdown.js +6 -3
  86. package/src/report/md-escape.js +16 -0
  87. package/src/reporter.js +1 -0
  88. package/src/scan-url.js +5 -1
  89. package/src/ui/api/data.js +309 -0
  90. package/src/ui/api/demo.js +464 -0
  91. package/src/ui/api/routes.js +134 -0
  92. package/src/ui/api/runs.js +243 -0
  93. package/src/ui/api/summarize.js +451 -0
  94. package/src/ui/security.js +153 -0
  95. package/src/ui/server.js +186 -0
  96. package/src/ui/static/app.css +723 -0
  97. package/src/ui/static/favicon.svg +1 -0
  98. package/src/ui/static/index.html +57 -0
  99. package/src/ui/static/js/api.js +29 -0
  100. package/src/ui/static/js/app.js +138 -0
  101. package/src/ui/static/js/charts.js +207 -0
  102. package/src/ui/static/js/copy.js +79 -0
  103. package/src/ui/static/js/dom.js +120 -0
  104. package/src/ui/static/js/format.js +42 -0
  105. package/src/ui/static/js/theme.js +10 -0
  106. package/src/ui/static/js/typer.js +99 -0
  107. package/src/ui/static/js/views/ai.js +68 -0
  108. package/src/ui/static/js/views/deps.js +96 -0
  109. package/src/ui/static/js/views/evidence.js +115 -0
  110. package/src/ui/static/js/views/findings.js +165 -0
  111. package/src/ui/static/js/views/history.js +92 -0
  112. package/src/ui/static/js/views/overview.js +184 -0
  113. package/src/ui/static/js/views/run.js +349 -0
  114. package/src/ui/static/js/views/settings.js +67 -0
@@ -0,0 +1,67 @@
1
+ import { h, put, panel, empty, announce } from "../dom.js";
2
+ import { SCREENS } from "../copy.js";
3
+
4
+ export async function render(root, { api, meta, refreshMeta, theme }) {
5
+ const [settings, mem] = await Promise.all([api.get("/api/settings"), api.get("/api/memory")]);
6
+
7
+ const sw = h("button", { type: "button", class: "switch", role: "switch", "aria-checked": String(settings.offline), "aria-describedby": "egress-help" },
8
+ h("span", {}, "Offline mode"), h("span", { class: "state" }, settings.offline ? "[ON]" : "[OFF]"));
9
+ const help = h("p", { id: "egress-help", class: "muted" });
10
+ const paint = (offline) => {
11
+ sw.setAttribute("aria-checked", String(offline));
12
+ sw.lastChild.textContent = offline ? "[ON]" : "[OFF]";
13
+ help.textContent = offline
14
+ ? "Scans started here contact only their target. No recon sources, no Retire.js download (the vendored snapshot is used)."
15
+ : "Scans started here may also contact recon sources (certificate logs, web archives) and download the latest Retire.js database.";
16
+ };
17
+ paint(settings.offline);
18
+ sw.addEventListener("click", async () => {
19
+ try {
20
+ const s = await api.post("/api/settings", { offline: sw.getAttribute("aria-checked") !== "true" });
21
+ paint(s.offline);
22
+ announce(`Offline ${s.offline ? "on" : "off"}`);
23
+ await refreshMeta?.();
24
+ } catch (e) {
25
+ help.textContent = e.message;
26
+ }
27
+ });
28
+
29
+ put(root,
30
+ h("h1", {}, "Settings"),
31
+ h("p", { class: "lede" }, "For this dashboard session. Nothing here is written to disk."),
32
+ h("div", { class: "grid" },
33
+ panel("Egress", { class: "span-6" }, sw, help,
34
+ h("p", { class: "panel-note" }, "The agent loop is always offline and limited to its URL host, whatever this says. The dashboard itself never contacts the network.")),
35
+ panel("Where data comes from", { class: "span-6" },
36
+ h("dl", { class: "kv" },
37
+ h("dt", {}, "reports"), h("dd", {}, meta.reportsDir),
38
+ h("dt", {}, "memory"), h("dd", {}, meta.memoryRoot),
39
+ h("dt", {}, "projects"), h("dd", {}, meta.projects.map((p) => h("div", {}, p))),
40
+ ),
41
+ h("p", { class: "panel-note" }, "Only these folders and ~/.penthera are read. Move memory with PENTHERA_HOME; add folders with penthera ui --project <dir> --reports <dir>."),
42
+ ),
43
+ panel("Memory chains", { class: "span-12" },
44
+ mem.projects.length ? h("div", { class: "table-wrap" }, h("table", {},
45
+ h("thead", {}, h("tr", {}, ...["Project", "Chain", "Events", "Scans", "Suppressions", "Median time to fix"].map((t, i) => h("th", { class: i >= 2 ? "num" : null }, t)))),
46
+ h("tbody", {}, mem.projects.map((p) => h("tr", {},
47
+ h("td", { class: "trunc", title: p.label }, p.label),
48
+ h("td", { class: p.chain === "ok" ? "ok" : "bad" }, p.chain === "ok" ? "[OK]" : `[BROKEN @ line ${p.brokenAt}]`),
49
+ h("td", { class: "num" }, String(p.events)),
50
+ h("td", { class: "num" }, String(p.scans)),
51
+ h("td", { class: "num" }, String(p.suppressions)),
52
+ h("td", { class: "num" }, p.medianTimeToFixDays == null ? "-" : `${p.medianTimeToFixDays}d`),
53
+ ))),
54
+ )) : empty("No memory yet."),
55
+ ),
56
+ panel("Session security", { class: "span-12" },
57
+ h("dl", { class: "kv" },
58
+ h("dt", {}, "bound to"), h("dd", {}, `${location.host} (127.0.0.1 only)`),
59
+ h("dt", {}, "session"), h("dd", {}, "one-time URL token swapped for an HttpOnly, SameSite=Strict cookie"),
60
+ h("dt", {}, "requests"), h("dd", {}, "Host header must be 127.0.0.1 or localhost (DNS rebinding); state changes need this page's Origin"),
61
+ h("dt", {}, "page"), h("dd", {}, "strict CSP, no inline script, no framing, no CORS, no third-party anything"),
62
+ h("dt", {}, "data"), h("dd", {}, meta.demo ? "--demo: fake data, nothing read from disk" : "local reports and memory"),
63
+ ),
64
+ ),
65
+ ),
66
+ );
67
+ }