plexavo 0.2.0__tar.gz → 0.2.2__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.
Files changed (50) hide show
  1. {plexavo-0.2.0 → plexavo-0.2.2}/PKG-INFO +75 -15
  2. {plexavo-0.2.0 → plexavo-0.2.2}/README.md +74 -14
  3. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/__init__.py +1 -1
  4. plexavo-0.2.2/plexavo/__main__.py +13 -0
  5. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/auth.py +9 -1
  6. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/interactive.py +42 -12
  7. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/PKG-INFO +75 -15
  8. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/SOURCES.txt +3 -0
  9. {plexavo-0.2.0 → plexavo-0.2.2}/pyproject.toml +1 -1
  10. plexavo-0.2.2/tests/test_auth_offline.py +91 -0
  11. plexavo-0.2.2/tests/test_cli_entrypoint.py +50 -0
  12. {plexavo-0.2.0 → plexavo-0.2.2}/LICENSE +0 -0
  13. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/aws_profile_setup.py +0 -0
  14. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/__init__.py +0 -0
  15. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/encryption.py +0 -0
  16. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/iam.py +0 -0
  17. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/iam_hygiene.py +0 -0
  18. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/logging.py +0 -0
  19. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/network.py +0 -0
  20. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/storage.py +0 -0
  21. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/checks/usage.py +0 -0
  22. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/cli.py +0 -0
  23. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/findings.py +0 -0
  24. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/principals.py +0 -0
  25. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/__init__.py +0 -0
  26. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/ai_narration.py +0 -0
  27. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
  28. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  29. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
  30. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
  31. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
  32. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/html_report.py +0 -0
  33. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/pdf.py +0 -0
  34. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/report/templates/report.html.j2 +0 -0
  35. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo/scoring.py +0 -0
  36. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/dependency_links.txt +0 -0
  37. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/entry_points.txt +0 -0
  38. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/requires.txt +0 -0
  39. {plexavo-0.2.0 → plexavo-0.2.2}/plexavo.egg-info/top_level.txt +0 -0
  40. {plexavo-0.2.0 → plexavo-0.2.2}/setup.cfg +0 -0
  41. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_ai_narration_offline.py +0 -0
  42. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_encryption_offline.py +0 -0
  43. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_iam_hygiene_offline.py +0 -0
  44. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_iam_offline.py +0 -0
  45. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_logging_offline.py +0 -0
  46. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_network_offline.py +0 -0
  47. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_report_offline.py +0 -0
  48. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_scoring.py +0 -0
  49. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_storage_offline.py +0 -0
  50. {plexavo-0.2.0 → plexavo-0.2.2}/tests/test_usage_offline.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
5
5
  Author: Kavee
6
6
  License: AGPL-3.0-or-later
@@ -127,20 +127,24 @@ scoped by a Condition block, or a CloudTrail lookup that hit its page cap on
127
127
 
128
128
  ## Quick start
129
129
 
130
- **Recommended: [uv](https://docs.astral.sh/uv/).** It installs Plexavo into
131
- its own isolated environment automatically — no venv to create, activate,
132
- or remember to reactivate in every new terminal.
130
+ The install path depends on your OS — **uv on macOS & Linux**, **pip on
131
+ Windows**. Either way it's a one-command install with no venv to create,
132
+ activate, or reactivate in every new terminal.
133
133
 
134
- ### 1. Install uv (if you don't already have it)
134
+ ### macOS & Linux — uv
135
+
136
+ [uv](https://docs.astral.sh/uv/) installs Plexavo into its own isolated
137
+ environment automatically.
138
+
139
+ **1. Install uv** (if you don't already have it):
135
140
 
136
141
  ```bash
137
- curl -LsSf https://astral.sh/uv/install.sh | sh # Mac/Linux
138
- powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
142
+ curl -LsSf https://astral.sh/uv/install.sh | sh
139
143
  ```
140
144
 
141
145
  (Full options: [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).)
142
146
 
143
- ### 2. Install Plexavo
147
+ **2. Install Plexavo:**
144
148
 
145
149
  ```bash
146
150
  uv tool install plexavo
@@ -178,12 +182,60 @@ uv tool upgrade plexavo
178
182
  uv tool uninstall plexavo
179
183
  ```
180
184
 
181
- ### 3. Run a scan
185
+ ### Windows — pip
186
+
187
+ Install with `pip` and run the tool as `py -m plexavo`:
188
+
189
+ ```powershell
190
+ py -m pip install --user plexavo
191
+ py -m plexavo
192
+ ```
193
+
194
+ `py -m plexavo` with no arguments opens the interactive menu, exactly
195
+ like the `plexavo` command does on macOS/Linux — flags are only for
196
+ scripting/CI.
197
+
198
+ > **Why not uv/pipx on Windows?** Both work by putting a small generated
199
+ > `plexavo.exe` launcher on your PATH. That launcher is unsigned, and
200
+ > Windows Smart App Control blocks unsigned executables it doesn't
201
+ > recognise — so `plexavo` can fail to start with a "can't confirm who
202
+ > published" message. `py -m plexavo` calls Python directly and never
203
+ > touches that launcher, so it always works. (If your machine doesn't
204
+ > enforce Smart App Control, `uv tool install plexavo` works here too.)
205
+
206
+ **AI-narrated explanations** (see [Cost](#cost)):
207
+
208
+ ```powershell
209
+ py -m pip install --user "plexavo[ai]"
210
+ ```
211
+
212
+ Updating or removing later:
213
+
214
+ ```powershell
215
+ py -m pip install --user --upgrade plexavo
216
+ py -m pip uninstall plexavo
217
+ ```
218
+
219
+ ### Optional: type `plexavo` instead of `py -m plexavo`
220
+
221
+ Add a shortcut to your PowerShell profile once:
222
+
223
+ ```powershell
224
+ Add-Content $PROFILE 'function plexavo { py -m plexavo @args }'
225
+ ```
226
+
227
+ Open a new terminal and `plexavo` then works just like it does on
228
+ macOS/Linux.
229
+
230
+ ### Run a scan
182
231
 
183
232
  ```bash
184
233
  plexavo scan --profile my-aws-profile --report-html report.html
185
234
  ```
186
235
 
236
+ (On Windows without the shortcut above: `py -m plexavo scan --profile
237
+ my-aws-profile --report-html report.html`.)
238
+
187
239
  No `--profile`? It uses your default profile / environment variables,
188
240
  same resolution order as the AWS CLI. No AI, no API key, no cost — and
189
241
  findings still come with free Next Step / Full Fix Detail guidance
@@ -213,19 +265,24 @@ plexavo scan --profile my-aws-profile --explain --report-html report.html --repo
213
265
 
214
266
  ### Alternative: pipx
215
267
 
216
- Already use [pipx](https://pipx.pypa.io/)? It works exactly the same way —
217
- its own isolated environment, one global command, no venv:
268
+ Already use [pipx](https://pipx.pypa.io/) on macOS/Linux? It works exactly
269
+ the same way as uv — its own isolated environment, one global command, no
270
+ venv:
218
271
 
219
272
  ```bash
220
273
  pipx install plexavo
221
274
  pipx install "plexavo[ai]" # with AI-narrated explanations
222
275
  ```
223
276
 
277
+ On Windows, pipx has the same Smart App Control caveat as uv (see the
278
+ Windows section above) — if the `plexavo` command is blocked, run
279
+ `py -m plexavo` instead, or use the `pip` install.
280
+
224
281
  ### Alternative: pip + venv
225
282
 
226
- Prefer to manage the environment yourself, or on a system without uv/pipx?
227
- Modern Python (PEP 668) blocks a plain `pip install` outside a venv on
228
- many systems, so create one first:
283
+ Want full isolation and prefer to manage the environment yourself? Modern
284
+ Python (PEP 668) blocks a plain `pip install` outside a venv on many
285
+ Linux and Homebrew setups, so create one first:
229
286
 
230
287
  ```bash
231
288
  python -m venv plexavo-env
@@ -238,7 +295,9 @@ pip install "plexavo[ai]" # with AI-narrated explanations
238
295
 
239
296
  You'll need to reactivate this venv (`plexavo-env\Scripts\activate` /
240
297
  `source plexavo-env/bin/activate`) every time you open a new terminal —
241
- `deactivate` exits it without uninstalling anything.
298
+ `deactivate` exits it without uninstalling anything. On Windows, run
299
+ `python -m plexavo` if the `plexavo` command is blocked by Smart App
300
+ Control.
242
301
 
243
302
  ### From source (for contributing, or trying an unreleased change)
244
303
 
@@ -257,6 +316,7 @@ plexavo/
257
316
  ├── findings.py # Finding data model, Severity enum
258
317
  ├── scoring.py # 0-100 score from a list of Findings
259
318
  ├── cli.py # `plexavo scan ...` entry point
319
+ ├── __main__.py # lets `python -m plexavo` run the CLI
260
320
  ├── checks/
261
321
  │ ├── iam.py # IAM-01 to IAM-06 (privilege escalation)
262
322
  │ ├── iam_hygiene.py # IAM-07 to IAM-14 (hygiene, cross-account trust)
@@ -98,20 +98,24 @@ scoped by a Condition block, or a CloudTrail lookup that hit its page cap on
98
98
 
99
99
  ## Quick start
100
100
 
101
- **Recommended: [uv](https://docs.astral.sh/uv/).** It installs Plexavo into
102
- its own isolated environment automatically — no venv to create, activate,
103
- or remember to reactivate in every new terminal.
101
+ The install path depends on your OS — **uv on macOS & Linux**, **pip on
102
+ Windows**. Either way it's a one-command install with no venv to create,
103
+ activate, or reactivate in every new terminal.
104
104
 
105
- ### 1. Install uv (if you don't already have it)
105
+ ### macOS & Linux — uv
106
+
107
+ [uv](https://docs.astral.sh/uv/) installs Plexavo into its own isolated
108
+ environment automatically.
109
+
110
+ **1. Install uv** (if you don't already have it):
106
111
 
107
112
  ```bash
108
- curl -LsSf https://astral.sh/uv/install.sh | sh # Mac/Linux
109
- powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
113
+ curl -LsSf https://astral.sh/uv/install.sh | sh
110
114
  ```
111
115
 
112
116
  (Full options: [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).)
113
117
 
114
- ### 2. Install Plexavo
118
+ **2. Install Plexavo:**
115
119
 
116
120
  ```bash
117
121
  uv tool install plexavo
@@ -149,12 +153,60 @@ uv tool upgrade plexavo
149
153
  uv tool uninstall plexavo
150
154
  ```
151
155
 
152
- ### 3. Run a scan
156
+ ### Windows — pip
157
+
158
+ Install with `pip` and run the tool as `py -m plexavo`:
159
+
160
+ ```powershell
161
+ py -m pip install --user plexavo
162
+ py -m plexavo
163
+ ```
164
+
165
+ `py -m plexavo` with no arguments opens the interactive menu, exactly
166
+ like the `plexavo` command does on macOS/Linux — flags are only for
167
+ scripting/CI.
168
+
169
+ > **Why not uv/pipx on Windows?** Both work by putting a small generated
170
+ > `plexavo.exe` launcher on your PATH. That launcher is unsigned, and
171
+ > Windows Smart App Control blocks unsigned executables it doesn't
172
+ > recognise — so `plexavo` can fail to start with a "can't confirm who
173
+ > published" message. `py -m plexavo` calls Python directly and never
174
+ > touches that launcher, so it always works. (If your machine doesn't
175
+ > enforce Smart App Control, `uv tool install plexavo` works here too.)
176
+
177
+ **AI-narrated explanations** (see [Cost](#cost)):
178
+
179
+ ```powershell
180
+ py -m pip install --user "plexavo[ai]"
181
+ ```
182
+
183
+ Updating or removing later:
184
+
185
+ ```powershell
186
+ py -m pip install --user --upgrade plexavo
187
+ py -m pip uninstall plexavo
188
+ ```
189
+
190
+ ### Optional: type `plexavo` instead of `py -m plexavo`
191
+
192
+ Add a shortcut to your PowerShell profile once:
193
+
194
+ ```powershell
195
+ Add-Content $PROFILE 'function plexavo { py -m plexavo @args }'
196
+ ```
197
+
198
+ Open a new terminal and `plexavo` then works just like it does on
199
+ macOS/Linux.
200
+
201
+ ### Run a scan
153
202
 
154
203
  ```bash
155
204
  plexavo scan --profile my-aws-profile --report-html report.html
156
205
  ```
157
206
 
207
+ (On Windows without the shortcut above: `py -m plexavo scan --profile
208
+ my-aws-profile --report-html report.html`.)
209
+
158
210
  No `--profile`? It uses your default profile / environment variables,
159
211
  same resolution order as the AWS CLI. No AI, no API key, no cost — and
160
212
  findings still come with free Next Step / Full Fix Detail guidance
@@ -184,19 +236,24 @@ plexavo scan --profile my-aws-profile --explain --report-html report.html --repo
184
236
 
185
237
  ### Alternative: pipx
186
238
 
187
- Already use [pipx](https://pipx.pypa.io/)? It works exactly the same way —
188
- its own isolated environment, one global command, no venv:
239
+ Already use [pipx](https://pipx.pypa.io/) on macOS/Linux? It works exactly
240
+ the same way as uv — its own isolated environment, one global command, no
241
+ venv:
189
242
 
190
243
  ```bash
191
244
  pipx install plexavo
192
245
  pipx install "plexavo[ai]" # with AI-narrated explanations
193
246
  ```
194
247
 
248
+ On Windows, pipx has the same Smart App Control caveat as uv (see the
249
+ Windows section above) — if the `plexavo` command is blocked, run
250
+ `py -m plexavo` instead, or use the `pip` install.
251
+
195
252
  ### Alternative: pip + venv
196
253
 
197
- Prefer to manage the environment yourself, or on a system without uv/pipx?
198
- Modern Python (PEP 668) blocks a plain `pip install` outside a venv on
199
- many systems, so create one first:
254
+ Want full isolation and prefer to manage the environment yourself? Modern
255
+ Python (PEP 668) blocks a plain `pip install` outside a venv on many
256
+ Linux and Homebrew setups, so create one first:
200
257
 
201
258
  ```bash
202
259
  python -m venv plexavo-env
@@ -209,7 +266,9 @@ pip install "plexavo[ai]" # with AI-narrated explanations
209
266
 
210
267
  You'll need to reactivate this venv (`plexavo-env\Scripts\activate` /
211
268
  `source plexavo-env/bin/activate`) every time you open a new terminal —
212
- `deactivate` exits it without uninstalling anything.
269
+ `deactivate` exits it without uninstalling anything. On Windows, run
270
+ `python -m plexavo` if the `plexavo` command is blocked by Smart App
271
+ Control.
213
272
 
214
273
  ### From source (for contributing, or trying an unreleased change)
215
274
 
@@ -228,6 +287,7 @@ plexavo/
228
287
  ├── findings.py # Finding data model, Severity enum
229
288
  ├── scoring.py # 0-100 score from a list of Findings
230
289
  ├── cli.py # `plexavo scan ...` entry point
290
+ ├── __main__.py # lets `python -m plexavo` run the CLI
231
291
  ├── checks/
232
292
  │ ├── iam.py # IAM-01 to IAM-06 (privilege escalation)
233
293
  │ ├── iam_hygiene.py # IAM-07 to IAM-14 (hygiene, cross-account trust)
@@ -4,4 +4,4 @@ Runs entirely with your own local AWS credentials. Nothing is sent to
4
4
  anyone else. See README.md for usage, or `plexavo scan --help`.
5
5
  """
6
6
 
7
- __version__ = "0.2.0"
7
+ __version__ = "0.2.2"
@@ -0,0 +1,13 @@
1
+ """Allow `python -m plexavo` (and `py -m plexavo` on Windows) to launch the CLI.
2
+
3
+ This is the exact same entry point as the installed `plexavo` command, so
4
+ running it with no arguments still opens the interactive menu. It exists so
5
+ Windows users can install with plain `pip` and run the tool without going
6
+ through the generated `plexavo.exe` launcher, which Windows Smart App Control
7
+ blocks on some machines (it's unsigned). See the README's Quick start.
8
+ """
9
+
10
+ from plexavo.cli import main
11
+
12
+ if __name__ == "__main__":
13
+ main()
@@ -11,7 +11,7 @@ your own credentials, and nothing leaves your machine.
11
11
  from __future__ import annotations
12
12
 
13
13
  import boto3
14
- from botocore.exceptions import NoCredentialsError, ProfileNotFound
14
+ from botocore.exceptions import ClientError, NoCredentialsError, ProfileNotFound
15
15
 
16
16
 
17
17
  def get_local_session(profile_name: str | None = None, region: str | None = None) -> boto3.Session:
@@ -50,6 +50,14 @@ def get_local_session(profile_name: str | None = None, region: str | None = None
50
50
  f"{f'--profile {profile_name} ' if profile_name else ''}"
51
51
  "to set them up."
52
52
  ) from e
53
+ except ClientError as e:
54
+ code = e.response.get("Error", {}).get("Code", "Unknown")
55
+ raise RuntimeError(
56
+ f"AWS rejected these credentials ({code}). The access key may "
57
+ "be invalid, deactivated, or deleted — run `aws configure "
58
+ f"{f'--profile {profile_name} ' if profile_name else ''}"
59
+ "` to update them."
60
+ ) from e
53
61
 
54
62
  return boto3.Session(profile_name=profile_name, region_name=resolved_region)
55
63
 
@@ -16,10 +16,10 @@ import os
16
16
 
17
17
  import boto3
18
18
  from rich import box
19
- from rich.align import Align
20
- from rich.console import Console
19
+ from rich.console import Console, Group
21
20
  from rich.panel import Panel
22
21
  from rich.prompt import Confirm, IntPrompt, Prompt
22
+ from rich.table import Table
23
23
  from rich.text import Text
24
24
 
25
25
  from plexavo import __version__
@@ -42,27 +42,57 @@ FEATURES = [
42
42
  "100% local scanning — your AWS credentials never leave this machine",
43
43
  ]
44
44
 
45
- def _splash_panel() -> Panel:
46
- header = Text()
47
- header.append("PLEXAVO", style="bold white")
48
- header.append(" ")
49
- header.append(f"v{__version__}", style="grey62")
45
+ def _gradient_wordmark(word: str, min_shade: int = 90, max_shade: int = 255) -> Text:
46
+ """Letter-spaced word with a symmetric grey -> white -> grey shimmer,
47
+ one shade per letter (a smooth peak at the center, not a linear ramp),
48
+ e.g. "P L E X A V O" fading brightest at the middle letter."""
49
+ text = Text()
50
+ n = len(word)
51
+ for i, ch in enumerate(word):
52
+ t = 1 - abs((i / (n - 1)) * 2 - 1) if n > 1 else 1.0
53
+ shade = round(min_shade + t * (max_shade - min_shade))
54
+ text.append(ch, style=f"bold #{shade:02x}{shade:02x}{shade:02x}")
55
+ if i != n - 1:
56
+ text.append(" ")
57
+ return text
58
+
59
+
60
+ CLOUD_ART = (
61
+ " .--. \n"
62
+ " .-( ). \n"
63
+ " (___.__)__) "
64
+ )
65
+
66
+
67
+ def _header_row() -> Table:
68
+ """Wordmark pinned to the left corner, a small ASCII cloud floating
69
+ in the opposite corner — a quiet logomark, not a centerpiece."""
70
+ wordmark = _gradient_wordmark("PLEXAVO")
71
+ wordmark.append(" ")
72
+ wordmark.append(f"v{__version__}", style="grey62")
73
+
74
+ grid = Table.grid(expand=True)
75
+ grid.add_column(ratio=1)
76
+ grid.add_column(justify="right")
77
+ grid.add_row(wordmark, Text(CLOUD_ART, style="grey50"))
78
+ return grid
79
+
50
80
 
81
+ def _splash_panel() -> Panel:
51
82
  body = Text()
52
- body.append_text(header)
53
- body.append("\n")
54
83
  body.append("AWS Security Reimagined", style="italic grey74")
55
84
  body.append("\n")
56
85
  body.append(WEBSITE, style="underline grey62")
57
86
  body.append("\n\n")
58
- body.append("FEATURES\n", style="bold grey74")
87
+ body.append("─" * 50, style="grey42")
88
+ body.append("\n\n")
59
89
  for i, feature in enumerate(FEATURES):
60
- body.append(f" · {feature}", style="grey66")
90
+ body.append(f" ▸ {feature}", style="grey66")
61
91
  if i != len(FEATURES) - 1:
62
92
  body.append("\n")
63
93
 
64
94
  return Panel(
65
- Align.center(body),
95
+ Group(_header_row(), body),
66
96
  border_style="grey50",
67
97
  box=box.ROUNDED,
68
98
  padding=(1, 4),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else.
5
5
  Author: Kavee
6
6
  License: AGPL-3.0-or-later
@@ -127,20 +127,24 @@ scoped by a Condition block, or a CloudTrail lookup that hit its page cap on
127
127
 
128
128
  ## Quick start
129
129
 
130
- **Recommended: [uv](https://docs.astral.sh/uv/).** It installs Plexavo into
131
- its own isolated environment automatically — no venv to create, activate,
132
- or remember to reactivate in every new terminal.
130
+ The install path depends on your OS — **uv on macOS & Linux**, **pip on
131
+ Windows**. Either way it's a one-command install with no venv to create,
132
+ activate, or reactivate in every new terminal.
133
133
 
134
- ### 1. Install uv (if you don't already have it)
134
+ ### macOS & Linux — uv
135
+
136
+ [uv](https://docs.astral.sh/uv/) installs Plexavo into its own isolated
137
+ environment automatically.
138
+
139
+ **1. Install uv** (if you don't already have it):
135
140
 
136
141
  ```bash
137
- curl -LsSf https://astral.sh/uv/install.sh | sh # Mac/Linux
138
- powershell -c "irm https://astral.sh/uv/install.ps1 | iex" # Windows
142
+ curl -LsSf https://astral.sh/uv/install.sh | sh
139
143
  ```
140
144
 
141
145
  (Full options: [uv installation docs](https://docs.astral.sh/uv/getting-started/installation/).)
142
146
 
143
- ### 2. Install Plexavo
147
+ **2. Install Plexavo:**
144
148
 
145
149
  ```bash
146
150
  uv tool install plexavo
@@ -178,12 +182,60 @@ uv tool upgrade plexavo
178
182
  uv tool uninstall plexavo
179
183
  ```
180
184
 
181
- ### 3. Run a scan
185
+ ### Windows — pip
186
+
187
+ Install with `pip` and run the tool as `py -m plexavo`:
188
+
189
+ ```powershell
190
+ py -m pip install --user plexavo
191
+ py -m plexavo
192
+ ```
193
+
194
+ `py -m plexavo` with no arguments opens the interactive menu, exactly
195
+ like the `plexavo` command does on macOS/Linux — flags are only for
196
+ scripting/CI.
197
+
198
+ > **Why not uv/pipx on Windows?** Both work by putting a small generated
199
+ > `plexavo.exe` launcher on your PATH. That launcher is unsigned, and
200
+ > Windows Smart App Control blocks unsigned executables it doesn't
201
+ > recognise — so `plexavo` can fail to start with a "can't confirm who
202
+ > published" message. `py -m plexavo` calls Python directly and never
203
+ > touches that launcher, so it always works. (If your machine doesn't
204
+ > enforce Smart App Control, `uv tool install plexavo` works here too.)
205
+
206
+ **AI-narrated explanations** (see [Cost](#cost)):
207
+
208
+ ```powershell
209
+ py -m pip install --user "plexavo[ai]"
210
+ ```
211
+
212
+ Updating or removing later:
213
+
214
+ ```powershell
215
+ py -m pip install --user --upgrade plexavo
216
+ py -m pip uninstall plexavo
217
+ ```
218
+
219
+ ### Optional: type `plexavo` instead of `py -m plexavo`
220
+
221
+ Add a shortcut to your PowerShell profile once:
222
+
223
+ ```powershell
224
+ Add-Content $PROFILE 'function plexavo { py -m plexavo @args }'
225
+ ```
226
+
227
+ Open a new terminal and `plexavo` then works just like it does on
228
+ macOS/Linux.
229
+
230
+ ### Run a scan
182
231
 
183
232
  ```bash
184
233
  plexavo scan --profile my-aws-profile --report-html report.html
185
234
  ```
186
235
 
236
+ (On Windows without the shortcut above: `py -m plexavo scan --profile
237
+ my-aws-profile --report-html report.html`.)
238
+
187
239
  No `--profile`? It uses your default profile / environment variables,
188
240
  same resolution order as the AWS CLI. No AI, no API key, no cost — and
189
241
  findings still come with free Next Step / Full Fix Detail guidance
@@ -213,19 +265,24 @@ plexavo scan --profile my-aws-profile --explain --report-html report.html --repo
213
265
 
214
266
  ### Alternative: pipx
215
267
 
216
- Already use [pipx](https://pipx.pypa.io/)? It works exactly the same way —
217
- its own isolated environment, one global command, no venv:
268
+ Already use [pipx](https://pipx.pypa.io/) on macOS/Linux? It works exactly
269
+ the same way as uv — its own isolated environment, one global command, no
270
+ venv:
218
271
 
219
272
  ```bash
220
273
  pipx install plexavo
221
274
  pipx install "plexavo[ai]" # with AI-narrated explanations
222
275
  ```
223
276
 
277
+ On Windows, pipx has the same Smart App Control caveat as uv (see the
278
+ Windows section above) — if the `plexavo` command is blocked, run
279
+ `py -m plexavo` instead, or use the `pip` install.
280
+
224
281
  ### Alternative: pip + venv
225
282
 
226
- Prefer to manage the environment yourself, or on a system without uv/pipx?
227
- Modern Python (PEP 668) blocks a plain `pip install` outside a venv on
228
- many systems, so create one first:
283
+ Want full isolation and prefer to manage the environment yourself? Modern
284
+ Python (PEP 668) blocks a plain `pip install` outside a venv on many
285
+ Linux and Homebrew setups, so create one first:
229
286
 
230
287
  ```bash
231
288
  python -m venv plexavo-env
@@ -238,7 +295,9 @@ pip install "plexavo[ai]" # with AI-narrated explanations
238
295
 
239
296
  You'll need to reactivate this venv (`plexavo-env\Scripts\activate` /
240
297
  `source plexavo-env/bin/activate`) every time you open a new terminal —
241
- `deactivate` exits it without uninstalling anything.
298
+ `deactivate` exits it without uninstalling anything. On Windows, run
299
+ `python -m plexavo` if the `plexavo` command is blocked by Smart App
300
+ Control.
242
301
 
243
302
  ### From source (for contributing, or trying an unreleased change)
244
303
 
@@ -257,6 +316,7 @@ plexavo/
257
316
  ├── findings.py # Finding data model, Severity enum
258
317
  ├── scoring.py # 0-100 score from a list of Findings
259
318
  ├── cli.py # `plexavo scan ...` entry point
319
+ ├── __main__.py # lets `python -m plexavo` run the CLI
260
320
  ├── checks/
261
321
  │ ├── iam.py # IAM-01 to IAM-06 (privilege escalation)
262
322
  │ ├── iam_hygiene.py # IAM-07 to IAM-14 (hygiene, cross-account trust)
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  pyproject.toml
4
4
  plexavo/__init__.py
5
+ plexavo/__main__.py
5
6
  plexavo/auth.py
6
7
  plexavo/aws_profile_setup.py
7
8
  plexavo/cli.py
@@ -34,6 +35,8 @@ plexavo/report/fonts/DejaVuSans.ttf
34
35
  plexavo/report/fonts/GEIST-FONT-LICENSE.txt
35
36
  plexavo/report/templates/report.html.j2
36
37
  tests/test_ai_narration_offline.py
38
+ tests/test_auth_offline.py
39
+ tests/test_cli_entrypoint.py
37
40
  tests/test_encryption_offline.py
38
41
  tests/test_iam_hygiene_offline.py
39
42
  tests/test_iam_offline.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plexavo"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  description = "Open-source AWS misconfiguration scanner — runs with your own local AWS credentials, nothing sent to anyone else."
9
9
  readme = "README.md"
10
10
  license = { text = "AGPL-3.0-or-later" }
@@ -0,0 +1,91 @@
1
+ """Offline tests for auth.py. No real AWS calls — boto3.Session.client is
2
+ monkeypatched to raise the errors AWS itself would raise, to prove
3
+ get_local_session converts them into a clear RuntimeError instead of
4
+ letting them crash the caller.
5
+
6
+ Run: python test_auth_offline.py
7
+ """
8
+
9
+ import boto3
10
+ from botocore.exceptions import ClientError, NoCredentialsError
11
+ from plexavo.auth import get_local_session
12
+
13
+ failures = 0
14
+
15
+
16
+ def assert_true(cond, msg):
17
+ global failures
18
+ status = "PASS" if cond else "FAIL"
19
+ print(f"[{status}] {msg}")
20
+ if not cond:
21
+ failures += 1
22
+
23
+
24
+ class _RaisingClient:
25
+ def __init__(self, exc):
26
+ self._exc = exc
27
+
28
+ def get_caller_identity(self):
29
+ raise self._exc
30
+
31
+
32
+ def _patch_client(monkeypatch_exc):
33
+ original_client = boto3.Session.client
34
+
35
+ def fake_client(self, service_name, *args, **kwargs):
36
+ if service_name == "sts":
37
+ return _RaisingClient(monkeypatch_exc)
38
+ return original_client(self, service_name, *args, **kwargs)
39
+
40
+ return original_client, fake_client
41
+
42
+
43
+ # --- InvalidClientTokenId (deactivated/deleted access key) surfaces as a clear RuntimeError ---
44
+ original_client, fake_client = _patch_client(
45
+ ClientError(
46
+ {"Error": {"Code": "InvalidClientTokenId", "Message": "The security token included in the request is invalid."}},
47
+ "GetCallerIdentity",
48
+ )
49
+ )
50
+ boto3.Session.region_name = property(lambda self: "us-east-1")
51
+ boto3.Session.client = fake_client
52
+ try:
53
+ raised = None
54
+ try:
55
+ # profile_name=None: boto3.Session() itself always succeeds
56
+ # regardless of what's in this machine's real ~/.aws config —
57
+ # only the (monkeypatched) sts call should fail.
58
+ get_local_session(profile_name=None)
59
+ except RuntimeError as e:
60
+ raised = e
61
+ except Exception as e:
62
+ raised = e
63
+
64
+ assert_true(isinstance(raised, RuntimeError), "ClientError (InvalidClientTokenId) is converted to RuntimeError, not left to crash")
65
+ assert_true(raised is not None and "InvalidClientTokenId" in str(raised), "RuntimeError message names the AWS error code")
66
+ finally:
67
+ boto3.Session.client = original_client
68
+
69
+
70
+ # --- NoCredentialsError still produces the pre-existing clear message ---
71
+ original_client, fake_client = _patch_client(NoCredentialsError())
72
+ boto3.Session.client = fake_client
73
+ try:
74
+ raised = None
75
+ try:
76
+ get_local_session(profile_name=None)
77
+ except RuntimeError as e:
78
+ raised = e
79
+ except Exception as e:
80
+ raised = e
81
+
82
+ assert_true(isinstance(raised, RuntimeError), "NoCredentialsError still converts to RuntimeError (regression check)")
83
+ assert_true(raised is not None and "No AWS credentials found" in str(raised), "NoCredentialsError message unchanged")
84
+ finally:
85
+ boto3.Session.client = original_client
86
+ del boto3.Session.region_name
87
+
88
+
89
+ print(f"\n{'ALL PASSED' if failures == 0 else f'{failures} FAILURE(S)'}")
90
+ import sys
91
+ sys.exit(1 if failures else 0)
@@ -0,0 +1,50 @@
1
+ """Checks that `python -m plexavo` works and is equivalent to the installed
2
+ `plexavo` command / `python -m plexavo.cli`.
3
+
4
+ This is the Windows install path (README Quick start): install with pip, run
5
+ via `py -m plexavo` instead of the Smart-App-Control-blocked `plexavo.exe`
6
+ launcher. If the `plexavo/__main__.py` shim regresses, that path breaks.
7
+
8
+ Run: python test_cli_entrypoint.py
9
+ """
10
+
11
+ import subprocess
12
+ import sys
13
+
14
+ failures = 0
15
+
16
+
17
+ def assert_true(cond, msg):
18
+ global failures
19
+ status = "PASS" if cond else "FAIL"
20
+ print(f"[{status}] {msg}")
21
+ if not cond:
22
+ failures += 1
23
+
24
+
25
+ def run(*args):
26
+ return subprocess.run(
27
+ [sys.executable, *args],
28
+ capture_output=True,
29
+ text=True,
30
+ )
31
+
32
+
33
+ print("=== python -m plexavo --version ===")
34
+ mod = run("-m", "plexavo", "--version")
35
+ assert_true(mod.returncode == 0, f"exits 0 (got {mod.returncode}, stderr: {mod.stderr!r})")
36
+ assert_true("plexavo" in (mod.stdout + mod.stderr).lower(), "prints the version string")
37
+
38
+ print("\n=== equivalent to python -m plexavo.cli ===")
39
+ cli = run("-m", "plexavo.cli", "--version")
40
+ assert_true(mod.stdout == cli.stdout, f"same --version output ({mod.stdout!r} vs {cli.stdout!r})")
41
+
42
+ print("\n=== python -m plexavo --help mentions the scan subcommand ===")
43
+ helptext = run("-m", "plexavo", "--help")
44
+ assert_true(helptext.returncode == 0, f"--help exits 0 (got {helptext.returncode})")
45
+ assert_true("scan" in helptext.stdout, "help lists the scan subcommand")
46
+ assert_true("usage: plexavo" in helptext.stdout,
47
+ "usage line shows 'plexavo', not '__main__.py'")
48
+
49
+ print(f"\n{'ALL PASSED' if failures == 0 else f'{failures} FAILURE(S)'}")
50
+ sys.exit(1 if failures else 0)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes