@pbpyrojust/universal-security-audit 0.0.0-stage → 0.3.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 (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +435 -2
  3. package/auth-config.example.json +12 -0
  4. package/bin/universal-security-audit.mjs +134 -0
  5. package/branding.example.json +10 -0
  6. package/package.json +60 -4
  7. package/scripts/compare-security-runs.mjs +94 -0
  8. package/scripts/generate-baseline.mjs +66 -0
  9. package/scripts/generate-security-report.mjs +99 -0
  10. package/scripts/generate-security-sarif.mjs +93 -0
  11. package/scripts/generate-security-tickets.mjs +126 -0
  12. package/scripts/lib/authorization.mjs +54 -0
  13. package/scripts/lib/baseline.mjs +37 -0
  14. package/scripts/lib/drupal-admin-audit.mjs +84 -0
  15. package/scripts/lib/exposed-paths.mjs +111 -0
  16. package/scripts/lib/fingerprint.mjs +154 -0
  17. package/scripts/lib/graphql-introspect.mjs +55 -0
  18. package/scripts/lib/infra-topology.mjs +71 -0
  19. package/scripts/lib/intensity.mjs +30 -0
  20. package/scripts/lib/misc-checks.mjs +78 -0
  21. package/scripts/lib/pentest-recon.mjs +220 -0
  22. package/scripts/lib/pii-payment.mjs +79 -0
  23. package/scripts/lib/port-scan.mjs +72 -0
  24. package/scripts/lib/proxy-support.mjs +14 -0
  25. package/scripts/lib/report-builder.mjs +113 -0
  26. package/scripts/lib/scoring.mjs +44 -0
  27. package/scripts/lib/script-inventory.mjs +131 -0
  28. package/scripts/lib/security-headers.mjs +115 -0
  29. package/scripts/lib/security-txt.mjs +69 -0
  30. package/scripts/lib/subdomain-enum.mjs +64 -0
  31. package/scripts/lib/tls-enum.mjs +49 -0
  32. package/scripts/lib/vuln-lookup.mjs +118 -0
  33. package/scripts/lib/whois-lookup.mjs +44 -0
  34. package/scripts/lib/wordfence.mjs +112 -0
  35. package/scripts/lib/wp-admin-audit.mjs +102 -0
  36. package/scripts/lib/wp-plugin-directory.mjs +58 -0
  37. package/scripts/lib/wp-rest-audit.mjs +59 -0
  38. package/scripts/run-batch-audit.mjs +111 -0
  39. package/scripts/run-security-audit.mjs +1192 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Adams
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,436 @@
1
- # Temporary Holding Version
1
+ # Universal Security Audit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Technical security & attack-surface audit CLI. Companion tool to [universal-seo-audit](../universal-seo-audit) and [universal-accessibility-audit](../universal-accessibility-audit) — same crawler/report/ticket/compare conventions, different lens.
4
+
5
+ Runs a battery of unauthenticated, publicly-visible checks against a website and produces a risk-graded HTML/PDF dashboard, CSV data files, a ticket-ready backlog, and run-over-run comparisons.
6
+
7
+ ## What's new in 0.3.0
8
+
9
+ - **Pentest checks need confirmed permission.** The active checks (exposed paths and admin/login pages, GraphQL introspection, subdomain liveness, the port scan, open-redirect/HTTP-method/user-enumeration/error-disclosure probes, and the authenticated admin audit) only run when you confirm you own the site or have the owner's written permission: `--authorized`, or typing "yes" at the prompt. Without it a run is passive only. The confirmation (exact statement, time, user, how) is saved in `summary.json` and `full-report.json`. **Scripts and CI that relied on the active checks need `--authorized` added.**
10
+ - **Open redirect:** only a redirect whose destination is the test host counts. Sites that redirect www → apex (or http → https) keep the query string, which made every redirect parameter a false positive.
11
+ - **WPScan:** a refused key (WPScan answers 403 for a missing or invalid token) now stops the lookups after the first one with one message.
12
+ - **Wordfence Intelligence** as a second source of WordPress core/plugin/theme vulnerabilities, with your own free key (`--wordfence-key` or `WORDFENCE_API_KEY`). Only for sites detected as WordPress. The feed is downloaded at most once a day and kept as a compact index in `~/.cache/universal-security-audit/`; components WPScan already reported aren't repeated. See [WordPress CVE lookups](#wordpress-cve-lookups).
13
+
14
+ ## What's new in 0.2.0
15
+
16
+ - **security.txt (RFC 9116) validation:** finds it at `/.well-known/security.txt` (or the legacy `/security.txt`) and checks the required Contact and Expires fields, an expiry date that hasn't passed (and isn't more than a year out), HTTPS, `text/plain`, Canonical and Policy; reports PGP signing. A missing or expired file is a finding.
17
+ - **WPScan free tier:** when the daily limit (25 requests) is hit, the remaining WordPress CVE lookups are skipped with one clear message instead of an error per plugin.
18
+ - Playwright 1.64, undici 8.12 and current dependencies.
19
+
20
+ ## Part of Vinny
21
+
22
+ This scanner is the free, open-source engine behind the [security checks in Vinny](https://vinny.justwhat.net/features/#security). Everything here stays free. [Vinny](https://vinny.justwhat.net) runs the same header grading on a schedule for any website, alongside [accessibility](https://vinny.justwhat.net/features/#accessibility), [SEO and rank tracking](https://vinny.justwhat.net/features/#seo), with history, alerts and fix tickets ([free for one site](https://vinny.justwhat.net/pricing/)). For WordPress, the [Universal Security Audit plugin](https://wordpress.justwhat.net/#plugins-heading) is coming soon.
23
+
24
+ ## ⚠️ Authorized use only: pentest checks need written permission
25
+
26
+ **Only run this tool against sites and infrastructure you own, or that you have the owner's explicit written permission to test.** Some of what it does is the recon phase of a penetration test, and in many places requests like these count as security testing that needs permission, even when every request is harmless on its own. Unauthorized scanning can break computer-misuse laws (such as the US CFAA or the UK Computer Misuse Act) and hosting providers' terms. Permission from a site's owner doesn't cover the hosting company's own servers: many hosts forbid port scans of their network, so add `--skip-port-scan` unless the host allows it.
27
+
28
+ The checks come in two kinds:
29
+
30
+ - **Passive (always on):** what any visitor gets. The pages, response headers and cookies, TLS, DNS and WHOIS, security.txt, the scripts and libraries the pages load, and public vulnerability databases (OSV.dev, WPScan).
31
+ - **Active (pentest-style, need confirmed permission):** exposed config/backup/admin/login/API paths and GraphQL introspection, subdomain liveness, a TCP port scan, open-redirect, HTTP-method, WordPress user-enumeration and error-disclosure probes, and the authenticated WordPress/Drupal admin audit.
32
+
33
+ The active checks only run after you confirm: pass `--authorized` (for scripts and CI), or type "yes" when an interactive run asks. Without that, a run is passive only and says what it skipped. The confirmation is written into the run's `summary.json` and `full-report.json` (`authorization`: the exact statement and its version, the time, your OS user name and how it was given), so each report shows what was agreed.
34
+
35
+ Even the active checks stay gentle: every probe is a single GET, OPTIONS, DNS lookup or TCP connect to a publicly documented path, record or port. No auth bypass, brute force, fuzzing or payload injection; destructive HTTP verbs (PUT/DELETE) are only *introspected* via OPTIONS, never sent. The authenticated admin audit only uses credentials you supply and only reads screens that account can already see.
36
+
37
+ **No warranty, no liability.** This software is provided "as is" under the MIT License (see `LICENSE`): the authors aren't liable for any claim or damage arising from its use. You are solely responsible for having permission for every site you test and for how you use the results.
38
+
39
+ ## What it checks
40
+
41
+ **Security headers** — A+–F grade covering CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP/CORP, `Server`/`X-Powered-By` version disclosure, and cookie `Secure`/`HttpOnly`/`SameSite` flags.
42
+
43
+ **Platform & infrastructure fingerprinting** — CMS/platform detection (WordPress, Drupal, Joomla, Shopify, Squarespace, Wix, Webflow, Magento, Ruby on Rails, Next.js/SPA); hosting/CDN detection (Cloudflare, Vercel, Netlify, Fastly, AWS S3/CloudFront, GitHub Pages, Pantheon, WP Engine, origin Apache/nginx); DNS records (A/AAAA/MX/NS/TXT); TLS certificate expiry and trust chain. Rails is detected via its CSRF meta tags (`csrf-param`/`csrf-token`), `authenticity_token` form fields, the `X-Runtime` header, or its default `_appname_session` cookie naming convention.
44
+
45
+ **Multi-platform / hybrid-architecture detection** — platform signals and backend headers (`Server`/`X-Powered-By`) are compared *across every scanned page*, not just the homepage. A different high-confidence platform (or a different backend header) showing up on a different page is reported as a likely hybrid architecture (e.g. a WordPress marketing site with a separately-built donation/fundraising app) — each stack has its own patching surface and CVE exposure. This only works as well as your page coverage: use `--crawl` with a reasonable `--max-pages` for sites you suspect might be hybrid, since a single-page scan can only ever see whichever platform served that one page.
46
+
47
+ **Multi-server / reverse-proxy detection** — three complementary signals, all built from data the tool is already collecting or a handful of extra plain GETs to the homepage:
48
+ - **Reverse proxy indicators**: a `Via` header, or seeing both a CDN/edge signal (Cloudflare, Fastly, etc.) and an origin-server signal (nginx/Apache) in the same response
49
+ - **DNS load-balancing hint**: more than one A record with no recognized CDN in front (CDNs also return multiple IPs, but those are edge nodes, not your own servers — already excluded)
50
+ - **Response-header consistency**: several repeated requests to the homepage compared for `Server`/`X-Powered-By`/`Via`/`X-Served-By` differences — inconsistent answers to the same URL over time is a solid signal of multiple backend instances behind a load balancer that may not be equally patched/configured
51
+
52
+ **TLS protocol enumeration** — actually handshakes with the server pinned to each of TLS 1.0/1.1/1.2/1.3 (rather than only reporting whatever one default connection negotiated) and flags legacy protocols that are still accepted. Note: forcing specific *weak cipher suites* (RC4/3DES/NULL/export-grade) isn't included — modern Node's bundled OpenSSL has those compiled out entirely, so we cannot reliably ask a server that question from this client; claiming to test for them would risk a false "not vulnerable" reading.
53
+
54
+ **Exposed path / admin / API discovery** — well-known admin & login pages across platforms (`wp-login.php`, Joomla `/administrator/`, Drupal `/user/login`, cPanel, phpMyAdmin, Adminer, Craft CMS, Umbraco, TYPO3); exposed config/backup files (`.env`, `.git/`, `wp-config.php.bak`, `.htpasswd`, SQL dumps, `phpinfo.php`, `server-status`); exposed APIs (`wp-json`, `wp-json/wp/v2/users`, `xmlrpc.php`, `/graphql`); directory-listing detection. When a `/graphql` endpoint is found, the tool sends a real (read-only, standard) introspection query — if introspection is enabled, the response hands over the entire schema, so exposed type/mutation names (especially sensitive-sounding ones like `deleteUser`) are reported directly.
55
+
56
+ **Third-party script/library inventory + CVE lookups** — detects common front-end libraries and versions (from filenames, query strings, and CDN path segments), WordPress core/plugin/theme versions (generator meta tag, `readme.html`, plugin `readme.txt` stable tags), Drupal core version (`CHANGELOG.txt`). Cross-references detected versions against:
57
+ - **[OSV.dev](https://osv.dev)** (no API key needed) for npm-ecosystem JS libraries (jQuery, Bootstrap, Lodash, Angular, React, Vue, etc.)
58
+ - **[WPScan Vulnerability API](https://wpscan.com/api)** (free API key required) for WordPress core, plugins, and themes — findings are filtered against the detected installed version where known
59
+
60
+ **WHOIS / domain registration** (RDAP) — registrar, creation/expiration/last-changed dates, nameservers, and domain status codes via [RDAP](https://rdap.org) (the modern JSON/HTTP successor to WHOIS — no shelling out to a system `whois` binary). Flags expired or soon-to-expire registrations, since a lapsed domain can be re-registered by an attacker.
61
+
62
+ **Subdomain enumeration** — passive discovery via [crt.sh](https://crt.sh) certificate-transparency logs (no active DNS brute force), with an optional liveness check (single HEAD request per host, capped by `--intensity`) to see which discovered subdomains currently respond.
63
+
64
+ **Port scan** — a lightweight Node-native TCP *connect* scan (`node:net`, no nmap dependency) against ~40 common service ports (FTP/SSH/Telnet/SMTP/databases — MySQL/Postgres/MongoDB/Redis/Elasticsearch/Memcached — RDP/VNC/message queues/etc). A connect scan just completes the ordinary TCP handshake and closes, identical to what any client does when it connects; an optional passive banner read (never sends bytes first) is captured when the service volunteers one. Flags any of ~18 "should not be internet-facing" ports (databases, remote admin protocols) found open.
65
+
66
+ **Recon-phase pentest checks** (all passive or single-request) —
67
+ - **Subdomain takeover**: DNS CNAME lookups on the target + common subdomains (`www`, `dev`, `staging`, `api`, `cdn`, etc.), fingerprinted against known dangling-resource response bodies for GitHub Pages, Heroku, AWS S3, Azure, Surge, Bitbucket Pages, Ghost, Pantheon, WP Engine, Fastly, Shopify, Zendesk, Webflow, Netlify
68
+ - **HTTP method exposure**: `OPTIONS` request inspects the `Allow` header for risky methods (PUT/DELETE/TRACE/CONNECT) — no risky method is ever actually sent
69
+ - **Open redirect**: canary-URL probing of common redirect parameters (`redirect`, `next`, `return`, `dest`, `continue`, etc.), `Location` header inspected but never followed
70
+ - **Missing Subresource Integrity (SRI)**: cross-origin `<script>`/`<link>` tags without an `integrity` attribute
71
+ - **Exposed source maps**: `.js.map` files that leak original (unminified) source
72
+ - **Email spoofing defense**: SPF presence + DMARC record/policy (`_dmarc.<domain>` TXT lookup)
73
+ - **WordPress user enumeration**: `?author=1..5` redirect-based username disclosure
74
+ - **Insecure login forms**: password fields on plain-HTTP pages or with an `http://` form action
75
+ - **Verbose error/debug disclosure**: a nonexistent-path probe checked against PHP/.NET/Java/Python stack-trace signatures
76
+ - **JWT cookie weaknesses**: cookies shaped like JWTs are decoded (never verified/forged) and flagged for `alg: none` or a missing `exp` claim
77
+
78
+ **PII exposure scan** — emails, phone numbers, SSN-like numbers, Luhn-valid credit-card-like numbers, and leaked secrets (AWS access keys, Stripe live secret/publishable keys, Google API keys, Slack tokens, GitHub tokens, PEM private key blocks).
79
+
80
+ **Payment/donation detection** — Stripe, PayPal, Square, Donorbox, Classy, Network for Good, GiveWP, WooCommerce, Shopify Checkout, Venmo, Braintree, Authorize.Net, Kindful, Bloomerang, Convio/Luminate, plus card-input-without-recognized-processor detection.
81
+
82
+ **CORS misconfiguration, mixed content, and AI/SEO-crawler exposure** — reflected-Origin + credentials CORS check; `http://` resources on HTTPS pages; what `robots.txt`/`llms.txt`/sitemap reveal publicly (including sensitive-looking `Disallow` paths, which are visible to attackers and AI scrapers even though they aren't blocked). Sitemap discovery reads any `Sitemap:` directive(s) in `robots.txt` first (taking priority over guessing `sitemap_index.xml`/`wp-sitemap.xml`/`sitemap.xml`), and cross-checks the two: it flags when a working sitemap exists but isn't declared in `robots.txt` (crawlers that only read `robots.txt` for sitemap discovery would miss it) and when `robots.txt` declares a sitemap URL that doesn't actually resolve.
83
+
84
+ ## Authenticated admin audit (opt-in, WordPress + Drupal)
85
+
86
+ If you supply credentials (see **Authentication** below) and the site is running WordPress or Drupal, the tool logs in and audits the admin dashboard itself — everything here is a normal screen the logged-in account already has access to; nothing is written or changed.
87
+
88
+ ### WordPress
89
+
90
+ Two ways to authenticate, checked in this order:
91
+
92
+ 1. **Application Passwords (preferred)** — `--wp-app-user`/`--wp-app-password`. Uses the official REST API (`/wp-json/wp/v2/*`) over HTTP Basic Auth — no Playwright login, no HTML scraping, stable across admin-theme/core UI changes. Create one under **Users → Profile → Application Passwords** in wp-admin. Plugin/theme "outdated" status is cross-referenced against the official [wordpress.org Plugin/Theme Directory API](https://api.wordpress.org) rather than scraped update-nag text.
93
+ 2. **Form login (fallback)** — `--login-url`/`--username`/`--password`. Logs into `wp-login.php` with Playwright and scrapes `wp-admin` screens directly (plugins, themes, Site Health, users). Used automatically when no Application Password is supplied.
94
+
95
+ Either way, it reports:
96
+ - **WordPress core, PHP, and MySQL/MariaDB versions** (form-login method only — Application Passwords don't expose these via REST) — flags end-of-life PHP versions
97
+ - **Every installed plugin/theme** — name, version, active/inactive status, and outdated status (including *inactive* ones, which public-facing scans can't see at all)
98
+ - **User accounts + roles** — flags more than 3 Administrator accounts, and a literal `admin` username (the first guess in any credential-stuffing attempt)
99
+ - **Security plugin presence** — checks active plugins against known security/WAF plugin slugs (Wordfence, Sucuri, iThemes/SolidWP Security, All In One WP Security, Shield Security, WP Cerber, MalCare, Defender, etc.) and flags if none are active
100
+
101
+ ### Drupal
102
+
103
+ Form login only (`--login-url`/`--username`/`--password` against `/user/login`), then:
104
+ - **Drupal core, PHP, and database versions** from `/admin/reports/status` (parsed from visible text rather than theme-specific CSS classes, since Drupal admin themes vary more than WordPress's) — flags end-of-life Drupal major versions and PHP versions, and counts error/warning-level status-report items
105
+ - **Installed modules** — name, version, enabled/disabled status, from `/admin/modules`
106
+ - **User accounts + roles** — from `/admin/people`, same Administrator-count and `admin`-username checks as WordPress
107
+
108
+ This is the one part of the tool that requires you to hand it real credentials — treat `--auth-config` files (and Application Passwords) as secrets (they're already gitignored) and prefer a dedicated low-privilege-but-sufficient account over your own login where possible.
109
+
110
+ ## Install
111
+
112
+ From npm (`@pbpyrojust/universal-security-audit`):
113
+
114
+ ```bash
115
+ npm install -g @pbpyrojust/universal-security-audit
116
+ npx playwright install chromium
117
+ universal-security-audit --site https://www.example.com
118
+ ```
119
+
120
+ Or without installing: `npm exec --package @pbpyrojust/universal-security-audit -- universal-security-audit --site https://www.example.com`.
121
+
122
+ From a clone of this repository:
123
+
124
+ ```bash
125
+ pnpm install
126
+ npx playwright install chromium
127
+ ```
128
+
129
+ ## Usage
130
+
131
+ ```bash
132
+ node scripts/run-security-audit.mjs --site https://www.example.com
133
+ # or
134
+ pnpm security-audit --site https://www.example.com
135
+ # or, via the CLI wrapper
136
+ node bin/universal-security-audit.mjs audit --site https://www.example.com
137
+ ```
138
+
139
+ ### Flags
140
+
141
+ | Flag | Description |
142
+ | --- | --- |
143
+ | `--site <url>` | **(required)** Target website URL |
144
+ | `--authorized` | Confirm you own the site or have the owner's written permission, so the active (pentest-style) checks run; recorded in the report. Without it, an interactive run asks, and a non-interactive one (CI, `--json`, batch) is passive only. Alias `--i-have-permission` |
145
+ | `--max-pages <n>` | Max pages to scan. Omit it and there's no limit — sitemap discovery pulls every URL it finds, and `--crawl` follows links until it exhausts the site; pass a number to cap either one on large sites |
146
+ | `--crawl` | Discover pages by following internal links instead of using the sitemap |
147
+ | `--max-per-url-pattern <n>` | Crawl-trap guard (default 3): caps how many pages matching the same URL "shape" get crawled — path segments that look like IDs/hashes/years (`/participants/mypage/1174017/2026`) are collapsed to a placeholder (`/participants/mypage/:id/:id`) for this comparison, so a site with thousands of per-record pages (fundraiser pages, product pages, forum threads) doesn't turn an uncapped `--crawl` into tens of thousands of near-duplicate requests |
148
+ | `--max-depth <n>` | How many link "hops" from the homepage `--crawl` follows: `0` = homepage only, `1` = homepage + whatever it links to, `2` = plus one more layer, etc. Omit for no limit. Combine with a small `--max-pages` for a quick "top-level pages only" pass |
149
+ | `--slow` | Conservative mode: longer waits between requests |
150
+ | `--respect-robots` | Skip URLs/paths disallowed by robots.txt |
151
+ | `--intensity <light\|normal\|aggressive>` | One dial for concurrency/delay/probe-list-size across path probing, port scanning, and subdomain liveness checks (default `normal`) |
152
+ | `--wordlist <path>` | Extra file/dir paths to probe (one per line, `#` comments allowed), merged with the built-in curated list |
153
+ | `--skip-exposed-paths` | Skip the admin/login/config/API path-probing phase |
154
+ | `--skip-cve` | Skip live OSV.dev/WPScan CVE lookups |
155
+ | `--skip-recon` | Skip the recon-phase checks (subdomain takeover, open redirect, SRI, source maps, SPF/DMARC, user enum, verbose errors) |
156
+ | `--skip-port-scan` | Skip the TCP port scan |
157
+ | `--skip-subdomain-enum` | Skip crt.sh subdomain enumeration |
158
+ | `--skip-whois` | Skip the WHOIS/RDAP domain-registration lookup |
159
+ | `--skip-admin-audit` | Skip the authenticated admin audit even if credentials are supplied |
160
+ | `--wordfence-key <key>` | Your own Wordfence Intelligence API key (or set `WORDFENCE_API_KEY`): free, commercial use allowed with attribution. WordPress sites only; see [WordPress CVE lookups](#wordpress-cve-lookups) |
161
+ | `--wpscan-key <key>` | Your own WPScan Vulnerability API token (or set `WPSCAN_API_KEY` env var) — free tier is 25 requests/day; see [WordPress CVE lookups](#wordpress-cve-lookups) |
162
+ | `--brand-config <path>` | JSON file to white-label the HTML/PDF report (see below) |
163
+ | `--proxy <url>` | Route all traffic (fetch + browser) through an upstream proxy, e.g. Burp Suite / OWASP ZAP, for manual inspection/replay |
164
+ | `--fail-on <severity>` | Exit code 1 if any *active* (non-suppressed) finding at or above this severity exists (`critical`/`high`/`medium`/`low`) — for CI gating |
165
+ | `--min-grade <letter>` | Exit code 1 if the overall risk grade is below this letter (e.g. `--min-grade B`) — for CI gating |
166
+ | `--baseline <path>` | JSON file of known-accepted findings (by fingerprint) to suppress from scoring/CI gating while keeping them visible in the report — see **Baseline / exceptions** below |
167
+ | `--json` | Print the full findings/report as JSON to stdout (all progress output moves to stderr, so stdout stays clean for piping to `jq` etc.) — `full-report.json` is always written to the run directory regardless of this flag |
168
+
169
+ ### Authentication
170
+
171
+ For the authenticated admin audit (and for scanning staging/password-protected sites in general):
172
+
173
+ | Flag | Description |
174
+ | --- | --- |
175
+ | `--auth-config <path>` | JSON file with any of the fields below (see `auth-config.example.json`) |
176
+ | `--http-username` / `--http-password` | HTTP Basic Auth credentials (or `USA_HTTP_USERNAME`/`USA_HTTP_PASSWORD` env vars) |
177
+ | `--wp-app-user` / `--wp-app-password` | WordPress Application Password (or `USA_WP_APP_USER`/`USA_WP_APP_PASSWORD` env vars) — **preferred** for the WP admin audit; no Playwright login needed |
178
+ | `--login-url <url>` | Form-based login page (e.g. `https://example.com/wp-login.php` or `/user/login` for Drupal) — supplying this + `--username` enables the authenticated admin audit when no Application Password is given |
179
+ | `--username` / `--password` | Form login credentials (or `USA_LOGIN_USERNAME`/`USA_LOGIN_PASSWORD` env vars) |
180
+ | `--username-selector` / `--password-selector` / `--submit-selector` | CSS selectors for the login form fields (default to WordPress's `wp-login.php` field names) |
181
+ | `--ready-selector` | A selector to wait for post-login as a "login succeeded" signal |
182
+ | `--post-login-wait-ms` | Fallback wait time after submitting the login form (default 2000ms) |
183
+
184
+ ```bash
185
+ # WordPress via Application Password (preferred):
186
+ node scripts/run-security-audit.mjs --site https://www.example.com \
187
+ --wp-app-user your-admin-user --wp-app-password "xxxx xxxx xxxx xxxx xxxx xxxx"
188
+
189
+ # WordPress or Drupal via form login (fallback):
190
+ node scripts/run-security-audit.mjs --site https://www.example.com \
191
+ --login-url https://www.example.com/wp-login.php \
192
+ --username your-admin-user --password your-admin-password
193
+ ```
194
+
195
+ ### WordPress CVE lookups
196
+
197
+ WordPress core/plugin/theme vulnerability matching only runs for sites detected as WordPress, and only with a key you supply. Two sources, use either or both:
198
+
199
+ - **[Wordfence Intelligence](https://www.wordfence.com/threat-intel/)** (`WORDFENCE_API_KEY` or `--wordfence-key`): free for commercial and personal use with attribution; create a key at wordfence.com under Account → Integrations. The full feed (~160 MB) is downloaded at most once every 24 hours and kept as a compact index (~18 MB) in `~/.cache/universal-security-audit/wordfence-index.json` (or `$XDG_CACHE_HOME`); the key's download limit is small, so if a refresh is refused the cached copy is used. Findings credit Wordfence and Defiant Inc. as its license requires.
200
+ - **WPScan**: uses the [WPScan Vulnerability Database API](https://wpscan.com/api), which requires an API key — there's no shared/bundled key in this repo, so each user registers their own at wpscan.com. WPScan's free key is for non-commercial use; for client or commercial work use Wordfence or a paid WPScan plan. Set it either way:
201
+
202
+ ```bash
203
+ export WPSCAN_API_KEY=your-key-here
204
+ node scripts/run-security-audit.mjs --site https://www.example.com
205
+ ```
206
+
207
+ or pass it per-run with `--wpscan-key your-key-here` (this takes precedence over the env var). Without a key, core/plugin/theme versions are still detected and reported — just without live CVE matching.
208
+
209
+ The free WPScan tier is capped at **25 requests/day**. If a scan hits that cap mid-run (checking WordPress core plus every detected plugin/theme can use up the quota quickly), the tool detects the `429` response, prints a one-time warning to the console telling you the daily limit was hit, and skips the remaining WPScan lookups for that run rather than continuing to burn failed requests — everything else in the scan (headers, exposed paths, PII/payment detection, non-WPScan CVE sources, etc.) still completes normally. The quota resets daily; see [wpscan.com/api](https://wpscan.com/api) for paid tiers with a higher limit.
210
+
211
+ ## Output
212
+
213
+ Each run writes to `reports/<site>-<timestamp>/`:
214
+
215
+ | File | Contents |
216
+ | --- | --- |
217
+ | `security-dashboard.html` / `.pdf` | Visual risk-graded report |
218
+ | `findings-summary.csv` | All findings, sorted by severity — includes `suppressed`/`suppressedReason` columns when `--baseline` is used |
219
+ | `exposed-paths.csv` | Every probed admin/config/API path and its result |
220
+ | `script-inventory.csv` | Detected front-end libraries + versions |
221
+ | `wordpress-components.csv` | Detected WP plugins/themes + versions, from public-facing detection (WordPress sites only) |
222
+ | `vulnerabilities.csv` | Matched CVEs/advisories from OSV.dev + WPScan |
223
+ | `port-scan.csv` | Every scanned port, its state (open/closed/filtered), and any banner captured |
224
+ | `subdomains.csv` | Subdomains discovered via certificate-transparency logs (when any are found) |
225
+ | `wp-admin-plugins.csv` / `wp-admin-themes.csv` / `wp-admin-users.csv` | Authenticated WordPress admin-audit data (only written when credentials were supplied and the audit succeeded) |
226
+ | `drupal-admin-modules.csv` / `drupal-admin-users.csv` | Authenticated Drupal admin-audit data |
227
+ | `summary.json` | Machine-readable summary: risk grade, platform, hosting, DNS, TLS, WHOIS, subdomains, open ports, admin-audit data |
228
+ | `full-report.json` | Everything in `summary.json` plus every finding, all matched CVEs, all exposed-path results, and the full library inventory — the same document `--json` prints to stdout |
229
+
230
+ ## Reports, tickets, and comparisons
231
+
232
+ Same workflow shape as `universal-seo-audit` / `universal-accessibility-audit`: the live audit produces raw data, and three standalone scripts turn it into deliverables — usable independently of a live scan, against any existing `reports/<run>` directory.
233
+
234
+ ### Regenerate / re-brand the dashboard
235
+
236
+ ```bash
237
+ node scripts/generate-security-report.mjs --run-dir reports/<run-folder> --brand-config ./branding.json
238
+ ```
239
+
240
+ Rebuilds `security-dashboard.html`/`.pdf` from a run's `summary.json` + CSVs without re-scanning the site. Useful for white-labeling a past run or after a template change.
241
+
242
+ `branding.json` (see `branding.example.json`):
243
+
244
+ ```json
245
+ {
246
+ "companyName": "JustWhat.net",
247
+ "logo": "./assets/logo.png",
248
+ "primaryColor": "#ef4444",
249
+ "secondaryColor": "#0f172a",
250
+ "accentColor": "#22c55e",
251
+ "reportTitle": "Security & Attack Surface Audit",
252
+ "author": "Justin Adams",
253
+ "footerText": "Confidential — Prepared by JustWhat.net"
254
+ }
255
+ ```
256
+
257
+ ### Ticket-ready backlog
258
+
259
+ ```bash
260
+ node scripts/generate-security-tickets.mjs --run-dir reports/<run-folder>
261
+ ```
262
+
263
+ Writes `security-ticket-backlog.csv`: one ticket per site-wide issue (headers, TLS, CVEs, exposed paths, recon findings — deduped across pages) plus one ticket per page-level finding (PII, mixed content, missing SRI, insecure login forms, JWT issues), each with a priority (`P0`–`P4` from severity), description, and label set — ready to import into GitHub Issues, Jira, Linear, etc.
264
+
265
+ ### Compare two runs over time
266
+
267
+ ```bash
268
+ node scripts/compare-security-runs.mjs --before reports/run-a --after reports/run-b
269
+ ```
270
+
271
+ Writes `security-compare-summary.csv` (severity counts before/after/delta), `security-compare-new-findings.csv`, `security-compare-resolved-findings.csv`, and a human-readable `security-compare-summary.md` — including the risk-grade delta. Add `--out-dir <path>` to write these somewhere other than the "after" run's own directory (the default). Use this after remediation work to confirm fixes landed and nothing regressed.
272
+
273
+ ### SARIF export (GitHub code scanning)
274
+
275
+ ```bash
276
+ node scripts/generate-security-sarif.mjs --run-dir reports/<run-folder> --out results.sarif
277
+ ```
278
+
279
+ Converts `findings-summary.csv` into a valid SARIF 2.1.0 document — severity maps to SARIF `level` (critical/high → `error`, medium → `warning`, low/info → `note`) plus a `security-severity` score GitHub uses for its Security tab display. Locations use each finding's URL as the artifact URI (the same convention OWASP ZAP's SARIF exporter uses for web findings that aren't tied to a repo file). Wire it into a workflow with `github/codeql-action/upload-sarif`.
280
+
281
+ ### Baseline / exceptions
282
+
283
+ Known-accepted findings can be suppressed from risk scoring and `--fail-on`/`--min-grade` CI gating on future runs, without hiding them from the report:
284
+
285
+ ```bash
286
+ # Snapshot everything from the current run as accepted (edit the output afterward to add real reasons/expiry):
287
+ node scripts/generate-baseline.mjs --run-dir reports/<run-folder> --out ./security-baseline.json --reason "Accepted 2026-Q3, ticket JIRA-123"
288
+
289
+ # Use it on future runs:
290
+ node scripts/run-security-audit.mjs --site https://www.example.com --baseline ./security-baseline.json
291
+ ```
292
+
293
+ Suppression is keyed on a stable fingerprint of `category + title + url`, so a finding stays suppressed as long as it's the same issue at the same location. Suppressed findings still appear in `findings-summary.csv` and the dashboard (dimmed, with the acceptance reason), they just don't count toward the risk grade or trip `--fail-on`/`--min-grade`. Add an `"expires"` date (ISO format) to any baseline entry to make the exception temporary — after that date it stops applying automatically. `generate-baseline.mjs` flags: `--out <path>` (default `./security-baseline.json`), `--reason <text>`, `--expires <ISO date>`, `--overwrite` (replace the file instead of merging new exceptions into it).
294
+
295
+ ### Multi-site batch scanning
296
+
297
+ ```bash
298
+ node scripts/run-batch-audit.mjs --sites-file ./sites.txt --intensity light --skip-cve --concurrency 2 --out-dir ./reports
299
+ ```
300
+
301
+ `sites.txt` is one URL per line (`#` comments allowed). Runs the full audit against each site (`--concurrency N`, default 1/sequential — keep this low, each run launches its own browser), forwarding every other flag through to each individual audit. Every site still gets its normal `reports/<site>-<timestamp>/` output, plus an aggregated `batch-summary-<id>.csv`/`.md` (`--out-dir` controls where, default `./reports`) with one row per site (grade, score, severity counts) sorted worst-first — useful for agencies auditing a portfolio of client sites in one pass.
302
+
303
+ ### CLI wrapper
304
+
305
+ All of the above are also reachable through the packaged CLI:
306
+
307
+ ```bash
308
+ node bin/universal-security-audit.mjs audit --site https://www.example.com
309
+ node bin/universal-security-audit.mjs report --run-dir reports/<run-folder> --brand-config ./branding.json
310
+ node bin/universal-security-audit.mjs tickets --run-dir reports/<run-folder>
311
+ node bin/universal-security-audit.mjs compare --before reports/run-a --after reports/run-b
312
+ node bin/universal-security-audit.mjs sarif --run-dir reports/<run-folder> --out results.sarif
313
+ node bin/universal-security-audit.mjs baseline --run-dir reports/<run-folder> --out ./security-baseline.json
314
+ node bin/universal-security-audit.mjs batch-audit --sites-file ./sites.txt
315
+ node bin/universal-security-audit.mjs help
316
+ ```
317
+
318
+ ## Example workflows by site type / use case
319
+
320
+ ### Small stakeholder-facing overview (skip deep/individual-record pages)
321
+
322
+ For sites with lots of per-record pages (fundraiser/participant pages, product listings, forum threads, etc.) where you just want a quick, presentable snapshot rather than exhaustive coverage:
323
+
324
+ ```bash
325
+ node bin/universal-security-audit.mjs audit --site https://www.example.com \
326
+ --crawl --max-depth 2 --max-pages 25 --intensity light --respect-robots
327
+ ```
328
+
329
+ `--max-depth 2` keeps the crawl to the homepage, its direct nav links, and one layer of their children — the pages that actually represent the site's structure — without falling into individual per-record pages several links deep. `--max-pages 25` is a backstop in case even that top layer is large. Good enough to hand stakeholders a representative grade and findings list without a multi-hour run.
330
+
331
+ ### Quick first-pass on a live WordPress site
332
+
333
+ Fast and unauthenticated, but the active checks still need the client's written permission first (`--authorized` records that you have it):
334
+
335
+ ```bash
336
+ node bin/universal-security-audit.mjs audit --site https://www.example.com --intensity light --authorized
337
+ ```
338
+
339
+ ### Full authenticated WordPress security review
340
+
341
+ Agency-style deep audit: create an Application Password in the client's wp-admin first (Users → Profile → Application Passwords), get a free [WPScan API key](https://wpscan.com/api), then:
342
+
343
+ ```bash
344
+ node bin/universal-security-audit.mjs audit --site https://www.example.com \
345
+ --wp-app-user admin --wp-app-password "xxxx xxxx xxxx xxxx xxxx xxxx" \
346
+ --wpscan-key YOUR_WPSCAN_KEY --crawl --max-pages 50 --authorized
347
+ node bin/universal-security-audit.mjs tickets --run-dir ./reports/<run-id>
348
+ ```
349
+
350
+ Gives you plugin/theme CVEs, outdated inactive plugins nobody remembers installing, missing security plugins, and a ticket-ready backlog in one pass.
351
+
352
+ ### Drupal site
353
+
354
+ Same shape, form-login only (no Application Passwords in Drupal core):
355
+
356
+ ```bash
357
+ node bin/universal-security-audit.mjs audit --site https://www.example.com \
358
+ --login-url https://www.example.com/user/login --username admin --password your-password --authorized
359
+ ```
360
+
361
+ ### E-commerce / donation site
362
+
363
+ Payment-processor and PCI-scope questions matter most here; a full run already covers this, but it's worth calling out what to look at in the output — the `payment` category in `findings-summary.csv` and the "Payment/donation" card on the dashboard:
364
+
365
+ ```bash
366
+ node bin/universal-security-audit.mjs audit --site https://www.example.com
367
+ ```
368
+
369
+ Look for: a card-input form without a recognized tokenizing processor (Stripe/Square/Braintree/Authorize.Net) flagged `high`, missing HSTS/CSP on checkout pages, and mixed-content resources on any page that touches payment.
370
+
371
+ ### Staging / password-protected site (HTTP Basic Auth)
372
+
373
+ ```bash
374
+ node bin/universal-security-audit.mjs audit --site https://staging.example.com \
375
+ --http-username staging-user --http-password staging-pass --respect-robots
376
+ ```
377
+
378
+ ### CI/CD gate on every deploy (GitHub Actions)
379
+
380
+ ```bash
381
+ node bin/universal-security-audit.mjs audit --site https://www.example.com \
382
+ --intensity light --skip-port-scan --skip-subdomain-enum --authorized \
383
+ --baseline ./security-baseline.json --fail-on high --json > report.json
384
+ node bin/universal-security-audit.mjs sarif --run-dir "$(ls -td reports/*/ | head -1)" --out results.sarif
385
+ ```
386
+
387
+ Then upload `results.sarif` with `github/codeql-action/upload-sarif` so findings show up in the repo's Security tab. `--fail-on high` fails the build on anything high/critical that isn't already in the baseline; port scan/subdomain enum are skipped here since they're slower and less relevant to "did this deploy regress anything."
388
+
389
+ ### Agency portfolio — many client sites on a schedule
390
+
391
+ ```bash
392
+ node bin/universal-security-audit.mjs batch-audit --sites-file ./client-sites.txt \
393
+ --intensity light --skip-cve --concurrency 2 --authorized
394
+ ```
395
+
396
+ Pair with a cron/scheduled job and diff each site's `summary.json` week over week (or use `compare` against the prior run per site) to catch regressions before a client notices.
397
+
398
+ ### Custom app / SPA / non-CMS site (Next.js, headless, etc.)
399
+
400
+ Platform fingerprinting won't find a CMS to fingerprint here, so CVE/plugin-version checks won't have much to do — the value comes from headers, TLS, CORS, exposed paths, port scan, and PII/secret scanning instead:
401
+
402
+ ```bash
403
+ node bin/universal-security-audit.mjs audit --site https://app.example.com --skip-cve
404
+ ```
405
+
406
+ ### Confirm remediation actually worked
407
+
408
+ ```bash
409
+ node bin/universal-security-audit.mjs audit --site https://www.example.com --intensity light # after fixes
410
+ node bin/universal-security-audit.mjs compare --before ./reports/<before-run> --after ./reports/<after-run>
411
+ ```
412
+
413
+ Check `security-compare-summary.md` for the risk-grade delta and confirm the specific findings you fixed show up under "Resolved findings," not "New findings" (which would mean something regressed elsewhere).
414
+
415
+ ### One-time deep pentest-style engagement
416
+
417
+ Maximum coverage, routed through Burp Suite so a human can inspect/replay anything interesting:
418
+
419
+ ```bash
420
+ node bin/universal-security-audit.mjs audit --site https://www.example.com \
421
+ --intensity aggressive --crawl --max-pages 100 \
422
+ --wordlist ./my-wordlist.txt --proxy http://127.0.0.1:8080 \
423
+ --wp-app-user admin --wp-app-password "xxxx xxxx xxxx xxxx xxxx xxxx" --wpscan-key YOUR_KEY --authorized
424
+ ```
425
+
426
+ ## Risk grading
427
+
428
+ Every finding is tagged `critical` / `high` / `medium` / `low` / `info`. The overall grade (A+–F) is a weighted deduction from 100 based on finding severity (critical −25, high −12, medium −5, low −2, info −0) — the header-grading module uses the same deduction methodology on its own 0–100 scale, loosely modeled on securityheaders.com / Mozilla Observatory.
429
+
430
+ ## Roadmap / not built yet
431
+
432
+ - `--crawl` is single-pass BFS link-following; no per-domain crawl-delay tuning yet
433
+ - No Drupal-specific CVE feed (SA-CORE) — Drupal core version is detected and reported, but not cross-referenced against known CVEs yet
434
+ - No favicon-hash-based CMS fingerprint boost
435
+ - Port scan is TCP-connect only (no UDP, no service-version fingerprinting beyond a passive banner read); weak-cipher-suite forcing isn't included (see the TLS section above for why)
436
+ - No login rate-limit/lockout detection or password-reset account enumeration — both are real pentest signals, but risk locking out real accounts or sending real emails, so they'd need explicit opt-in and heavy guardrails if ever added
@@ -0,0 +1,12 @@
1
+ {
2
+ "loginUrl": "https://www.example.com/wp-login.php",
3
+ "username": "your-admin-user",
4
+ "password": "your-admin-password",
5
+ "usernameSelector": "input[name='log']",
6
+ "passwordSelector": "input[name='pwd']",
7
+ "submitSelector": "#wp-submit",
8
+ "readySelector": "#wpadminbar",
9
+ "postLoginWaitMs": 2000,
10
+ "httpUsername": "",
11
+ "httpPassword": ""
12
+ }
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from 'node:child_process';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = path.dirname(__filename);
8
+ const root = path.resolve(__dirname, '..');
9
+
10
+ function run(script, args) {
11
+ const scriptPath = path.join(root, 'scripts', script);
12
+ const result = spawnSync(process.execPath, [scriptPath, ...args], { stdio: 'inherit', env: process.env });
13
+ process.exit(result.status ?? 1);
14
+ }
15
+
16
+ function printHelp() {
17
+ console.log(`universal-security-audit
18
+
19
+ Usage:
20
+ universal-security-audit <command> [options]
21
+
22
+ Commands:
23
+ audit Full security & attack-surface audit (headers, TLS protocol enumeration,
24
+ fingerprinting, exposed paths, live GraphQL introspection, port scan, subdomain
25
+ enum, WHOIS, CVE lookups, PII/payment detection, recon checks, optional
26
+ authenticated WordPress/Drupal admin audit) + HTML/PDF dashboard
27
+ report Regenerate the HTML/PDF dashboard from an existing run (e.g. after rebranding)
28
+ tickets Generate a ticket-ready backlog CSV from a run's findings
29
+ compare Diff two runs (new/resolved findings, risk grade delta)
30
+ sarif Convert a run's findings into SARIF for GitHub code scanning
31
+ baseline Snapshot a run's current findings into a baseline/exceptions file
32
+ batch-audit Run the full audit across a list of sites, aggregating a summary table
33
+ help Show this help
34
+ version Show package version
35
+
36
+ Examples:
37
+ universal-security-audit audit --site https://www.example.com
38
+ universal-security-audit audit --site https://www.example.com --crawl --max-pages 25
39
+ universal-security-audit audit --site https://www.example.com --authorized --intensity aggressive
40
+ universal-security-audit audit --site https://www.example.com --wordfence-key YOUR_KEY
41
+ universal-security-audit audit --site https://www.example.com --wpscan-key YOUR_KEY
42
+ universal-security-audit audit --site https://www.example.com --skip-exposed-paths --skip-recon
43
+ universal-security-audit audit --site https://www.example.com --login-url https://www.example.com/wp-login.php --username U --password P
44
+ universal-security-audit audit --site https://www.example.com --wp-app-user U --wp-app-password 'xxxx xxxx xxxx xxxx'
45
+ universal-security-audit audit --site https://www.example.com --proxy http://127.0.0.1:8080
46
+ universal-security-audit audit --site https://www.example.com --baseline ./security-baseline.json
47
+ universal-security-audit audit --site https://www.example.com --fail-on high --json > report.json
48
+ universal-security-audit report --run-dir ./reports/<run-id> --brand-config ./branding.json
49
+ universal-security-audit tickets --run-dir ./reports/<run-id>
50
+ universal-security-audit compare --before ./reports/<run-a> --after ./reports/<run-b>
51
+ universal-security-audit sarif --run-dir ./reports/<run-id> --out results.sarif
52
+ universal-security-audit baseline --run-dir ./reports/<run-id> --out ./security-baseline.json
53
+ universal-security-audit batch-audit --sites-file ./sites.txt --intensity light
54
+
55
+ Pentest-style (active) checks need the site owner's written permission: pass --authorized,
56
+ or answer "yes" when asked. Without it a run is passive only.
57
+
58
+ Key flags (see README for the full list):
59
+ --authorized Confirm written permission; runs the active checks
60
+ --intensity light|normal|aggressive Concurrency/delay/probe-size dial
61
+ --max-pages <n> Cap pages scanned (default: no limit)
62
+ --max-depth <n> Link-hops from homepage to follow during --crawl
63
+ --max-per-url-pattern <n> Crawl-trap guard, default 3 (per-record pages, etc.)
64
+ --wordlist <path> Extra paths to probe, one per line
65
+ --skip-port-scan / --skip-subdomain-enum / --skip-whois / --skip-admin-audit
66
+ --auth-config <path> | --login-url/--username/--password Enables authenticated admin audit
67
+ --wp-app-user / --wp-app-password WordPress Application Password (preferred over form login)
68
+ --proxy <url> Route traffic through Burp/ZAP for manual inspection
69
+ --fail-on <severity> / --min-grade <letter> CI/CD exit-code gating
70
+ --baseline <path> Suppress known-accepted findings from scoring/CI gating
71
+ --json Clean JSON on stdout (progress moves to stderr)
72
+
73
+ Primary outputs (in reports/<site>-<timestamp>/):
74
+ findings-summary.csv All findings, sorted by severity (includes suppressed column)
75
+ exposed-paths.csv Every probed admin/config/API path + result
76
+ script-inventory.csv Detected front-end libraries + versions
77
+ wordpress-components.csv Detected WP plugins/themes (WordPress sites only)
78
+ vulnerabilities.csv Matched CVEs/advisories (OSV.dev + WPScan)
79
+ port-scan.csv Every scanned port + state + banner
80
+ subdomains.csv Subdomains found via certificate-transparency logs
81
+ wp-admin-*.csv / drupal-admin-*.csv Authenticated admin-audit data
82
+ summary.json / full-report.json Machine-readable run summary / full data dump
83
+ security-dashboard.html/.pdf Visual risk-graded report
84
+ security-ticket-backlog.csv Ticket-ready findings (via 'tickets' command)
85
+
86
+ ⚠️ Authorized-use only: this tool actively probes admin/config paths, ports, and
87
+ subdomains on the target. Only run it against sites/infrastructure you own or
88
+ have explicit written permission to test.
89
+
90
+ If no command is provided, the CLI defaults to 'audit'.
91
+ `);
92
+ }
93
+
94
+ const argv = process.argv.slice(2);
95
+ const command = argv[0] && !argv[0].startsWith('--') ? argv[0] : 'audit';
96
+ const rest = command === 'audit' ? (argv[0] === 'audit' ? argv.slice(1) : argv) : argv.slice(1);
97
+
98
+ switch (command) {
99
+ case 'audit':
100
+ run('run-security-audit.mjs', rest);
101
+ break;
102
+ case 'report':
103
+ run('generate-security-report.mjs', rest);
104
+ break;
105
+ case 'tickets':
106
+ run('generate-security-tickets.mjs', rest);
107
+ break;
108
+ case 'compare':
109
+ run('compare-security-runs.mjs', rest);
110
+ break;
111
+ case 'sarif':
112
+ run('generate-security-sarif.mjs', rest);
113
+ break;
114
+ case 'baseline':
115
+ run('generate-baseline.mjs', rest);
116
+ break;
117
+ case 'batch-audit':
118
+ run('run-batch-audit.mjs', rest);
119
+ break;
120
+ case 'help':
121
+ case '--help':
122
+ case '-h':
123
+ printHelp();
124
+ break;
125
+ case 'version':
126
+ case '--version':
127
+ case '-v':
128
+ console.log('0.1.0');
129
+ break;
130
+ default:
131
+ console.error(`Unknown command: ${command}`);
132
+ printHelp();
133
+ process.exit(1);
134
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "companyName": "JustWhat.net",
3
+ "logo": "./assets/logo.png",
4
+ "primaryColor": "#ef4444",
5
+ "secondaryColor": "#0f172a",
6
+ "accentColor": "#22c55e",
7
+ "reportTitle": "Security & Attack Surface Audit",
8
+ "author": "Justin Adams",
9
+ "footerText": "Confidential — Prepared by JustWhat.net"
10
+ }