plexavo 0.3.0__tar.gz → 0.4.0__tar.gz
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.
- plexavo-0.4.0/PKG-INFO +256 -0
- plexavo-0.4.0/README.md +227 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/__init__.py +1 -1
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/cli.py +37 -1
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/interactive.py +1 -0
- plexavo-0.4.0/plexavo/report/json_report.py +104 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/templates/report.html.j2 +6 -3
- plexavo-0.4.0/plexavo.egg-info/PKG-INFO +256 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo.egg-info/SOURCES.txt +2 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/pyproject.toml +1 -1
- plexavo-0.4.0/tests/test_json_report_offline.py +152 -0
- plexavo-0.3.0/PKG-INFO +0 -194
- plexavo-0.3.0/README.md +0 -165
- plexavo-0.3.0/plexavo.egg-info/PKG-INFO +0 -194
- {plexavo-0.3.0 → plexavo-0.4.0}/LICENSE +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/__main__.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/attack_paths.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/auth.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/aws_profile_setup.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/__init__.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/encryption.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/iam.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/iam_hygiene.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/logging.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/network.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/storage.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/checks/usage.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/findings.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/principals.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/__init__.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/ai_narration.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/html_report.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/report/pdf.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo/scoring.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo.egg-info/dependency_links.txt +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo.egg-info/entry_points.txt +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo.egg-info/requires.txt +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/plexavo.egg-info/top_level.txt +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/setup.cfg +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_ai_narration_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_attack_paths_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_auth_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_cli_entrypoint.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_encryption_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_fail_on_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_iam_hygiene_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_iam_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_logging_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_network_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_report_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_scoring.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_storage_offline.py +0 -0
- {plexavo-0.3.0 → plexavo-0.4.0}/tests/test_usage_offline.py +0 -0
plexavo-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: plexavo
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
|
|
5
|
+
Author: Kavee
|
|
6
|
+
License: AGPL-3.0-or-later
|
|
7
|
+
Project-URL: Homepage, https://github.com/plexavo/Plexavo
|
|
8
|
+
Project-URL: Issues, https://github.com/plexavo/Plexavo/issues
|
|
9
|
+
Project-URL: Security, https://github.com/plexavo/Plexavo/blob/main/SECURITY.md
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
|
|
12
|
+
Classifier: Topic :: Security
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: boto3>=1.34
|
|
18
|
+
Requires-Dist: python-dateutil>=2.9
|
|
19
|
+
Requires-Dist: rich>=13.7
|
|
20
|
+
Requires-Dist: jinja2>=3.1
|
|
21
|
+
Requires-Dist: markupsafe>=2.1
|
|
22
|
+
Requires-Dist: fpdf2>=2.8
|
|
23
|
+
Provides-Extra: ai
|
|
24
|
+
Requires-Dist: anthropic>=0.116; extra == "ai"
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pypdf>=4.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
<div align="center">
|
|
31
|
+
|
|
32
|
+
<picture>
|
|
33
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/plexavo-logo-dark.png">
|
|
34
|
+
<img src="assets/plexavo-logo-light.png" alt="Plexavo" height="105">
|
|
35
|
+
</picture>
|
|
36
|
+
|
|
37
|
+
<p>
|
|
38
|
+
<a href="https://github.com/plexavo/plexavo">
|
|
39
|
+
<img src="https://img.shields.io/github/stars/plexavo/plexavo?style=for-the-badge&logo=github&label=STARS" alt="GitHub stars">
|
|
40
|
+
</a>
|
|
41
|
+
<a href="https://github.com/plexavo/plexavo/blob/main/LICENSE">
|
|
42
|
+
<img src="https://img.shields.io/github/license/plexavo/plexavo?style=for-the-badge&label=LICENSE" alt="License">
|
|
43
|
+
</a>
|
|
44
|
+
<a href="https://pypi.org/project/plexavo/">
|
|
45
|
+
<img src="https://img.shields.io/pypi/v/plexavo?style=for-the-badge&logo=pypi&logoColor=white&label=PYPI" alt="PyPI version">
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://github.com/plexavo/plexavo/actions">
|
|
48
|
+
<img src="https://img.shields.io/github/actions/workflow/status/plexavo/plexavo/ci.yml?style=for-the-badge&label=BUILD" alt="Build status">
|
|
49
|
+
</a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<p><strong>See your cloud. Secure what matters.</strong></p>
|
|
53
|
+
<p>Turn AWS configuration data into clear, actionable security intelligence.</p>
|
|
54
|
+
|
|
55
|
+
</div>
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
<div align="center">
|
|
60
|
+
<img src="assets/demo.gif" alt="Plexavo interactive scan demo" width="800">
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
> [!NOTE]
|
|
64
|
+
> **Plexavo is built around a simple idea:** security tooling should tell you what is wrong, why it matters, and what to do next, without requiring a dedicated security team.
|
|
65
|
+
|
|
66
|
+
Plexavo is an open-source cloud security tool that audits AWS accounts
|
|
67
|
+
for real-world misconfigurations, using your own local AWS credentials
|
|
68
|
+
the same way `aws s3 ls` does, so nothing about your account ever
|
|
69
|
+
leaves your machine.
|
|
70
|
+
|
|
71
|
+
Each scan produces a **0–100 security score** and a plain-English report:
|
|
72
|
+
what's wrong, what an attacker would actually do with it, and the exact
|
|
73
|
+
command to fix it.
|
|
74
|
+
|
|
75
|
+
> [!IMPORTANT]
|
|
76
|
+
> **Detection is not AI.** Plexavo's detections are pure Python/boto3;
|
|
77
|
+
> Claude only rewrites already-found findings for readability, and
|
|
78
|
+
> narration is fully optional. See [Cost](#cost).
|
|
79
|
+
|
|
80
|
+
> [!TIP]
|
|
81
|
+
> 🤖 **New: talk to Plexavo through Claude Code.** No CLI flags, just ask
|
|
82
|
+
> "scan my AWS account with Plexavo" in a Claude Code session and get a
|
|
83
|
+
> conversational security briefing. See
|
|
84
|
+
> [Using it from Claude Code](#using-it-from-claude-code).
|
|
85
|
+
|
|
86
|
+
## ✦ What it checks
|
|
87
|
+
|
|
88
|
+
**32 checks across 6 categories**, run against real AWS accounts:
|
|
89
|
+
|
|
90
|
+
| Category | What Plexavo looks for |
|
|
91
|
+
|---|---|
|
|
92
|
+
| **IAM** | Privilege escalation paths, wildcard admin, cross-account trust, root usage, dormant credentials |
|
|
93
|
+
| **Network** | Security groups and RDS instances exposed to the internet |
|
|
94
|
+
| **Storage** | Public S3 buckets via ACLs, bucket policies, or missing Block Public Access; buckets with no access logging |
|
|
95
|
+
| **Encryption** | Unencrypted EBS volumes, RDS instances, S3 buckets |
|
|
96
|
+
| **Logging** | CloudTrail coverage and encryption, GuardDuty status |
|
|
97
|
+
| **Usage** | Permissions granted but never used, roles nobody has assumed in 90+ days |
|
|
98
|
+
|
|
99
|
+
See `docs/*-TEST-MATRIX.md` for how each check was verified.
|
|
100
|
+
|
|
101
|
+
> [!TIP]
|
|
102
|
+
> **Don't just trust the number.** Plexavo keeps severity, confidence, and evidence as separate signals, so a low-confidence Critical does not read the same as a high-confidence Medium.
|
|
103
|
+
|
|
104
|
+
## ⚡ Installation
|
|
105
|
+
|
|
106
|
+
Every path below installs Plexavo into its own isolated environment.
|
|
107
|
+
|
|
108
|
+
### macOS & Linux
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # skip if you have uv
|
|
112
|
+
uv tool install plexavo
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Prefer [pipx](https://pipx.pypa.io/)? `pipx install plexavo` works the
|
|
116
|
+
same way.
|
|
117
|
+
|
|
118
|
+
### Windows
|
|
119
|
+
|
|
120
|
+
`uv`/`pipx` still work, but the PATH launcher is unsigned and Windows
|
|
121
|
+
Smart App Control blocks it. Run Plexavo through Python instead:
|
|
122
|
+
|
|
123
|
+
**Option 1, uv (recommended)**
|
|
124
|
+
|
|
125
|
+
```powershell
|
|
126
|
+
uv tool install plexavo
|
|
127
|
+
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
|
|
128
|
+
if (!(Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
|
|
129
|
+
Add-Content $PROFILE 'function plexavo { & "$env:APPDATA\uv\tools\plexavo\Scripts\python.exe" -m plexavo @args }'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Open a new terminal. `plexavo` now works like it does on macOS/Linux,
|
|
133
|
+
routed through uv's signed Python instead of the blocked launcher.
|
|
134
|
+
|
|
135
|
+
**Option 2, plain venv**
|
|
136
|
+
|
|
137
|
+
```powershell
|
|
138
|
+
py -m venv plexavo-venv
|
|
139
|
+
.\plexavo-venv\Scripts\Activate.ps1
|
|
140
|
+
python -m pip install plexavo
|
|
141
|
+
python -m plexavo
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Use `python -m` for everything here too. The venv's own `pip.exe` and
|
|
145
|
+
`plexavo.exe` are unsigned as well, only `python.exe` is signed.
|
|
146
|
+
|
|
147
|
+
### AI narration (optional)
|
|
148
|
+
|
|
149
|
+
Want each finding rewritten as a full narrative? Install `"plexavo[ai]"`
|
|
150
|
+
instead of `plexavo`, and set `ANTHROPIC_API_KEY`. See [Cost](#cost).
|
|
151
|
+
|
|
152
|
+
## 🚀 Using Plexavo
|
|
153
|
+
|
|
154
|
+
Run it with no arguments and it walks you through everything: picking an
|
|
155
|
+
AWS profile, choosing HTML or PDF, then scanning and showing your score
|
|
156
|
+
with every finding.
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
plexavo # macOS/Linux, and Windows Option 1
|
|
160
|
+
python -m plexavo # Windows Option 2
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
<div align="center">
|
|
164
|
+
<img src="assets/screenshot-cli.png" alt="Plexavo interactive CLI" width="700">
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
### Running it on a schedule
|
|
168
|
+
|
|
169
|
+
Plexavo can also run unattended from cron or a GitHub Actions workflow,
|
|
170
|
+
and flag a run when something regresses so you get notified without
|
|
171
|
+
opening a report. See [`docs/automation.md`](docs/automation.md).
|
|
172
|
+
|
|
173
|
+
> [!NOTE]
|
|
174
|
+
> **Think of a scan as a snapshot, not a finish line.** Run it repeatedly to catch regressions as your infrastructure changes.
|
|
175
|
+
|
|
176
|
+
### 🤖 Using it from Claude Code
|
|
177
|
+
|
|
178
|
+
Using [Claude Code](https://claude.com/claude-code)? Save
|
|
179
|
+
[`plexavo-scan/SKILL.md`](https://raw.githubusercontent.com/plexavo/Plexavo/main/.claude/skills/plexavo-scan/SKILL.md)
|
|
180
|
+
to `~/.claude/skills/plexavo-scan/SKILL.md`
|
|
181
|
+
(`%USERPROFILE%\.claude\skills\plexavo-scan\SKILL.md` on Windows), then
|
|
182
|
+
just ask: "scan my AWS account for security issues using Plexavo." It
|
|
183
|
+
relays exactly what Plexavo found, and never runs a state-changing AWS
|
|
184
|
+
command without asking first.
|
|
185
|
+
|
|
186
|
+
## 📊 The report
|
|
187
|
+
|
|
188
|
+
Reports are generated as HTML, PDF, or both. Every finding gets a free,
|
|
189
|
+
template-based fix by default, no key, no cost. Full AI-written
|
|
190
|
+
narration kicks in automatically once an `ANTHROPIC_API_KEY` is
|
|
191
|
+
detected, see [Cost](#cost).
|
|
192
|
+
|
|
193
|
+
<div align="center">
|
|
194
|
+
<img src="assets/screenshot-report.png" alt="Plexavo HTML report" width="700">
|
|
195
|
+
</div>
|
|
196
|
+
|
|
197
|
+
## 💰 Cost
|
|
198
|
+
|
|
199
|
+
Detection and the free templates always cost nothing. Live AI only runs
|
|
200
|
+
with `--explain`, using **your own** `ANTHROPIC_API_KEY` in **your own**
|
|
201
|
+
Anthropic account.
|
|
202
|
+
|
|
203
|
+
Plexavo never sees your key and never calls the API without it. A full
|
|
204
|
+
scan with `--explain` typically costs a few cents.
|
|
205
|
+
|
|
206
|
+
> [!IMPORTANT]
|
|
207
|
+
> **No hidden AI bill.** If you don't provide an Anthropic API key, Plexavo does not make an AI API call.
|
|
208
|
+
|
|
209
|
+
## 🤝 Contributing
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
git clone https://github.com/plexavo/plexavo.git
|
|
213
|
+
cd plexavo
|
|
214
|
+
uv pip install -e .
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the pattern used to add a
|
|
218
|
+
new check.
|
|
219
|
+
|
|
220
|
+
### 🧨 Break Plexavo
|
|
221
|
+
|
|
222
|
+
Think you can make Plexavo miss something, or give confusing guidance?
|
|
223
|
+
|
|
224
|
+
[**Report it →**](../../issues/new?template=break-plexavo.yml)
|
|
225
|
+
|
|
226
|
+
Every confirmed, genuinely new finding gets fixed and shipped, and you
|
|
227
|
+
get a permanent credit in the [Hall of Bugs](HALL_OF_BUGS.md).
|
|
228
|
+
|
|
229
|
+
**No bounty. Public credit only.**
|
|
230
|
+
|
|
231
|
+
> [!TIP]
|
|
232
|
+
> **The best security tool is one that gets challenged.** If you find a blind spot, break it, report it, and help make the next scan better.
|
|
233
|
+
|
|
234
|
+
## 🔐 Security
|
|
235
|
+
|
|
236
|
+
Found a vulnerability in the tool itself, not a misconfiguration in your
|
|
237
|
+
own AWS account (that's the tool working correctly)?
|
|
238
|
+
|
|
239
|
+
See [`SECURITY.md`](SECURITY.md) for a private reporting path.
|
|
240
|
+
|
|
241
|
+
## 📄 License
|
|
242
|
+
|
|
243
|
+
AGPL-3.0, see [`LICENSE`](LICENSE).
|
|
244
|
+
|
|
245
|
+
Use, run, and modify it freely. If you run a modified version as a hosted
|
|
246
|
+
service, you're required to publish those modifications too.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
<div align="center">
|
|
251
|
+
|
|
252
|
+
**Plexavo · Open-source cloud security, built to be understood.**
|
|
253
|
+
|
|
254
|
+
<sub>Scan. Understand. Fix. Repeat.</sub>
|
|
255
|
+
|
|
256
|
+
</div>
|
plexavo-0.4.0/README.md
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="assets/plexavo-logo-dark.png">
|
|
5
|
+
<img src="assets/plexavo-logo-light.png" alt="Plexavo" height="105">
|
|
6
|
+
</picture>
|
|
7
|
+
|
|
8
|
+
<p>
|
|
9
|
+
<a href="https://github.com/plexavo/plexavo">
|
|
10
|
+
<img src="https://img.shields.io/github/stars/plexavo/plexavo?style=for-the-badge&logo=github&label=STARS" alt="GitHub stars">
|
|
11
|
+
</a>
|
|
12
|
+
<a href="https://github.com/plexavo/plexavo/blob/main/LICENSE">
|
|
13
|
+
<img src="https://img.shields.io/github/license/plexavo/plexavo?style=for-the-badge&label=LICENSE" alt="License">
|
|
14
|
+
</a>
|
|
15
|
+
<a href="https://pypi.org/project/plexavo/">
|
|
16
|
+
<img src="https://img.shields.io/pypi/v/plexavo?style=for-the-badge&logo=pypi&logoColor=white&label=PYPI" alt="PyPI version">
|
|
17
|
+
</a>
|
|
18
|
+
<a href="https://github.com/plexavo/plexavo/actions">
|
|
19
|
+
<img src="https://img.shields.io/github/actions/workflow/status/plexavo/plexavo/ci.yml?style=for-the-badge&label=BUILD" alt="Build status">
|
|
20
|
+
</a>
|
|
21
|
+
</p>
|
|
22
|
+
|
|
23
|
+
<p><strong>See your cloud. Secure what matters.</strong></p>
|
|
24
|
+
<p>Turn AWS configuration data into clear, actionable security intelligence.</p>
|
|
25
|
+
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
<div align="center">
|
|
31
|
+
<img src="assets/demo.gif" alt="Plexavo interactive scan demo" width="800">
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
> [!NOTE]
|
|
35
|
+
> **Plexavo is built around a simple idea:** security tooling should tell you what is wrong, why it matters, and what to do next, without requiring a dedicated security team.
|
|
36
|
+
|
|
37
|
+
Plexavo is an open-source cloud security tool that audits AWS accounts
|
|
38
|
+
for real-world misconfigurations, using your own local AWS credentials
|
|
39
|
+
the same way `aws s3 ls` does, so nothing about your account ever
|
|
40
|
+
leaves your machine.
|
|
41
|
+
|
|
42
|
+
Each scan produces a **0–100 security score** and a plain-English report:
|
|
43
|
+
what's wrong, what an attacker would actually do with it, and the exact
|
|
44
|
+
command to fix it.
|
|
45
|
+
|
|
46
|
+
> [!IMPORTANT]
|
|
47
|
+
> **Detection is not AI.** Plexavo's detections are pure Python/boto3;
|
|
48
|
+
> Claude only rewrites already-found findings for readability, and
|
|
49
|
+
> narration is fully optional. See [Cost](#cost).
|
|
50
|
+
|
|
51
|
+
> [!TIP]
|
|
52
|
+
> 🤖 **New: talk to Plexavo through Claude Code.** No CLI flags, just ask
|
|
53
|
+
> "scan my AWS account with Plexavo" in a Claude Code session and get a
|
|
54
|
+
> conversational security briefing. See
|
|
55
|
+
> [Using it from Claude Code](#using-it-from-claude-code).
|
|
56
|
+
|
|
57
|
+
## ✦ What it checks
|
|
58
|
+
|
|
59
|
+
**32 checks across 6 categories**, run against real AWS accounts:
|
|
60
|
+
|
|
61
|
+
| Category | What Plexavo looks for |
|
|
62
|
+
|---|---|
|
|
63
|
+
| **IAM** | Privilege escalation paths, wildcard admin, cross-account trust, root usage, dormant credentials |
|
|
64
|
+
| **Network** | Security groups and RDS instances exposed to the internet |
|
|
65
|
+
| **Storage** | Public S3 buckets via ACLs, bucket policies, or missing Block Public Access; buckets with no access logging |
|
|
66
|
+
| **Encryption** | Unencrypted EBS volumes, RDS instances, S3 buckets |
|
|
67
|
+
| **Logging** | CloudTrail coverage and encryption, GuardDuty status |
|
|
68
|
+
| **Usage** | Permissions granted but never used, roles nobody has assumed in 90+ days |
|
|
69
|
+
|
|
70
|
+
See `docs/*-TEST-MATRIX.md` for how each check was verified.
|
|
71
|
+
|
|
72
|
+
> [!TIP]
|
|
73
|
+
> **Don't just trust the number.** Plexavo keeps severity, confidence, and evidence as separate signals, so a low-confidence Critical does not read the same as a high-confidence Medium.
|
|
74
|
+
|
|
75
|
+
## ⚡ Installation
|
|
76
|
+
|
|
77
|
+
Every path below installs Plexavo into its own isolated environment.
|
|
78
|
+
|
|
79
|
+
### macOS & Linux
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
curl -LsSf https://astral.sh/uv/install.sh | sh # skip if you have uv
|
|
83
|
+
uv tool install plexavo
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Prefer [pipx](https://pipx.pypa.io/)? `pipx install plexavo` works the
|
|
87
|
+
same way.
|
|
88
|
+
|
|
89
|
+
### Windows
|
|
90
|
+
|
|
91
|
+
`uv`/`pipx` still work, but the PATH launcher is unsigned and Windows
|
|
92
|
+
Smart App Control blocks it. Run Plexavo through Python instead:
|
|
93
|
+
|
|
94
|
+
**Option 1, uv (recommended)**
|
|
95
|
+
|
|
96
|
+
```powershell
|
|
97
|
+
uv tool install plexavo
|
|
98
|
+
Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
|
|
99
|
+
if (!(Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
|
|
100
|
+
Add-Content $PROFILE 'function plexavo { & "$env:APPDATA\uv\tools\plexavo\Scripts\python.exe" -m plexavo @args }'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Open a new terminal. `plexavo` now works like it does on macOS/Linux,
|
|
104
|
+
routed through uv's signed Python instead of the blocked launcher.
|
|
105
|
+
|
|
106
|
+
**Option 2, plain venv**
|
|
107
|
+
|
|
108
|
+
```powershell
|
|
109
|
+
py -m venv plexavo-venv
|
|
110
|
+
.\plexavo-venv\Scripts\Activate.ps1
|
|
111
|
+
python -m pip install plexavo
|
|
112
|
+
python -m plexavo
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Use `python -m` for everything here too. The venv's own `pip.exe` and
|
|
116
|
+
`plexavo.exe` are unsigned as well, only `python.exe` is signed.
|
|
117
|
+
|
|
118
|
+
### AI narration (optional)
|
|
119
|
+
|
|
120
|
+
Want each finding rewritten as a full narrative? Install `"plexavo[ai]"`
|
|
121
|
+
instead of `plexavo`, and set `ANTHROPIC_API_KEY`. See [Cost](#cost).
|
|
122
|
+
|
|
123
|
+
## 🚀 Using Plexavo
|
|
124
|
+
|
|
125
|
+
Run it with no arguments and it walks you through everything: picking an
|
|
126
|
+
AWS profile, choosing HTML or PDF, then scanning and showing your score
|
|
127
|
+
with every finding.
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
plexavo # macOS/Linux, and Windows Option 1
|
|
131
|
+
python -m plexavo # Windows Option 2
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
<div align="center">
|
|
135
|
+
<img src="assets/screenshot-cli.png" alt="Plexavo interactive CLI" width="700">
|
|
136
|
+
</div>
|
|
137
|
+
|
|
138
|
+
### Running it on a schedule
|
|
139
|
+
|
|
140
|
+
Plexavo can also run unattended from cron or a GitHub Actions workflow,
|
|
141
|
+
and flag a run when something regresses so you get notified without
|
|
142
|
+
opening a report. See [`docs/automation.md`](docs/automation.md).
|
|
143
|
+
|
|
144
|
+
> [!NOTE]
|
|
145
|
+
> **Think of a scan as a snapshot, not a finish line.** Run it repeatedly to catch regressions as your infrastructure changes.
|
|
146
|
+
|
|
147
|
+
### 🤖 Using it from Claude Code
|
|
148
|
+
|
|
149
|
+
Using [Claude Code](https://claude.com/claude-code)? Save
|
|
150
|
+
[`plexavo-scan/SKILL.md`](https://raw.githubusercontent.com/plexavo/Plexavo/main/.claude/skills/plexavo-scan/SKILL.md)
|
|
151
|
+
to `~/.claude/skills/plexavo-scan/SKILL.md`
|
|
152
|
+
(`%USERPROFILE%\.claude\skills\plexavo-scan\SKILL.md` on Windows), then
|
|
153
|
+
just ask: "scan my AWS account for security issues using Plexavo." It
|
|
154
|
+
relays exactly what Plexavo found, and never runs a state-changing AWS
|
|
155
|
+
command without asking first.
|
|
156
|
+
|
|
157
|
+
## 📊 The report
|
|
158
|
+
|
|
159
|
+
Reports are generated as HTML, PDF, or both. Every finding gets a free,
|
|
160
|
+
template-based fix by default, no key, no cost. Full AI-written
|
|
161
|
+
narration kicks in automatically once an `ANTHROPIC_API_KEY` is
|
|
162
|
+
detected, see [Cost](#cost).
|
|
163
|
+
|
|
164
|
+
<div align="center">
|
|
165
|
+
<img src="assets/screenshot-report.png" alt="Plexavo HTML report" width="700">
|
|
166
|
+
</div>
|
|
167
|
+
|
|
168
|
+
## 💰 Cost
|
|
169
|
+
|
|
170
|
+
Detection and the free templates always cost nothing. Live AI only runs
|
|
171
|
+
with `--explain`, using **your own** `ANTHROPIC_API_KEY` in **your own**
|
|
172
|
+
Anthropic account.
|
|
173
|
+
|
|
174
|
+
Plexavo never sees your key and never calls the API without it. A full
|
|
175
|
+
scan with `--explain` typically costs a few cents.
|
|
176
|
+
|
|
177
|
+
> [!IMPORTANT]
|
|
178
|
+
> **No hidden AI bill.** If you don't provide an Anthropic API key, Plexavo does not make an AI API call.
|
|
179
|
+
|
|
180
|
+
## 🤝 Contributing
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
git clone https://github.com/plexavo/plexavo.git
|
|
184
|
+
cd plexavo
|
|
185
|
+
uv pip install -e .
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the pattern used to add a
|
|
189
|
+
new check.
|
|
190
|
+
|
|
191
|
+
### 🧨 Break Plexavo
|
|
192
|
+
|
|
193
|
+
Think you can make Plexavo miss something, or give confusing guidance?
|
|
194
|
+
|
|
195
|
+
[**Report it →**](../../issues/new?template=break-plexavo.yml)
|
|
196
|
+
|
|
197
|
+
Every confirmed, genuinely new finding gets fixed and shipped, and you
|
|
198
|
+
get a permanent credit in the [Hall of Bugs](HALL_OF_BUGS.md).
|
|
199
|
+
|
|
200
|
+
**No bounty. Public credit only.**
|
|
201
|
+
|
|
202
|
+
> [!TIP]
|
|
203
|
+
> **The best security tool is one that gets challenged.** If you find a blind spot, break it, report it, and help make the next scan better.
|
|
204
|
+
|
|
205
|
+
## 🔐 Security
|
|
206
|
+
|
|
207
|
+
Found a vulnerability in the tool itself, not a misconfiguration in your
|
|
208
|
+
own AWS account (that's the tool working correctly)?
|
|
209
|
+
|
|
210
|
+
See [`SECURITY.md`](SECURITY.md) for a private reporting path.
|
|
211
|
+
|
|
212
|
+
## 📄 License
|
|
213
|
+
|
|
214
|
+
AGPL-3.0, see [`LICENSE`](LICENSE).
|
|
215
|
+
|
|
216
|
+
Use, run, and modify it freely. If you run a modified version as a hosted
|
|
217
|
+
service, you're required to publish those modifications too.
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
<div align="center">
|
|
222
|
+
|
|
223
|
+
**Plexavo · Open-source cloud security, built to be understood.**
|
|
224
|
+
|
|
225
|
+
<sub>Scan. Understand. Fix. Repeat.</sub>
|
|
226
|
+
|
|
227
|
+
</div>
|
|
@@ -12,6 +12,7 @@ credentials, the same way `aws s3 ls` does.
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
import argparse
|
|
15
|
+
import json
|
|
15
16
|
import os
|
|
16
17
|
import random
|
|
17
18
|
import sys
|
|
@@ -38,6 +39,7 @@ from plexavo.findings import Severity
|
|
|
38
39
|
from plexavo.scoring import calculate_score
|
|
39
40
|
from plexavo.report.ai_narration import explain_finding, COMMON_CHECK_TEMPLATES
|
|
40
41
|
from plexavo.report.html_report import build_report_data, generate_html
|
|
42
|
+
from plexavo.report.json_report import build_json_report
|
|
41
43
|
from plexavo.report.pdf import generate_pdf
|
|
42
44
|
|
|
43
45
|
console = Console()
|
|
@@ -147,10 +149,34 @@ def _build_parser() -> argparse.ArgumentParser:
|
|
|
147
149
|
"non-zero exit makes GitHub Actions or cron flag the run, so a regression "
|
|
148
150
|
"reaches you without opening a report by hand. Exit 1 still means the scan "
|
|
149
151
|
"itself could not run. Off by default. See docs/automation.md.")
|
|
152
|
+
scan.add_argument("--format", choices=["json"], default=None,
|
|
153
|
+
help="Print one machine-readable JSON object to stdout instead of the normal "
|
|
154
|
+
"console output (findings table, panels, progress) — that output still "
|
|
155
|
+
"happens, it just goes to stderr, so stdout is always exactly one valid "
|
|
156
|
+
"JSON object. Meant for a calling agent/script, not a person at a terminal. "
|
|
157
|
+
"--report-html/--report-pdf still write their files as normal alongside it.")
|
|
150
158
|
return parser
|
|
151
159
|
|
|
152
160
|
|
|
153
161
|
def _run_scan(args) -> None:
|
|
162
|
+
"""Thin wrapper around _run_scan_impl: when --format json is set,
|
|
163
|
+
every existing console.print call in the impl still runs unchanged,
|
|
164
|
+
it's just redirected to stderr for the duration, so stdout stays
|
|
165
|
+
exactly one JSON object printed via plain print() at the end. Always
|
|
166
|
+
restored afterward (try/finally), including on sys.exit paths, so a
|
|
167
|
+
later in-process call (tests, or a future caller) never inherits a
|
|
168
|
+
silently-redirected console."""
|
|
169
|
+
json_mode = args.format == "json"
|
|
170
|
+
original_file = console.file
|
|
171
|
+
if json_mode:
|
|
172
|
+
console.file = sys.stderr
|
|
173
|
+
try:
|
|
174
|
+
_run_scan_impl(args, json_mode)
|
|
175
|
+
finally:
|
|
176
|
+
console.file = original_file
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _run_scan_impl(args, json_mode: bool) -> None:
|
|
154
180
|
console.print(f"[bold]Using AWS profile:[/bold] {args.profile or '(default)'}")
|
|
155
181
|
try:
|
|
156
182
|
session = get_local_session(profile_name=args.profile, region=args.region)
|
|
@@ -168,7 +194,11 @@ def _run_scan(args) -> None:
|
|
|
168
194
|
# False (confirmed — Rich only renders it in real terminals), so
|
|
169
195
|
# scripting users get the exact same plain progress lines as before,
|
|
170
196
|
# not silence. This is a deliberate branch, not an oversight.
|
|
171
|
-
|
|
197
|
+
# Forced off in json_mode regardless of the actual terminal, so
|
|
198
|
+
# progress always uses plain print-per-stage lines (to stderr), never
|
|
199
|
+
# a Rich Live/Status spinner — deterministic output for a calling
|
|
200
|
+
# agent/script, not a cosmetic call.
|
|
201
|
+
is_tty = console.is_terminal and not json_mode
|
|
172
202
|
findings = []
|
|
173
203
|
principals = []
|
|
174
204
|
chains = []
|
|
@@ -244,6 +274,8 @@ def _run_scan(args) -> None:
|
|
|
244
274
|
# never make it into a report.
|
|
245
275
|
if not findings and not chains:
|
|
246
276
|
console.print("[green]No findings.[/green]")
|
|
277
|
+
if json_mode:
|
|
278
|
+
print(json.dumps(build_json_report(findings, result, account_id, [], chains=chains), indent=2))
|
|
247
279
|
_apply_fail_on(args.fail_on, findings)
|
|
248
280
|
return
|
|
249
281
|
|
|
@@ -352,6 +384,10 @@ def _run_scan(args) -> None:
|
|
|
352
384
|
generate_pdf(report_data, args.report_pdf)
|
|
353
385
|
console.print(f"[green]PDF report written to {os.path.abspath(args.report_pdf)}[/green]")
|
|
354
386
|
|
|
387
|
+
if json_mode:
|
|
388
|
+
json_payload = build_json_report(findings, result, account_id, explanations, chains=chains)
|
|
389
|
+
print(json.dumps(json_payload, indent=2))
|
|
390
|
+
|
|
355
391
|
# Last thing in the scan: any report is on disk by now, so a non-zero
|
|
356
392
|
# exit here never costs you the report.
|
|
357
393
|
_apply_fail_on(args.fail_on, findings)
|
|
@@ -328,4 +328,5 @@ def run_interactive() -> None:
|
|
|
328
328
|
report_html=options["report_html"],
|
|
329
329
|
report_pdf=options["report_pdf"],
|
|
330
330
|
fail_on=None, # scheduled-run flag only; a person watching the score doesn't need it
|
|
331
|
+
format=None, # agent/script output mode only; interactive mode always shows the normal console UI
|
|
331
332
|
))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""report/json_report.py — machine-readable JSON output, additive
|
|
2
|
+
alongside the existing HTML/PDF reports (see html_report.py). Built for
|
|
3
|
+
agent/orchestration callers (the Claude Code Agent Skill this feeds) that
|
|
4
|
+
need a stable, parseable shape rather than rendered markup.
|
|
5
|
+
|
|
6
|
+
This module never computes anything new. It reuses the exact same
|
|
7
|
+
findings/score/chain objects every report already gets from _run_scan,
|
|
8
|
+
including _chain_report_views (the same chain-dedup/badge logic the HTML
|
|
9
|
+
Important tab uses) — a JSON consumer must see the identical picture a
|
|
10
|
+
human reading the HTML report would, never a second, independently
|
|
11
|
+
derived interpretation.
|
|
12
|
+
|
|
13
|
+
schema_version is bumped only on a breaking shape change, so a consumer
|
|
14
|
+
(the Skill) can pin to a known version and fail loudly instead of
|
|
15
|
+
silently misparsing a future release.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
|
|
20
|
+
from plexavo import __version__
|
|
21
|
+
from plexavo.report.html_report import _chain_report_views
|
|
22
|
+
|
|
23
|
+
SCHEMA_VERSION = "1.0"
|
|
24
|
+
|
|
25
|
+
# Limitations that must stay disclosed in every surface Plexavo ships,
|
|
26
|
+
# this one included — never let a machine-readable summary imply broader
|
|
27
|
+
# coverage than what actually ran, per the no-optics-tuning rule.
|
|
28
|
+
DISCLOSED_LIMITATIONS = [
|
|
29
|
+
"Attack-path detection covers single-account chains only, matched against "
|
|
30
|
+
"two hardcoded templates — not an exhaustive graph traversal.",
|
|
31
|
+
"Privilege-escalation detection is one-hop only.",
|
|
32
|
+
"Cross-account trust resolution is not performed.",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def build_json_report(findings, score_result, account_id, explanations=None, chains=None) -> dict:
|
|
37
|
+
"""Same inputs as html_report.build_report_data — a second view over
|
|
38
|
+
the identical scan output, not a second computation of it.
|
|
39
|
+
|
|
40
|
+
Findings are ordered most-severe-first (Critical -> Low), matching how
|
|
41
|
+
the HTML report groups them, so a consumer presenting "top findings
|
|
42
|
+
first" doesn't need to re-sort. Where a resource has more than one
|
|
43
|
+
finding, `findings_by_resource_arn` (used to back attack-chain nodes
|
|
44
|
+
with real finding content) keeps the most severe one, same rule
|
|
45
|
+
build_report_data uses.
|
|
46
|
+
"""
|
|
47
|
+
if explanations is None:
|
|
48
|
+
explanations = [None] * len(findings)
|
|
49
|
+
if len(explanations) != len(findings):
|
|
50
|
+
raise ValueError(
|
|
51
|
+
f"explanations must be the same length as findings ({len(findings)}), "
|
|
52
|
+
f"got {len(explanations)} — pass None for any unexplained finding, not a shorter list"
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
paired = sorted(zip(findings, explanations), key=lambda pair: -pair[0].severity.rank)
|
|
56
|
+
|
|
57
|
+
findings_view = []
|
|
58
|
+
findings_by_resource_arn = {}
|
|
59
|
+
for f, exp in paired:
|
|
60
|
+
if exp is not None:
|
|
61
|
+
impact, how_to_fix, next_step = exp.impact, exp.how_to_fix, exp.next_step
|
|
62
|
+
# Same derivation build_report_data uses: judged from actual
|
|
63
|
+
# content, not exp.source, so "fallback"/"api-unparsed" (which
|
|
64
|
+
# carry the same empty how_to_fix/next_step shape as "never
|
|
65
|
+
# attempted") don't falsely claim to be explained.
|
|
66
|
+
explained = bool(how_to_fix) and bool(next_step)
|
|
67
|
+
else:
|
|
68
|
+
impact, how_to_fix, next_step, explained = f.raw_detail, "", "", False
|
|
69
|
+
|
|
70
|
+
entry = {
|
|
71
|
+
"check_id": f.check_id,
|
|
72
|
+
"title": f.title,
|
|
73
|
+
"severity": f.severity.value,
|
|
74
|
+
"resource": f.resource_arn.rsplit("/", 1)[-1] if "/" in f.resource_arn else f.resource_arn,
|
|
75
|
+
"resource_arn": f.resource_arn,
|
|
76
|
+
"confidence": f.confidence,
|
|
77
|
+
"evidence": f.evidence,
|
|
78
|
+
"impact": impact,
|
|
79
|
+
"explained": explained,
|
|
80
|
+
"next_step": next_step,
|
|
81
|
+
"how_to_fix": how_to_fix,
|
|
82
|
+
"chain_breaks_count": f.chain_breaks_count,
|
|
83
|
+
}
|
|
84
|
+
findings_view.append(entry)
|
|
85
|
+
findings_by_resource_arn.setdefault(f.resource_arn, entry)
|
|
86
|
+
|
|
87
|
+
chains_view, important_findings = _chain_report_views(chains or [], findings_by_resource_arn)
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"schema_version": SCHEMA_VERSION,
|
|
91
|
+
"plexavo_version": __version__,
|
|
92
|
+
"account_id": account_id,
|
|
93
|
+
"scan_date": datetime.now(timezone.utc).strftime("%Y-%m-%d"),
|
|
94
|
+
"score": score_result.score,
|
|
95
|
+
"rating": score_result.rating,
|
|
96
|
+
"summary_line": score_result.summary_line(),
|
|
97
|
+
"counts_by_severity": score_result.counts_by_severity,
|
|
98
|
+
"counts_by_category": score_result.counts_by_category,
|
|
99
|
+
"total_findings": score_result.total_findings,
|
|
100
|
+
"findings": findings_view,
|
|
101
|
+
"attack_chains": chains_view,
|
|
102
|
+
"important_findings": important_findings,
|
|
103
|
+
"disclosed_limitations": DISCLOSED_LIMITATIONS,
|
|
104
|
+
}
|