Scanning your app: penthera <url> 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
Pick a profile, or add individual flags for more depth:
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 <path>, --templates <dirs>, --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 <host>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 <n>;--rate 0removes the limit.--ratealso works on a standard scan, which has no limit by default.
Quick start
-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
Output
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.
Library mode: scanning a package
In an app, untrusted data arrives in a request. In a library it arrives as an argument: whoever calls an exported function decides what its parameters hold. In library mode the repo scan treats those parameters as untrusted too.package.json looks
publishable: it has main, exports or bin (or an index.js next to it),
is not "private": true, and does not depend on an app framework such as
Express, Koa, Fastify or Next.js. The report says when it is on, and why.
How it works. Penthera finds the functions a caller can reach
(module.exports, exports.x, prototype and class methods of an exported
constructor, objects and instances returned by an exported factory, members
copied onto exports in a loop, ESM export). It
follows each parameter through assignments, closures and callbacks inside that
function, and one call further: when the function passes the value to a named
function in the same file, that function’s parameter is followed too. A value
a constructor or setter keeps on this (this.path = path) is followed into
the other methods of the same class or prototype. A shell command built from one is reported as command-injection
at potential confidence and severity high. A function whose whole job is to
run its argument (exec(cmd)) is reported at severity medium, with the note
that callers must not pass untrusted input. In library mode the scan also
reads dist/ or build/ when package.json points there, and skips tooling,
examples and docs folders.
How we know. On the dev half of SecBench.js (49 command injections in npm
packages, the half we tuned on), strict recall went from 0.00 to 0.82, 95% CI
0.69 to 0.90 (40 of 49), in the first version. The helper step and fields on
this took it to 0.94, 95% CI 0.83 to 0.98 (46 of 49); 39 of 49 without the
“runs its argument” cases. It flagged no command injection in 150 npm packages
of the benign baseline (dev half), and nothing new in 16 app repositories. The
sealed test half is not scored yet.
Limits. Only one helper call is followed, and only to a function the file
calls by name. A field set from another field is not followed. Sanitizers are not recognized, so a
correctly escaped command can still be reported; that is what potential
means.
CI integration
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:npm run pentest:live. Set PENTEST_BASE_URL to
point at staging without editing the file.
Back to the README · All commands