# Penthera: the full documentation > Penthera 1.9.0. README.md and the user documentation from https://github.com/danoszz/penthera, as one Markdown file. Each page is also at its own URL, listed in https://penthera.org/llms.txt. Only scan targets you own or are authorized to test. ---
Penthera. Is your site/app safe? Check it with a free cyber security quick scan: the scan field and a report of penthera.org, 95 out of 100, with Prompt to fix

**Penthera: your open-source cyber security agent** The security review you never had time for. Offline, rule-based, and you can verify every finding yourself. **[penthera.org](https://penthera.org)**: check your live site in a few seconds. Passive, nothing stored. **132** readable rules · **6** read-only MCP tools · **1** runtime dependency · **0** requests past loopback in the benchmark · **MIT**, no telemetry
[![npm](https://img.shields.io/npm/v/penthera?color=0000ed)](https://www.npmjs.com/package/penthera) [![npm downloads](https://img.shields.io/npm/dm/penthera?color=0000ed&label=downloads)](https://www.npmjs.com/package/penthera) [![GitHub stars](https://img.shields.io/github/stars/danoszz/penthera?color=0000ed)](https://github.com/danoszz/penthera/stargazers) ![node](https://img.shields.io/badge/node-%E2%89%A522-0000ed) ![license](https://img.shields.io/badge/license-MIT-0000ed) [![CI](https://github.com/danoszz/penthera/actions/workflows/ci.yml/badge.svg)](https://github.com/danoszz/penthera/actions/workflows/ci.yml) [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/danoszz/penthera/badge)](https://scorecard.dev/viewer/?uri=github.com/danoszz/penthera)
A 12-second loop: npx penthera sets up a project, an offline scan runs in the terminal, the dashboard shows the verdict in Simple mode and the triage matrix in Advanced mode, and a fix is approved

| `penthera ui`, Simple, dark | `penthera ui`, Advanced, light | |---|---| | ![The dashboard in Simple mode, dark: the verdict, problems in plain words and one problem open](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-dark.jpg) | ![The dashboard in Advanced mode, light: a project's tabs, the triage matrix and a finding open](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-light.jpg) | Walkthrough video: setup, a scan, the dashboard in Simple and Advanced mode, one fix approved, the report [Watch the 80-second walkthrough](https://github.com/danoszz/penthera/raw/main/assets/walkthrough.mp4): setup, a scan, the dashboard, a fix approved, the report. Demo data, no sound, captions in the video.
--- ## What it is Penthera checks your app, its dependencies and your AI coding tools on your own machine. It fixes what it can, with your approval. Every finding comes from a rule you can read, with its evidence at `file:line`. A model may propose; a rule decides. It is also an open R&D project. We measure every check in public and publish the results, good or bad, so you can see how far to trust it. It finds known classes of problems, not novel ones. It does not replace a human pentester. ### One real fix On 23 October 2026 the live check on [penthera.org](https://penthera.org) gave penthera.org itself 95 out of 100. Every header, TLS and page check passed; two email-spoofing records were missing. Two DNS records later, it scored 100. ``` penthera.org 95/100 FIX No SPF record for penthera.org FIX No DMARC record for penthera.org + TXT @ v=spf1 -all + TXT _dmarc v=DMARC1; p=reject; adkim=s; aspf=s penthera.org 100/100 Solid basics ``` The same report, with a prompt for your coding agent to make the fixes, is one click away for your own site. ## Start in 30 seconds You need Node.js 22 or later. No install, no account. **In your terminal.** Run this in your project folder: ```bash npx penthera ``` It detects your setup offline, asks what to check, and gives you a grade per area and the three things to fix first. It scans your running app only after you confirm the app is yours. Nothing is committed or pushed ([guided start](https://penthera.org/docs/commands/init.md)). **In your coding agent, as a skill.** Then ask "scan my app for security issues": ```bash npx skills add danoszz/penthera ``` **In your coding agent, as an MCP server.** Six read-only tools for Claude Code, Cursor, Codex, VS Code and Windsurf, new in 1.9.0. Pin the version: ```bash claude mcp add penthera -- npx -y penthera@1.9.0 mcp ``` **In Claude Code, as a plugin.** From Claude Code's prompt: ``` /plugin marketplace add danoszz/penthera /plugin install penthera@penthera ``` **In Gemini CLI, as an extension.** With the same pinned MCP server: ```bash gemini extensions install https://github.com/danoszz/penthera ``` Other clients: [docs/commands/mcp.md](https://penthera.org/docs/commands/mcp.md). Other skill install routes: [docs/commands/skill.md](https://penthera.org/docs/commands/skill.md). To run it in a container: `docker build -t penthera . && docker run --rm penthera https://your-app.example`. ## What it does ```mermaid flowchart LR U["You or your AI agent"] --> P(("Penthera
on your machine")) P --> APP["Your app
URL scan, repo scan
runtime advisories"] P --> DEP["Your dependencies
capabilities, install firewall
typosquats, upgrade diff"] P --> AI["Your AI tooling
skills, MCP, hooks
plugins, rule files"] APP --> F[["Findings
severity, confidence
evidence at file:line"]] DEP --> F AI --> F F --> FIX["Fix loop
asks first, local branch
cannot edit its own tests"] FIX -. "re-scan proves it" .-> F F --> R["Reports and dashboard
terminal, Markdown, SARIF
penthera ui, MCP tools"] F --> E["Evidence
signed bundle
NIS2 and CRA drafts"] classDef core fill:#0000ed,stroke:#0000ed,color:#ffffff classDef box fill:#ffffff,stroke:#000000,color:#000000 class P core class U,APP,DEP,AI,F,FIX,R,E box ``` ### Before you deploy | Job | What Penthera does | Command | |---|---|---| | Test the app | Scans your localhost or preview URL (TLS, headers, CORS, cookies, auth, JWT, IDOR, OAuth, OpenAPI; injection and fuzzing only when you ask, after a confirmation) and your repository (routes, trust boundaries, code-execution sinks, secrets including AI keys) | [`penthera --repo .`](https://penthera.org/docs/commands/scan.md) | | Check your runtimes | Whether the PHP, nginx, Apache, Node.js, Python, OpenSSL or MongoDB version you run is affected by a known advisory, from the signed advisories pack | [Runtime advisories](https://penthera.org/docs/runtime-advisories.md) | | Fix and verify | Applies deterministic fix recipes on a local branch, re-scans to prove they worked, and remembers the result. Never pushes | [`penthera agent --loop`](https://penthera.org/docs/commands/fix.md) | | Block it at commit time | Stops secrets, risky agent config and dependency changes before they are committed | [`penthera hook install`](https://penthera.org/docs/commands/hook.md) | ### What you build with | Job | What Penthera does | Command | |---|---|---| | Vet dependencies | Shows what every npm and Python package can do (install scripts, shell, network, env, obfuscation), what an upgrade added, lookalike names and licenses | [`penthera deps --base main`](https://penthera.org/docs/commands/deps.md) | | Guard installs | Shows what a new package brings before any install script runs, and stops it under your policy | [`penthera install `](https://penthera.org/docs/commands/install.md) | | Review pull requests | Comments the dependency capability diff on every PR that touches the lockfile | [GitHub Action](https://penthera.org/docs/commands/action.md) | | Audit your AI tooling | Checks skills, MCP servers, hooks, plugins and rule files for hidden instructions, unpinned servers, over-broad permissions and silent updates | [`penthera agents --home`](https://penthera.org/docs/commands/agents.md) | | Work from your agent | Six read-only MCP tools: scan the repo, check dependencies, audit AI tooling, runtime advisories, explain a finding, a passive URL scan | [`penthera mcp`](https://penthera.org/docs/commands/mcp.md) | ### After you ship | Job | What Penthera does | Command | |---|---|---| | Report and prove | Writes reports for developers, owners and auditors; a hashed, signable evidence bundle; NIS2 and CRA readiness reports and drafts | [`penthera --audience auditor --evidence out/`](https://penthera.org/docs/evidence.md) | | See it all | A local dashboard on 127.0.0.1. Simple mode for non-programmers: a one-sentence verdict, problems in plain words, and a check-and-fix mode with Apply, Skip and Stop per change. Advanced adds dependencies, AI tooling, history and evidence | [`penthera ui`](https://penthera.org/docs/dashboard.md) | | Keep watching | Scheduled checks of production for drift: certificates, headers, new endpoints, keys in bundles, new CVEs in your SBOM | on the [roadmap](https://github.com/danoszz/penthera/blob/main/ROADMAP.md) | Every finding carries a severity, a confidence label (`confirmed`, `likely`, `potential`, `needs-human-review`), its evidence and the research behind it. Reports come out as Markdown, JSON and SARIF for GitHub code scanning. Exit code `1` means critical or high findings. Every command and flag is in [docs/commands/](https://penthera.org/docs/commands/README.md). Compliance output is readiness tooling, not a certification and not legal advice. **AI mode (opt-in, new in 1.9.0).** `penthera --repo . --ai` lets a local model propose problems no rule describes. Penthera checks each proposal with its rules, and anything unconfirmed is labelled "model-proposed, unverified" and never fails CI. Whether it finds more than the rules alone is not measured yet ([AI mode](https://penthera.org/docs/ai-mode.md)). ## How it is measured Each feature goes through the same path: **method → evidence → tool.** - **Method.** The [methodology](https://penthera.org/docs/METHODOLOGY.md) states the threat model, what black-box (HTTP) and white-box (source) analysis can and cannot see, the confidence model, and the false-positive controls on each probe. Each design choice cites the research or standard it rests on: 95 verified references, in APA style, in [docs/references.md](https://penthera.org/docs/references.md). - **Evidence.** A benchmark runs on every release, and the release gate fails if precision or recall drops by more than 0.02. Experiments that decide what ships next are pre-registered in [docs/research/prereg/](https://github.com/danoszz/penthera/tree/main/docs/research/prereg) before any data is collected, and the results are published either way. - **Tool.** What holds up ships in a reviewed, signed release. What does not hold up is dropped, and the rules stay. | Measure | Result | Sample | |---|---|---| | Precision, bundled benchmark | 0.88, 95% CI 0.69 to 0.96 | 21 true and 3 false positives | | Recall, bundled benchmark | 0.91, 95% CI 0.73 to 0.98 | 2 misses, [each explained](https://github.com/danoszz/penthera/blob/main/bench/RESULTS.md) | | Requests past loopback during the benchmark | 0 | Enforced by a network guard | | E10 pilot, known-vulnerable apps | Precision 0.88, 95% CI 0.78 to 0.93. Recall 0.07 before fixes, 0.71 (CI 0.45 to 0.88) after | 14 root causes in Juice Shop and DVNA ([results](https://github.com/danoszz/penthera/blob/main/bench/known-vuln/RESULTS-after-fixes.md)) | | Detection rules, all readable | 132 in this release: 16 URL scan templates, 21 secret and AI-key patterns, 15 dependency capabilities, 39 AI-tooling rules, 5 fix recipes, and the signed rule packs | [Rule packs](https://penthera.org/docs/rule-packs.md) | The bundled benchmark is 24 findings in small labs we wrote ourselves. It catches regressions; it does not measure real-world accuracy. The E10 pilot used one labeller and is not independent: the fixes were written after seeing the misses on the same two apps. On WebGoat, held out, recall stayed at 2 of 9. Accuracy on real projects is experiment E1, pre-registered and not yet run. Intervals are Wilson. What Penthera cannot see is listed under [Known limitations](https://penthera.org/docs/METHODOLOGY.md#known-limitations). Refresh the counts with `npm run docs:numbers` and `npm run bench`. **Cite this repository** with the button on GitHub or [CITATION.cff](https://github.com/danoszz/penthera/blob/main/CITATION.cff). APA and BibTeX forms are in the [methodology](https://penthera.org/docs/METHODOLOGY.md#how-to-cite). ## Why you can trust it - **Local and offline.** No account, no telemetry, no cloud. `--offline` limits network traffic to the target you named, and every report lists the hosts it contacted ([network use](https://penthera.org/docs/privacy.md)). - **Deterministic.** Rules you can read, with `file:line` evidence. A model may propose a fix or a lead; a deterministic re-scan and your tests decide. - **Non-destructive by default.** Default scans send no attack payloads. Payload testing (`--deep`, `--fuzz`, `--all`) is opt-in, asks for a confirmation bound to the target, and is rate-limited. - **Asks before it changes anything.** The fix loop works on a local branch, asks before every change by default, and never pushes or opens pull requests. - **No update channel to hijack.** Detection rules ship inside reviewed releases as signed, versioned [rule packs](https://penthera.org/docs/rule-packs.md). You can add your own by path (`--rules `). Nothing is downloaded and run at run time. - **Releases you can verify.** npm provenance through trusted publishing, a maintainer approval with 2FA for every version, SHA-pinned CI, CodeQL and OpenSSF Scorecard. Every release passes an [internal audit](https://github.com/danoszz/penthera/blob/main/docs/internal-audit.md) of eight gates ([latest report](https://github.com/danoszz/penthera/blob/main/audit/REPORT.md)). Penthera finds known classes of problems, not novel ones, and it does not replace a human pentester or incident response. The decisions behind it, its limits, and how to challenge them are in the [manifesto and FAQ](https://penthera.org/docs/MANIFESTO.md). ## How it fits with other tools Penthera works next to the tools you already use; its SARIF output goes straight into GitHub code scanning, for example. Each comparison page says where the other tool goes further and how to run the two together. | | Focus | Where it runs | How it decides | |---|---|---|---| | **Penthera** | Your app, dependencies and AI tooling; fixes; EU evidence | Your machine or CI, offline | Readable deterministic rules, with evidence | | [Socket](https://penthera.org/docs/compare/socket.md) | Supply-chain risk across package registries | Cloud service, GitHub App, CLI | Registry-scale package analysis, including AI review | | [Snyk](https://penthera.org/docs/compare/snyk.md) | Known vulnerabilities in dependencies, code, containers, IaC | Cloud service, IDE, CLI | Curated vulnerability database plus static analysis | | [GitHub Dependabot and code scanning](https://penthera.org/docs/compare/github.md) | Known-CVE alerts, update PRs, CodeQL | GitHub | Advisory database, CodeQL queries | | [OWASP ZAP](https://penthera.org/docs/compare/zap.md) | Dynamic testing of web apps | Your machine or CI | Active and passive scan rules | | [Semgrep](https://penthera.org/docs/compare/semgrep.md) | Static analysis of your own code | Your machine, CI, cloud platform | Pattern rules, plus a paid cross-file engine | All comparisons, including Cloudflare's security-audit skill: [docs/compare/](https://penthera.org/docs/compare/README.md). ## Start here | You want to know | Read | |---|---| | Is my app safe to ship? | [I'm new](https://penthera.org/docs/index.md#1-im-new), then [scan](https://penthera.org/docs/commands/scan.md) | | Is this package or MCP server safe? | [deps](https://penthera.org/docs/commands/deps.md), [install](https://penthera.org/docs/commands/install.md), [agents](https://penthera.org/docs/commands/agents.md) | | How do I use it from my coding agent? | [MCP server](https://penthera.org/docs/commands/mcp.md), [skill](https://penthera.org/docs/commands/skill.md), [agents safely](https://penthera.org/docs/agents-safely.md) | | How do I put it in CI? | [The GitHub Action](https://penthera.org/docs/commands/action.md), [scan in CI](https://penthera.org/docs/commands/scan.md#ci-integration) | | What do I give my auditor? | [Evidence bundles](https://penthera.org/docs/evidence.md), [NIS2](https://penthera.org/docs/compliance-nis2.md), [CRA](https://penthera.org/docs/compliance-cra.md) | | How do I run the fix loop? | [Fix and verify](https://penthera.org/docs/commands/fix.md), [the local loop](https://penthera.org/docs/local-loop.md) | The full map, by situation and from first scan to research, is in [docs/index.md](https://penthera.org/docs/index.md). ## Ethics and authorization Penthera sends HTTP requests to the targets you give it, and some modes (`--deep`, `--fuzz`, `--all`) send attack payloads, after a confirmation. Scan only what you own, what you have written authorization to test, or local environments you control. Do not use it to probe third-party systems, exfiltrate data, disrupt services, or harm anyone. Unauthorized testing may break computer-misuse laws such as the US CFAA, the UK Computer Misuse Act, and their EU equivalents. When in doubt, do not scan. The software is provided "as is", without warranty. The author accepts no liability for misuse, and you are responsible for making sure every scan you run is lawful. The live check on penthera.org has its own [terms](https://penthera.org/docs/live-check-terms.md). ## Building in public What changed, what we are measuring, and what comes next, in one place. | | Where it stands | Read more | |---|---|---| | **Latest release** | 1.9.0 (2026-10-23) on npm `latest`: `penthera mcp`, opt-in AI mode, new detection rules, no attack payloads in default scans, a refreshed dashboard | [CHANGELOG](https://github.com/danoszz/penthera/blob/main/CHANGELOG.md), [roadmap](https://github.com/danoszz/penthera/blob/main/ROADMAP.md#now-190) | | **Research** | E10 pilot published: precision 0.88, recall 0.07 to 0.71 on two known-vulnerable apps after fixes (one labeller, not independent; a re-run is pending because default scans no longer send login checks). E1 on real code is pre-registered; E8 (agent guardrails) is a draft | [E10 results](https://github.com/danoszz/penthera/blob/main/bench/known-vuln/RESULTS.md), [E1 pre-registration](https://github.com/danoszz/penthera/blob/main/docs/research/prereg/E1-real-world-accuracy.md), [agents safely](https://penthera.org/docs/agents-safely.md) | | **Next** | Real-world precision and recall with intervals (E1), published whatever the result | [Roadmap: next](https://github.com/danoszz/penthera/blob/main/ROADMAP.md#next), [research plan](https://penthera.org/docs/vision-2.0.md) | [![Star history](https://api.star-history.com/svg?repos=danoszz/penthera&type=Date)](https://star-history.com/#danoszz/penthera&Date) Releases are deliberately slow: a feature freeze, an independent review, the release checks, and a release candidate before any new minor version. Watch the repository for releases or join the [Discussions](https://github.com/danoszz/penthera/discussions). ## How Penthera is built Penthera is developed with substantial help from generative AI: 91% of the commits up to v1.7.0 were written with Claude (Anthropic) through Claude Code, directed and reviewed by the maintainer, and marked with a co-author trailer. Generated code passes the same gates as any change: tests, CodeQL, an independent review, the internal audit, and a 2FA-approved release. Inside the product, models only ever propose; deterministic checks decide. The full disclosure, and the rules for AI-assisted contributions, are in [docs/genai.md](https://penthera.org/docs/genai.md). ## Contributing and support Clone the repository and run `npm install`, then `npm test` for the suite and `npm run bench` for precision and recall. [CONTRIBUTING.md](https://github.com/danoszz/penthera/blob/main/CONTRIBUTING.md) covers the project layout, the release cadence and the release checklist; [AGENTS.md](https://github.com/danoszz/penthera/blob/main/AGENTS.md) is the short version for coding agents. The [roadmap](https://github.com/danoszz/penthera/blob/main/ROADMAP.md) shows what ships next, and the [research plan](https://penthera.org/docs/vision-2.0.md) what we measure. Issues and pull requests are welcome. Penthera is free. If it caught something before you shipped it, you can [sponsor its maintenance](https://github.com/sponsors/danoszz). Sponsorship pays for maintainer time and buys no influence over findings ([tiers](https://github.com/danoszz/penthera/blob/main/docs/funding/sponsor-tiers.md)). ## License MIT. See [LICENSE](https://github.com/danoszz/penthera/blob/main/LICENSE). Report vulnerabilities in Penthera itself through [SECURITY.md](https://github.com/danoszz/penthera/blob/main/SECURITY.md). --- # Penthera documentation > **Starter and pro.** For "where do I start, and where do I go next?" Penthera is an open-source cyber security agent. It checks your app, its dependencies and your AI coding tools on your own machine, offline, with rules you can read. A model may propose; a rule decides. Pick the journey that fits you. Each page answers one question, and each journey runs from the first command to the details. [![Walkthrough video: setup, a scan, the dashboard in Simple and Advanced mode, one fix approved, the report](https://github.com/danoszz/penthera/raw/main/assets/walkthrough-poster.jpg)](https://github.com/danoszz/penthera/raw/main/assets/walkthrough.mp4) [Watch the 80-second walkthrough](https://github.com/danoszz/penthera/raw/main/assets/walkthrough.mp4). Demo data, no sound, captions in the video ([as text](https://github.com/danoszz/penthera/raw/main/assets/walkthrough.vtt)). ## 1. I'm new For "is my app safe to ship?" Start with one command in your project folder: ```bash npx penthera ``` | Your question | Read | |---|---| | "What does `npx penthera` do, and what does it not do?" | [Set up in one command](https://penthera.org/docs/commands/init.md) | | "How do I scan my running app and its source?" | [Scanning your app](https://penthera.org/docs/commands/scan.md) | | "Is this package safe to add?" | [Dependency risk](https://penthera.org/docs/commands/deps.md), [the install firewall](https://penthera.org/docs/commands/install.md) | | "Can I see it all in one place?" | [The local dashboard](https://penthera.org/docs/dashboard.md) | | "What does it send over the network?" | [Privacy and network use](https://penthera.org/docs/privacy.md) | ## 2. I use AI coding tools For "is my AI setup safe, and can my agent run Penthera?" | Your question | Read | |---|---| | "Is this MCP server or skill safe?" | [Auditing your AI tooling](https://penthera.org/docs/commands/agents.md) | | "Can my coding agent run it for me?" | [Penthera as an Agent Skill](https://penthera.org/docs/commands/skill.md) | | "Can my agent call it as a tool?" | [Penthera as an MCP server](https://penthera.org/docs/commands/mcp.md) (1.9 release candidate) | | "I let an agent work on my code. What should I check?" | [Running AI agents safely](https://penthera.org/docs/agents-safely.md) | | "Can a model look for what the rules miss?" | [AI mode](https://penthera.org/docs/ai-mode.md) (opt-in, 1.9 release candidate) | | "An AI audit gave me a list of findings. Which are real?" | [`penthera verify`](https://penthera.org/docs/commands/verify.md) | ## 3. I run it in CI For "how do I make it part of every change?" | Your question | Read | |---|---| | "How do I put it in CI?" | [Penthera in CI](https://penthera.org/docs/commands/action.md), [scan in CI](https://penthera.org/docs/commands/scan.md#ci-integration), [example workflows](https://github.com/danoszz/penthera/blob/main/docs/examples/github-actions-scan.yml) | | "How do I stop problems before they are committed?" | [The pre-commit hook](https://penthera.org/docs/commands/hook.md) | | "How do I run the fix loop?" | [Fixing what it finds](https://penthera.org/docs/commands/fix.md), [the local loop and its memory](https://penthera.org/docs/local-loop.md) | | "How do I tune the rules?" | [Scan configuration](https://penthera.org/docs/commands/scan.md#configuration), [rule packs](https://penthera.org/docs/rule-packs.md) | | "Is the runtime I deploy affected by an advisory?" | [Runtime advisories](https://penthera.org/docs/runtime-advisories.md) | | "Which OWASP tests does it cover?" | [OWASP WSTG coverage](https://penthera.org/docs/owasp-wstg-coverage.md) | | "Which command and flag do I need?" | [All commands](https://penthera.org/docs/commands/README.md) | ## 4. I need evidence for an auditor For "what do I give my auditor?" Compliance output is readiness tooling. It is not a certification and not legal advice. | Your question | Read | |---|---| | "What do I hand over, and how does the auditor check it?" | [Evidence bundles and audience reports](https://penthera.org/docs/evidence.md) | | "What does NIS2 ask, and what can Penthera show?" | [NIS2](https://penthera.org/docs/compliance-nis2.md) | | "What must I report under the CRA, and what is in my AI-BOM?" | [The CRA and AI-BOM](https://penthera.org/docs/compliance-cra.md) | ## 5. I'm a researcher For "how do you know it works?" | Your question | Read | |---|---| | "How does it decide, and what can't it see?" | [Methodology](https://penthera.org/docs/METHODOLOGY.md), [known limitations](https://penthera.org/docs/METHODOLOGY.md#known-limitations) | | "How accurate is it?" | [Benchmark](https://github.com/danoszz/penthera/blob/main/bench/README.md) and [latest results](https://github.com/danoszz/penthera/blob/main/bench/RESULTS.md), the [E10 pilot](https://github.com/danoszz/penthera/blob/main/bench/known-vuln/RESULTS.md) on known-vulnerable apps | | "What will you measure next?" | [Research plan](https://penthera.org/docs/vision-2.0.md), [pre-registrations](https://github.com/danoszz/penthera/blob/main/docs/research/prereg/E1-real-world-accuracy.md), [research notes](https://github.com/danoszz/penthera/tree/main/docs/research) | | "How do I know a release is sound?" | [Internal audit](https://github.com/danoszz/penthera/blob/main/docs/internal-audit.md) | | "What research is it based on?" | [References](https://penthera.org/docs/references.md) ([BibTeX](https://github.com/danoszz/penthera/blob/main/docs/references.bib)), [how to cite](https://penthera.org/docs/METHODOLOGY.md#how-to-cite) | | "How does it compare to the tool I use?" | [Comparisons](https://penthera.org/docs/compare/README.md) | | "Why was it built this way?" | [Manifesto and FAQ](https://penthera.org/docs/MANIFESTO.md) | | "How much of it did AI write?" | [Use of generative AI](https://penthera.org/docs/genai.md) | The accuracy numbers come with their sample and caveats on the same page. In short: the bundled benchmark is 24 findings in labs we wrote ourselves, the E10 pilot used one labeller, and accuracy on real projects (E1) is not measured yet. ## The project | Page | For | |---|---| | [Roadmap](https://github.com/danoszz/penthera/blob/main/ROADMAP.md) | Where it stands, what ships next, and what is not planned | | [CONTRIBUTING](https://github.com/danoszz/penthera/blob/main/CONTRIBUTING.md) | Setup, tests, the merge flow and the release steps | | [AGENTS.md](https://github.com/danoszz/penthera/blob/main/AGENTS.md) | Coding agents working on or with Penthera | | [Voice guide](https://github.com/danoszz/penthera/blob/main/docs/voice.md) | How Penthera's docs and messages are written | | [CHANGELOG](https://github.com/danoszz/penthera/blob/main/CHANGELOG.md) | Every release | | [SECURITY](https://github.com/danoszz/penthera/blob/main/SECURITY.md) | Reporting a vulnerability in Penthera itself | | [Funding](https://github.com/danoszz/penthera/blob/main/docs/funding/sponsor-tiers.md) | Sponsor tiers, the [partner brief](https://github.com/danoszz/penthera/blob/main/docs/funding/partner-one-pager.md) and the [due diligence checklist](https://github.com/danoszz/penthera/blob/main/docs/funding/due-diligence-checklist.md) | | [Marketing automation](https://github.com/danoszz/penthera/blob/main/docs/marketing.md) | What runs on a schedule, and what a person still posts | | [MCP directory listings](https://github.com/danoszz/penthera/blob/main/docs/mcp-listing.md) | What the maintainer pastes into MCP directories | ## Programmatic API ```javascript import { loadTemplatesFromPaths, runTemplateScan } from "penthera/plugins"; import { runAdaptiveProbes } from "penthera/adaptive"; ``` The exports are in `lib/plugins.js` and `lib/blackbox/adaptive-scan.js`, both shipped in the npm package. The npm package ships only `docs/examples/` from this folder; the rest of the docs live on GitHub. Only scan systems you own or are authorized to test. --- # Set up in one command: `penthera init` > **Starter.** For "is my app safe to ship?" ## Start here New to Penthera? Run it with no arguments in your project folder: ```bash npx penthera # in a terminal, with no arguments: the setup wizard penthera init # the same wizard, by name (also works with piped answers) penthera init --yes # accept every safe default, ask nothing penthera init --dry-run # show what would run and be written, change nothing ``` The wizard finds what your project has, asks one question about what to check, runs those checks offline, shows one summary, and then offers to set up the hook, the CI workflow and the rest, one yes/no at a time. Press Enter to accept a default. ## What it does **1. Detect.** Offline, read-only: - whether this is a git repository (and which branch); - the web framework, from `package.json`, `requirements.txt` or `pyproject.toml`; - lockfiles: `package-lock.json`, `npm-shrinkwrap.json`, `uv.lock`, `poetry.lock`, `pdm.lock`, `Pipfile.lock`, `requirements.txt`; - agent config: `.mcp.json`, `.claude/`, `.cursor/`, `CLAUDE.md`, `AGENTS.md`, `skills/`; - a dev server on localhost ports 3000, 5173, 8000 or 8080 (a TCP connect to 127.0.0.1 and ::1, nothing else); - whether the Penthera pre-commit hook or a Penthera GitHub workflow is already there. **2. Ask what to check.** One checklist, with defaults from what was found. Type numbers to toggle items (`2,4`), or press Enter: ``` What should Penthera check? 1. [x] Code: secrets and risky API routes in ./ penthera --repo . 2. [x] Dependencies: what your npm and Python packages can do penthera deps 3. [x] AI tooling: skills, MCP servers, hooks, agent instructions penthera agents 4. [ ] The running app at http://localhost:3000 only if it is yours 5. [x] Offline mode: nothing leaves this machine recommended ``` - AI tooling then asks whether to include `~/.claude` and `~/.cursor` too (default no). - The running app is only listed when a dev server answered, is never preselected, and is only scanned after you confirm it is yours (default no). It runs the standard safe checks: no attack payloads. - With offline mode off, `deps` also reads npm registry metadata and the URL scan may download the Retire.js database. Nothing else changes. **3. Run and summarize.** One line per check, then the grade per area (A to F, the same scale as `penthera agents`), the three things to fix first in plain language, and the report folder: ``` Running 3 checks (offline) ✓ Code B 1 high 0.1s ✓ Dependencies A no issues 0.0s ✓ AI tooling A 1 medium 0.0s ``` Every report lands in `reports/penthera-/`: `code`, `deps`, `agents` and `app` as JSON and Markdown, plus `summary.md` and `summary.json`. **4. Set up.** Each step is a yes/no: | Step | Default | What it does | |---|---|---| | Pre-commit hook | yes | `penthera hook install`: blocks staged secrets, risky agent config and lockfile changes ([hook.md](https://penthera.org/docs/commands/hook.md)). Git repos only | | GitHub workflow | yes | Writes `.github/workflows/penthera.yml` from [the PR dependency review example](https://github.com/danoszz/penthera/blob/main/docs/examples/pr-dependency-review.yml), with the action pinned to the release commit SHA. Shown as a diff first. Needs a git repo and `package-lock.json` | | `.gitignore` | yes | Appends `reports/` so scan output is never committed | | Fix loop | no | `penthera agent --loop` on a local `penthera/fix-` branch; every diff asks first ([fix.md](https://penthera.org/docs/commands/fix.md)) | | Dashboard | yes | `penthera ui --open` on this run's reports ([dashboard.md](https://penthera.org/docs/dashboard.md)). Runs until Ctrl-C | Steps that are already done (a hook or workflow that exists, `reports/` already ignored) are skipped and say so. **5. What next.** The commands to rerun each check, check staged changes, run the fix loop or open the dashboard. ## Before you launch When you know what you want, run the checks directly. Start the app locally, then: ```bash npx penthera http://localhost:3000 --repo . # the running app and its source, together npx penthera fix --plan --repo . # fixes for what it can fix, as diffs npx penthera hook install # stop secrets at commit time from now on ``` Or ask your coding agent, with the [Agent Skill](https://penthera.org/docs/commands/skill.md) installed: "scan my app on localhost:3000 and fix what you find". Before you deploy, run the same scan against your preview URL. Only scan systems you own. The scan looks for what tends to go wrong in apps written fast, often with an AI assistant: an API route that returns any user's data if you change the id, an AI provider key in a client bundle, a missing security header, a CORS policy of `*`, a login with no rate limit. `penthera fix --plan` then prints a diff per fix it knows. Nothing is written until you apply a plan ([fixes](https://penthera.org/docs/commands/fix.md)). ## `--yes` and `--dry-run` `--yes` answers every question with its safe default: the code, dependency and AI tooling checks that apply, offline, then the hook, the workflow and `.gitignore`. It never scans a URL, never runs the fix loop and never opens the dashboard, even when a dev server is running. `--dry-run` runs detection and asks the questions, then prints what it would run and write instead of doing it. Combine them for a plan with no prompts: ```bash penthera init --yes --dry-run ``` `penthera --yes` and `penthera --dry-run` (no other arguments) are the same as `penthera init --yes` and `penthera init --dry-run`. ## What it does not do - It is not a penetration test by a person. It checks known classes of problem. It will not find a flaw in your business logic, such as a coupon that can be applied twice. - It does not fix IDOR or auth design for you. It points at the route. The fix is yours. - It does not watch production yet. Scheduled checks are on the [roadmap](https://github.com/danoszz/penthera/blob/main/ROADMAP.md). - A clean scan is not proof of safety. Every finding has a confidence label, and probes that could not run are listed as inconclusive rather than passed. - Its accuracy is measured only on labs we wrote ourselves: precision 0.88 (95% CI 0.69 to 0.96) and recall 0.91 (95% CI 0.73 to 0.98) on 24 findings ([results](https://github.com/danoszz/penthera/blob/main/bench/RESULTS.md)). Accuracy on real apps is not measured yet. That is [experiment E1](https://github.com/danoszz/penthera/blob/main/docs/research/prereg/E1-real-world-accuracy.md). ## What it never does - Commit, push, open a pull request, or touch a remote. The workflow file is written for you to review and commit yourself. - Write outside the project: reports, `.gitignore` and the workflow go under the project root (the git top level when there is one), the hook into the repository's hooks folder. - Contact anything but localhost, unless you turn offline mode off or confirm the running app. Exit code: 0 when the wizard finishes (whatever the grades), 2 for a bad flag. For a gate in CI, use the commands themselves (`penthera --repo .`, `penthera deps`): they exit 1 on high or critical findings. The pinned SHA is the last release tagged before this version (`ACTION_PIN` in `src/cli/onboarding.js`); Dependabot's `github-actions` updates keep it current in your repo. --- # Scanning your app: `penthera ` and `--repo` > **Starter.** For "is my app safe to ship?" Black-box scans of a running app (localhost, preview, or staging) and white-box scans of its source. The default scan is non-destructive. Payload-based testing is opt-in. ## Scan modes and profiles | Mode | Command | Coverage | |------|---------|----------| | **URL scan** | `penthera https://example.com` | TLS, fingerprinting, endpoint discovery, templates, CORS, cookies, JS CVEs, param discovery, OpenAPI/FastAPI probes, security headers, auth hardening | | **Repo scan** | `penthera --repo ./my-app` | API route discovery (Next.js, Express, Hono, Fastify), trust-boundary mapping, hardcoded-secret scanning (including Anthropic, OpenAI, Gemini, Groq, Hugging Face and other AI provider keys) | | **Combined** | `penthera https://staging.app --repo .` | Black-box and white-box in one run | | **Interactive** | `penthera` *(no args)* | Setup wizard: detects the project, runs the offline checks, offers the hook and workflow ([init.md](https://penthera.org/docs/commands/init.md)) | | **Machine audit** | `penthera --machine` | macOS checks (keyloggers, persistence, rootkits) | | **Dependencies** | `penthera deps` | What each npm or Python package can do (install scripts, shell, network, env, eval, native, obfuscation, `.pth` startup hooks), capability diff on upgrades, typosquats. Offline ([deps.md](https://penthera.org/docs/commands/deps.md)) | Pick a profile, or add individual flags for more depth: | Profile | Command | What runs | |---------|---------|-----------| | **quick** | `--profile quick` | Headers, OpenAPI, auth checks; no login POSTs (about 10s) | | **standard** | *(default)* | Full non-destructive scan: no attack payloads, and one `POST /api/login` with invalid credentials | | **deep** | `--profile deep` | recon, injection probes, API fuzzing, and on `/api/login` an 8-attempt rate-limit burst and an SQL error check | What each profile sends to `/api/login`: `quick` nothing; `standard` one login with obviously invalid credentials, to catch a login that accepts anything; `--deep` also 8 rapid attempts (rate limiting) and an `' OR '1'='1` string (SQL errors). A CDN firewall can read the last two as an attack and start challenging you, so they only run with `--deep`. Individual flags: `--recon`, `--deep`, `--fuzz`, `--adaptive`, `--nuclei `, `--templates `, `--all` ### Active modes ask first `--deep`, `--fuzz`, `--adaptive`, `--all` and `--profile deep` send test payloads (and, with `--deep`, the login burst above), so they need your confirmation before the first request: - In a terminal, Penthera asks. For a target on this machine (`localhost`, `127.0.0.1`, `*.localhost`) a yes is enough; for any other target, type its hostname back. - In CI, Docker or a script, pass `--confirm-target ` with the target's hostname. Without it, or with a different host, the run stops with exit code 2 before anything is sent. - Active scans send at most 10 requests per second. Change that with `--rate `; `--rate 0` removes the limit. `--rate` also works on a standard scan, which has no limit by default. ```bash penthera https://staging.myapp.com --profile deep --confirm-target staging.myapp.com penthera http://localhost:3000 --deep --rate 25 ``` ## Quick start ```bash # First time? Run with no args for guided setup penthera # Safe default scan (non-destructive) penthera https://myapp.com # Staging plus source code, full report set penthera https://staging.myapp.com --repo . --all -o reports/scan.json --sarif reports/scan.sarif # Baseline diff: show only new findings since the last scan penthera https://myapp.com -o reports/scan.json --baseline reports/previous.json # Authenticated scan (session cookie or bearer token) PENTHERA_BEARER=eyJ... penthera https://myapp.com --profile standard penthera https://myapp.com --auth-cookie "session=abc123" ``` Writing to `-o reports/scan.json` also drops a human-readable `reports/scan.md` next to it. `penthera init` can add `reports/` to your `.gitignore`. ### Exit codes | Code | Meaning | |------|---------| | `0` | No critical or high findings | | `1` | Critical or high findings detected | | `2` | Scan failed (unreachable target, bad config) | --- ## Output | Format | Flag | Use | |--------|------|-----| | Terminal | *(default)* | Colored summary plus top 3 fixes | | **Markdown** | `--markdown file.md` or `-o file.json` | Human-readable report (exec summary, findings tables, fixes). Written next to `-o` automatically | | JSON | `--json` or `-o file.json` | CI pipelines, baseline diffs, custom tooling | | SARIF | `--sarif file.sarif` | GitHub Security / Code Scanning tab | | AI-audit seeds | `--audit-seeds seeds.json` (with `--repo`) | Repo findings in Cloudflare security-audit-skill `findings.json` form, so an agent running the skill starts from them ([verify.md](https://penthera.org/docs/commands/verify.md#the-other-way-seeds-for-the-skill)) | | Audience report | `--audience developer\|owner\|auditor` | The same scan written for a developer, an owner or an auditor ([docs/evidence.md](https://penthera.org/docs/evidence.md#audience-reports)) | | Your template | `--report-template file.md` | Your own Markdown with `{{summary.critical}}`, `{{#each findings}}`, `{{#if x}}`; no code runs | | **Evidence bundle** | `--evidence [--sign-key ]` | All reports plus `manifest.json` with SHA-256s, optionally SSH-signed. Auditors check it with `penthera evidence verify --allowed-signers ` ([docs/evidence.md](https://penthera.org/docs/evidence.md)) | ### Coverage: what a repo scan read Every `--repo` scan adds a **Coverage** section to the terminal, Markdown and JSON (`coverage`) reports. For each check (the secret scan, route and code-pattern analysis, runtime versions) it says how many files were read and why the others were not: file type not read, over the size limit, a language route analysis does not support, or no API routes found. Source files no code analysis read are listed as blind spots, up to 200. A file that was not read is unchecked, not clean. Dependency behaviour and agent config have their own commands (`penthera deps`, `penthera agents`) and are listed as not part of a repo scan. ## CI integration ```yaml # Scan a staging URL on a schedule and upload results to GitHub Security. - run: npx penthera@1.9.0 "$PENTEST_URL" --profile standard --sarif reports/scan.sarif - uses: github/codeql-action/upload-sarif@6f2ac81f5cd81fe351a197ba0b00d91d03cd2aee # v4.39.0 with: sarif_file: reports/scan.sarif ``` Copy the full workflow from [`docs/examples/github-actions-scan.yml`](https://github.com/danoszz/penthera/blob/main/docs/examples/github-actions-scan.yml) into your app's repo. Pin the Penthera version so a scheduled run never picks up code you have not reviewed. Penthera's own CI runs the full test suite on Node 22 and 24, self-scans a mock vulnerable API on every push, and runs CodeQL and OpenSSF Scorecard. ## Configuration For live integration tests against your own routes: ```bash cp pentest.config.example.js pentest.config.js # gitignored ``` Edit the route groups, then run `npm run pentest:live`. Set `PENTEST_BASE_URL` to point at staging without editing the file. [Back to the README](https://penthera.org/README.md) · [All commands](https://penthera.org/docs/commands/README.md) --- # Dependency risk: `penthera deps` > **Starter.** For "is this package safe to add?" ## Dependency risk: what can your packages do? A lockfile change is code you did not write, about to run with your app's access to files, network, environment and CI secrets. `penthera deps` reads `package-lock.json`, statically scans every installed package, and tells you what each one *can do*, and which packages are new or gained capabilities in an upgrade. ```bash penthera deps # every package in node_modules penthera deps --base origin/main # capability diff vs the lockfile on main penthera deps --diff ../base-checkout # diff vs another installed tree (or an old package-lock.json) penthera deps --pr-comment deps.md # short Markdown diff for a PR comment ``` ``` ▲ net-helper 1.0.0 → 1.1.0 + network, whole environment node_modules/net-helper/index.js:3 const https = require('https'); node_modules/net-helper/index.js:5 const body = JSON.stringify(process.env); ``` | Capability | Detected from | |---|---| | Install scripts | `preinstall` / `install` / `postinstall`, and `binding.gyp` (npm runs `node-gyp` implicitly). Flagged as risky when the hook pipes a download to a shell or runs a file with network or decoding code | | Shell, network, dynamic code | `child_process`; `http`/`https`/`net`/`dgram`/`tls`/`http2`, `fetch`, `WebSocket`; `eval`, `new Function`, `vm` | | Environment | `process.env` reads, and bulk access (`JSON.stringify(process.env)`, enumeration). Serializing the environment next to network code is reported as likely exfiltration | | Files, modules, native | Writes near home-directory or system paths; computed `require()`/`import()`; `.node` addons and `binding.gyp` | | Obfuscation | Obfuscator identifiers (`_0x…`), `\x`-escape density, encoded blobs that are decoded and run (embedded WebAssembly and ANSI color codes are recognized and ignored) | | Attack indicators | Writes to `~/.claude`, `~/.cursor`, `.mcp.json` or the Claude Desktop config; running the `claude`, `gemini` or `q` CLIs (the s1ngularity pattern); GitHub token harvesting | | Typosquats | Edit distance, separator and suffix tricks against ~7,800 popular npm names, vendored in the package and refreshed at release time | | Known-bad versions | Package versions, exfil endpoints and dropped files from named campaigns (s1ngularity, Shai-Hulud, Shai-Hulud 2.0, postmark-mcp), from the signed [rule packs](https://penthera.org/docs/rule-packs.md) that ship with each release. Matched against the lockfile, so a bad version is reported even when it is not installed | Every capability carries `file:line` evidence. It is a regex-and-tokenizer pass, not a full parser: deterministic, never executes package code, and fast enough for every PR (0.4 s for Penthera's own 61 packages, 5 to 16 s for real app trees of 500 to 1,700 packages). A capability is what code *can* do, not proof that it is malicious; the findings carry confidence labels (`potential` for obfuscation and typosquats). **Offline by default.** Old versions for the diff come from the base tree's `node_modules` or from npm's local cache (`~/.npm/_cacache`), looked up by the lockfile's integrity hash and verified byte for byte. `--online` is the only network use: it asks the npm registry for publish dates, publisher changes and deprecations, and fetches old tarballs the cache does not have (verified the same way). To comment the capability diff on every PR that changes the lockfile, use the GitHub Action below. ### npm and Python The same command reads Python projects: `uv.lock`, `poetry.lock`, `pdm.lock`, `Pipfile.lock` or `requirements*.txt` (pinned and `--hash` lines), and the installed `.venv` / `venv` (or `--venv `) through `site-packages/*.dist-info` `RECORD` files, without running pip or Python. A repo with both a `package-lock.json` and Python inputs gets one combined report. ```bash penthera deps # uv.lock + .venv penthera deps --venv ~/.venvs/app # a virtualenv somewhere else (no lockfile needed) penthera deps --base origin/main # capability diff vs the Python lockfile on main ``` Capabilities and evidence are the same as for npm (`subprocess`/`os.system`, `socket`/`requests`/`httpx`/`aiohttp`, `os.environ` bulk access, `eval`/`exec`/`compile`, computed `__import__`, native `.so`/`.pyd`, AI-agent indicators), plus what is specific to Python: `setup.py` custom commands (install-time code), `.pth` files with `import` lines and a shipped `sitecustomize.py` or `usercustomize.py` (they run on every interpreter start, so installing the package is enough to run them: medium, high when the code they load is risky), including ones a wheel places at the site-packages root from `-.data/purelib/` or `platlib/`, as the malicious litellm 1.107.0 did, unpickling, `exec` of a base64/zlib-decoded payload, installed files that no longer match their `RECORD` hash, and `.pth` files no distribution owns. Old versions for the diff come from pip's HTTP cache (matched by the lockfile's sha256) or uv's cache (by recorded hash), otherwise they are marked "not compared". Typosquats are checked against the top 8,000 PyPI projects, PEP 503 normalized. Known-malicious PyPI versions, dropped files and endpoints come from the `ioc-pypi-2026` rule pack and match whether or not the package is installed. `--online` registry checks are npm-only for now. ### License policy ```bash penthera deps --license-policy permissive # or weak-copyleft-ok, no-agpl penthera deps --license-policy legal/policy.json # a file with a "licenses" section ``` Without the flag, `penthera deps` applies the `licenses` section of `penthera.policy.json` when there is one: ```json { "licenses": { "preset": "permissive", "allow": ["Python-2.0"], "deny": ["WTFPL"], "allowPackages": ["@acme/*", "sharp@0.33.5"], "unknown": "warn" } } ``` | Preset | Allows | |---|---| | `permissive` | MIT, ISC, BSD, Apache-2.0, 0BSD, Unlicense, CC0, Zlib, BlueOak and similar. Everything else breaches | | `weak-copyleft-ok` | `permissive` plus LGPL, MPL, EPL, CDDL and Artistic-2.0 | | `no-agpl` | Anything except AGPL and SSPL (network copyleft) | Licenses come from the lockfile (npm records them in v2 and v3), else from the installed `package.json`. Expressions are parsed as SPDX: `MIT OR GPL-3.0-only` passes `permissive` because one branch is enough, while `MIT AND GPL-3.0-only` does not. Deprecated ids (`GPL-3.0`, `GPL-2.0+`) are matched to their current form, so a deny of `AGPL-3.0-only` also catches a package that says `AGPL-3.0`. A breach and `UNLICENSED` (no rights granted) are high. A missing, custom (`SEE LICENSE IN ...`) or non-SPDX license is medium, or high with `"unknown": "block"`. `allowPackages` exempts packages your legal review has cleared. Findings use category `license` and appear in every output format, and `--sbom` records each component's license. [Back to the README](https://penthera.org/README.md) · [All commands](https://penthera.org/docs/commands/README.md) --- # Install firewall: `penthera install` > **Starter.** For "is this package safe to add?" ### Install firewall: `penthera install` `penthera deps` tells you what changed after the fact. `penthera install` asks before anything runs: ```bash penthera install left-pad # check, then install (asks if the policy says so) penthera install -D vitest --dry-run # report only penthera install --yes # CI: install only if nothing breaches the policy ``` ``` ▲ color-helper 1.0.0 → 1.1.0 + install script postinstall: node postinstall.js Policy defaults (no penthera.policy.json) ! color-helper@1.1.0 needs approval: install script Install anyway? [y]es, run the install scripts / [s]kip scripts / [N]o: ``` 1. npm resolves the new tree in a temporary copy of `package.json` and the lockfile, with scripts off (`npm install --package-lock-only --ignore-scripts`). 2. New tarballs are fetched with `npm cache add`. Nothing is unpacked into `node_modules` and no package code runs; each tarball is checked against the proposed lockfile's integrity before it is read. 3. The `deps` engine diffs capabilities against your current lockfile, lists new install scripts with their commands, and checks new names for typosquats. 4. `penthera.policy.json` decides. On approval, the reviewed lockfile is installed as is, with `--ignore-scripts` unless you approved the scripts (on npm 12+, approved scripts are pinned in `package.json` `allowScripts`). On abort, exit code 1, and the project is byte for byte unchanged. ```json { "allow": ["@acme/*", "esbuild", "sharp@0.33.5"], "deny": ["ai-cli-invoke", "token-harvest"], "requireApproval": ["install-scripts", "startup-hook", "ai-config-write", "ai-cli-invoke", "token-harvest", "obfuscated", "typosquat", "unscanned"] } ``` `deny` and `requireApproval` take capability ids from the [capability table](https://penthera.org/docs/commands/deps.md) (`install-scripts`, `shell`, `network`, `env-bulk`, `obfuscated`, ...) plus `typosquat` and `unscanned`. The `requireApproval` list shown is the default. `allow` takes names or globs, optionally pinned to a version, and overrides both. The one network use is npm talking to your configured registry, and Penthera prints which one; Penthera itself sends nothing. Workspaces and `file:` dependencies are not supported yet. [Back to the README](https://penthera.org/README.md) · [All commands](https://penthera.org/docs/commands/README.md) --- # The local dashboard: `penthera ui` > **Starter.** For "can I see it all in one place?" `penthera ui` opens Penthera in your browser: whether your app is safe to ship, what to fix first, in plain words, and the fix loop that proposes a change and waits for your OK. It looks like a small 1-bit computer: a menu bar over three windows. It runs on `127.0.0.1` only, reads only the folders you give it and `~/.penthera`, and never contacts the network. | Simple, dark | Advanced, light | |---|---| | ![Simple mode in the dark theme: the verdict, problems in plain words, and one problem open](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-dark.jpg) | ![Advanced mode in the light theme: a project with its tabs, the triage matrix and a finding open](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-light.jpg) | ## Start it ```bash penthera ui --open # current folder, reports in ./reports penthera ui --project ~/code/shop --project ~/code/api --reports ~/code/shop/reports penthera ui --demo # fake data, nothing read from disk ``` | Option | Default | What it does | |--------|---------|--------------| | `--port ` | `0` (random free port) | Port on 127.0.0.1 | | `--open` | off | Open the one-time URL in your browser, through a launcher file only you can read, so the token is not on a command line | | `--project ` | the current folder | A folder the dashboard may read and run in. Repeat for more | | `--reports ` | `/reports` | Where report JSON is read from, and where runs started here write theirs. The default folder must not be a symlink; name a symlinked folder here to use it on purpose | | `--demo` | off | Realistic fake data and a scripted fix loop. For a first look, screenshots and docs | It prints a URL with a one-time token after the `#`, which the browser never sends over the network. Open it once: the page swaps the token for a session that lives in that tab (a reload keeps it, a new tab or browser does not), and the token stops working, so a second browser or a link that leaked into history is refused. Restart `penthera ui` for a new link. Ctrl-C stops the server. ## First visit: setup The first time a browser opens the dashboard, a three-screen setup runs (run it again from **Penthera ▸ Set up…**): 1. What Penthera does, in two sentences, and the rule: only check apps you own or have written permission to test. 2. **Simple** or **Advanced**. Simple answers "is my app safe, and what do I do?" in plain words with one button at a time. Advanced shows everything. Switch any time in the Penthera menu or Settings; the choice is remembered in this browser. 3. Your project: the folder (one of the `--project` folders) and, optionally, where the app runs. **Check my app now** starts a first scan; **Skip** closes setup. Closing it early keeps Simple. ![Setup, choosing Simple or Advanced](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-setup.jpg) ## The view **Menu bar.** The Penthera menu (the wordmark; the P alone on small screens) holds About, Set up, Settings, the mode and the theme. Simple mode adds **Check** (Check my app, Fix problems for me, the running check) and **View**. Advanced has **File**, **Scan**, **Fix**, **Evidence** and **View** instead. Every item shows its shortcut. On the right: the run in progress (blue, "Fix 1 of 3 waiting for you", when a change waits), and **Offline** or **Online** for scans started here (click to switch). **Projects** (left, `[` to hide). **New scan…** on top, then "All projects" and every project with its A to F grade, open problems and last check. A project with a run in progress shows a blinking square. **The document** (centre). For a project, or all of them: - **The verdict**: one sentence as the headline that answers "can I ship?", such as "Not ready to ship: 1 critical problem to fix first.", with the age of the check, the counts to fix and fixed, and the score out of 100 as a 10 × 10 grid of pixels (for all projects, the lowest score). One button is blue: **Fix problems for me…** in Simple, the first finding in Advanced. While a problem is open in the inspector, its **Fix it for me…** is the blue one instead. - **Simple**: the problems, in plain words (Critical, Serious, Worth fixing, Minor), "Fix first" before "When you have time", then what was fixed since the check before, folded into one line. No paths, categories or charts. - **Advanced**: five tabs (`1` to `5`). **Findings** has what changed since the last scan, a triage matrix of severity against how sure Penthera is (Confirmed, Likely, Needs a person; pick a cell to filter) and the full list (`/` searches, `j` and `k` move). **Dependencies** has the capability diff, typosquats and install firewall decisions. **AI tooling** has the grade, `penthera.lock` drift and hidden-content hits. **History** has open findings per scan, every run, the commits the fix loop made and the memory chain. **Proof** has the hosts the last scan contacted and evidence bundles: create, verify, and download the auditor, owner or developer report. A run streams into the centre in **check and fix** mode: the side windows step aside, and **Back** at the top left (or `Esc`) returns to the project. In Simple mode it is one sentence ("Fix 2 of 3 is waiting for you to approve it.", "Done. 2 fixes applied and checked, 1 undone.") with the raw output folded under "Show what it is doing". In Advanced it is the transcript, with a gutter square per step: filled when done, hollow on the step in progress. **The inspector** (right, `]` to hide) shows one thing. With a problem picked: in Simple, what is wrong, how sure Penthera is, what to do and how to check the fix, then **Fix it for me…**, with the evidence and the code change behind "Show the details"; in Advanced, the same in plain words plus where, the evidence, sources and the fix-recipe diff. `Esc` goes back. With nothing picked it shows the **halftone field**: dark with dots when something serious is open, plain paper when nothing is. The dotted desktop between the windows follows the same rule. **Every change waits in a window.** When the fix loop has a change, a pinstriped window opens over a heavy blur: "Penthera wants to change 1 file to fix this:", the problem, what it will do, and the change (folded in Simple). **Apply** (`y`), **Skip** (`n`) or **Stop** (`q`). Closing the window decides nothing; the change stays waiting, in the transcript and the menu bar. A diff over 60,000 characters is cut, and the window says so; Apply (and `y`) then stays off until you tick that you have not seen all of it. Every Apply names the SHA-256 of the diff it showed, and the server applies nothing else. ![A change waiting for a decision](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-run.jpg) Below 1180px the inspector becomes a drawer; below 760px the projects window does too. A tap outside closes them. | Narrow, dark | Narrow, light, a problem open | |---|---| | Simple mode at 390px, dark | A problem in the drawer at 390px, light | ## User stories | Story | Where it lives | |---|---| | *As someone who is not a programmer, I want to know if my app is safe without learning security words.* | Simple mode: the verdict sentence, problems as Critical, Serious, Worth fixing or Minor, and one button at a time. | | *As a solo founder before launch, I want one sentence that says whether I can ship.* | The verdict at the top of every project. | | *As a first-time user, I want to be walked through setting up my project.* | The three-screen setup on the first visit. ![First run](https://github.com/danoszz/penthera/raw/main/assets/dashboard/ui-start.jpg) | | *As a founder with no security engineer, I want fixes applied only when I approve them.* | The approval window: Apply, Skip or Stop on every change; verified fixes land as local commits on a separate branch, failed ones are undone, nothing is pushed. | | *As a developer, I want to start a scan and be stopped before I hit a host I do not own.* | New scan…: a remote hostname must be typed back before the run starts. | | *As a product owner, I want one problem explained in plain words.* | Pick it: the inspector leads with what is wrong, how sure and what to do. | | *As a returning user, I want to see if something is waiting for me wherever I am.* | The menu bar: "Fix 1 of 3 waiting for you". | | *As a tech lead, I want to triage by how serious and how sure.* | Advanced ▸ Findings: the triage matrix. | | *As a tech lead after a release, I want to see what changed.* | "Since the last scan" and Advanced ▸ History. | | *As the person answering the NIS2 questionnaire, I want evidence an auditor can check.* | Advanced ▸ Proof: create and verify a bundle, download the auditor report. | | *As a developer reviewing a lockfile bump, I want to see what packages gained.* | Advanced ▸ Dependencies. | | *As someone who installs skills and MCP servers, I want to know if any hide instructions or drifted.* | Advanced ▸ AI tooling. | | *As a keyboard user, I want every action without the mouse.* | Every menu item shows its shortcut; the menus, tabs and windows follow the WAI-ARIA patterns. | ## Running things from the page **New scan…** (or `n`) opens a window: what to run, the folder, an optional URL and the offline switch. In Simple mode it offers two things, "Check my app" and "Fix problems for me"; Advanced offers scan, dependencies, AI tooling and the fix loop. It calls the same functions as the CLI (`executeScan`, `scanDeps`, `scanAgents`, `runLoop`) with the same rules. The fix loop needs a clean git tree on a branch, works on a local `penthera/fix-` branch, runs only allow-listed local git commands with hooks off, is always offline with egress limited to the URL's host, and never pushes. On top of that: - The project folder must be inside one of the `--project` folders. - A URL that is not on this machine (anything but `localhost`, `127.x.x.x`, `[::1]` and `*.localhost`) needs its hostname typed back before the run starts. Only scan what you own or have written permission to test. - One run at a time. Closing the tab does not stop a run; open it from the menu bar or the project's History tab and its transcript replays and continues. Stop finishes the current step, and the fix branch holds only verified commits. - Reports land in the reports folder (`loop-.json`, `deps_.json`, and so on), so every screen picks them up. A write is refused when the reports folder (or `evidence/` in it) no longer resolves to the folder checked at startup, and evidence bundles are built in a private temp folder and renamed into place. ### AI mode A scan of a project folder has an **AI mode** switch, off. Turning it on opens a sheet, on every new scan: a picture of where your code goes (your code, Penthera, Ollama on this computer, and "internet" crossed out), the model picker, the same Model / Reads / Sends / Decides lines the terminal shows, and two buttons of equal weight, "Use on this computer" and "Not now" (Esc). A model that runs on ollama.com turns the last box into "ollama.com" and needs the host typed before its button works. The server asks Ollama for the model's route itself, refuses an AI run without that consent or with an older consent text, and logs each consent locally. A run shows an "AI · local" (or "AI · ollama.com") tag that opens to say which model ran where; when it ends, the model's unverified proposals are folded away under their own heading and are not counted anywhere else. Settings lists the consent log. See [ai-mode.md](https://penthera.org/docs/ai-mode.md). ## Security model The dashboard is a web server on your machine, so any page open in the same browser can send it requests. Every request passes, in this order: 1. **Host check.** The `Host` header must be exactly `127.0.0.1:` or `localhost:`. This blocks DNS rebinding, where `attacker.example` re-resolves to 127.0.0.1 so the attacker's page can read the response. It runs before the token check, so a rebinding page cannot even redeem the link. 2. **Path check.** No dot segments, encoded dots or slashes, backslashes or NUL bytes. Static files come from an allowlist built at startup; no part of a request path is ever joined onto a filesystem path. 3. **Session.** The one-time token sits in the URL fragment, so it never reaches a server or a log. The page posts it to `/api/session` once, gets a session id back, removes the token from the address bar and keeps the id in that tab's `sessionStorage`. Every `/api` request sends it as `Authorization: Bearer`; the server keeps only its SHA-256 and compares in constant time. There is no cookie: cookies are not bound to a port, so every other server on 127.0.0.1 you visit would receive one. The run transcript is read with `fetch`, not `EventSource`, so it carries the header too. The page and its scripts are served without a session (they hold no data). Requests without a session are rate-limited (429 with `Retry-After`). 4. **Origin.** Every `POST` needs an `Origin` equal to the server's own (and `Sec-Fetch-Site: same-origin` when the browser sends it) and a JSON body, so a cross-site form or `fetch` cannot change anything. Every response carries: ``` Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; require-trusted-types-for 'script' X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: no-referrer Cache-Control: no-store Cross-Origin-Opener-Policy / -Resource-Policy / -Embedder-Policy ``` There are no CORS headers, and `OPTIONS` gets a 405. The page has no inline script or style, loads nothing from another host (no CDN, fonts, analytics or chart library), and never turns data into HTML: finding titles and evidence come from the sites and repos you scanned, so they are only ever set as text. Report downloads are served as `text/markdown` attachments from the same checks; the audience is one of `auditor`, `owner` or `developer`, nothing else. Limits: a request body over 64 KB gets a 413, a request must arrive within 30 seconds, and an idle connection closes after two minutes (the run transcript excepted; it sends a keep-alive line instead). At most 16 transcripts stream at once, and the 20 most recent finished runs are kept for replay. Penthera scans its own dashboard clean with `--profile standard`, signed in and not; `tests/ui.test.js` runs that scan on every test run, alongside tests for the Host check, the token and bearer session, Origin, the headers, the rate limit and path traversal. ## Design System 1-bit, blue ink: ink, paper and `#0000ed`, and nothing else. It should feel like a security instrument from a calmer era of computing, honest about what it knows. - **Windows.** Every pane is a window with a 1px frame and a title bar. The window that holds focus has the solid title bar, so you can see which one `j`, `k` and `Esc` act on. Only floating windows (setup, New scan, About, the approval) have the hard 3px shadow, over a heavy blur. The approval alone is pinstriped: pinstripes mean "waiting on you". - **No greys.** Shades are dither patterns: solid, hatched, half, sparse, dotted, empty. Severity is a word plus its pattern, never colour alone. - **One accent.** `#0000ed` marks selection, the default button (with the classic outer ring), the focus ring, chart lines and links, and the menu bar when a change waits for you. On black it measures 2.2:1, so there it is only a fill with white on it (9.5:1), a frame or a ring; text in that hue uses `#7a7aff` (6.1:1). On paper `#0000ed` is text-safe (9.3:1). Green is retired: OK is a ✓ and a word. - **Type, three roles.** [Departure Mono](https://departuremono.com) (OFL, self-hosted, 11 and 22px) for chrome, labels and numbers; `ui-monospace` for paths, evidence, diffs and transcripts; the system sans for sentences meant for people. - **The logo, once per surface.** The horizontal wordmark in the menu bar (the P mark below 760px), the P on a blue tile as the favicon, the wordmark large in About, setup and the first-run screen. Never the mark and the wordmark side by side, never in a window's title bar. - **The halftone field.** The one strange element, and it carries data: its density is the open risk. Blobs are seeded by the project, so each keeps its shape. Drawn on a canvas once per size or theme change, never animated. - **Both themes pass WCAG AA** for every text node in every state (Simple and Advanced, every tab, a finding open, settings, menus, setup, New scan, About), measured in the browser, composited. - **Motion.** Almost none. Run output types itself out and the cursor and the live squares blink; with `prefers-reduced-motion` all of that stops. With `prefers-reduced-transparency` the blur becomes a plain dark backdrop. - **Keyboard.** `n` new scan, `/` search, `j`/`k` findings, `1` to `5` tabs (Advanced), `[`/`]` side windows, `y`/`n`/`q` answer a change, `Esc` closes a menu, the detail, the search or a drawer. Every control is a real button or link; dialogs trap focus and give it back; screen readers get each log line whole. The screenshots in `assets/dashboard/` come from the demo data: `node scripts/dashboard-screenshots.mjs` regenerates them with a local Chrome. --- # Privacy and network use > **Pro.** For "what does it send over the network?" Penthera has no telemetry. It does not collect, store or send usage data, analytics or personal information to us or to any third party. This applies to the CLI, the dashboard and the npm package. The website, [penthera.org](https://penthera.org), is separate: it counts anonymous page views and load times with Vercel Web Analytics and Speed Insights, which set no cookies. The browser check on the site never sends the files you paste; its Content-Security-Policy allows only those two counters. - **Scan traffic goes to the target you name.** Auth tokens passed with `--auth-bearer`, `--auth-cookie`, or `PENTHERA_*` env vars are sent only to that target. - **Every request identifies itself** as `penthera/ (+https://github.com/danoszz/penthera)`, so a target's operator can tell where the traffic comes from. Override it with `--user-agent`. - **Reports stay local.** Penthera writes them to `reports/` (gitignored) on your machine. - **Every scan lists the third-party hosts it contacted** besides the target, in the terminal summary and in the report's "Network egress" section. Without `--offline`, a scan may contact these third parties: | Host | When | What it learns | |---|---|---| | `raw.githubusercontent.com`, `cdn.jsdelivr.net` | Retire.js check (standard and deep profiles) | That someone downloaded the public database. Nothing about your target. | | `crt.sh`, `web.archive.org`, `otx.alienvault.com` | `--recon` only | The target domain. | | CDN hosts serving your page's `