plexavo 0.2.6__tar.gz → 0.2.7__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.6 → plexavo-0.2.7}/PKG-INFO +11 -3
  2. {plexavo-0.2.6 → plexavo-0.2.7}/README.md +10 -2
  3. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/__init__.py +1 -1
  4. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/storage.py +35 -2
  5. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/cli.py +1 -1
  6. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/PKG-INFO +11 -3
  7. {plexavo-0.2.6 → plexavo-0.2.7}/pyproject.toml +1 -1
  8. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_storage_offline.py +28 -1
  9. {plexavo-0.2.6 → plexavo-0.2.7}/LICENSE +0 -0
  10. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/__main__.py +0 -0
  11. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/auth.py +0 -0
  12. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/aws_profile_setup.py +0 -0
  13. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/__init__.py +0 -0
  14. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/encryption.py +0 -0
  15. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/iam.py +0 -0
  16. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/iam_hygiene.py +0 -0
  17. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/logging.py +0 -0
  18. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/network.py +0 -0
  19. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/usage.py +0 -0
  20. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/findings.py +0 -0
  21. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/interactive.py +0 -0
  22. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/principals.py +0 -0
  23. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/__init__.py +0 -0
  24. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/ai_narration.py +0 -0
  25. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
  26. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
  27. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
  28. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
  29. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
  30. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/html_report.py +0 -0
  31. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/pdf.py +0 -0
  32. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/templates/report.html.j2 +0 -0
  33. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/scoring.py +0 -0
  34. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/SOURCES.txt +0 -0
  35. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/dependency_links.txt +0 -0
  36. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/entry_points.txt +0 -0
  37. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/requires.txt +0 -0
  38. {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/top_level.txt +0 -0
  39. {plexavo-0.2.6 → plexavo-0.2.7}/setup.cfg +0 -0
  40. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_ai_narration_offline.py +0 -0
  41. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_auth_offline.py +0 -0
  42. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_cli_entrypoint.py +0 -0
  43. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_encryption_offline.py +0 -0
  44. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_iam_hygiene_offline.py +0 -0
  45. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_iam_offline.py +0 -0
  46. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_logging_offline.py +0 -0
  47. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_network_offline.py +0 -0
  48. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_report_offline.py +0 -0
  49. {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_scoring.py +0 -0
  50. {plexavo-0.2.6 → plexavo-0.2.7}/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.6
3
+ Version: 0.2.7
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
@@ -53,14 +53,14 @@ can read, and it's entirely optional. See [Cost](#cost).
53
53
 
54
54
  ## What it checks
55
55
 
56
- 31 checks across 6 categories, run against real AWS accounts:
56
+ 32 checks across 6 categories, run against real AWS accounts:
57
57
 
58
58
  - **IAM**: privilege escalation paths, wildcard admin, cross-account
59
59
  trust, root usage, dormant credentials
60
60
  - **Network**: security groups and RDS instances exposed to the
61
61
  internet
62
62
  - **Storage**: public S3 buckets, via ACLs, bucket policies, or missing
63
- Block Public Access
63
+ Block Public Access; buckets with no access logging configured
64
64
  - **Encryption**: unencrypted EBS volumes, RDS instances, S3 buckets
65
65
  - **Logging**: CloudTrail coverage and encryption, GuardDuty status
66
66
  - **Usage**: permissions granted but never used, roles nobody has
@@ -167,6 +167,14 @@ uv pip install -e .
167
167
  See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the pattern used to add a
168
168
  new check.
169
169
 
170
+ ## Break Plexavo
171
+
172
+ Think you can make Plexavo miss something, or give confusing guidance?
173
+ [Report it](../../issues/new?template=break-plexavo.yml). Every
174
+ confirmed, genuinely new finding gets fixed and shipped, and you get a
175
+ permanent credit in the [Hall of Bugs](HALL_OF_BUGS.md). No bounty,
176
+ public credit only.
177
+
170
178
  ## Security
171
179
 
172
180
  Found a vulnerability in the tool itself, not a misconfiguration in your
@@ -24,14 +24,14 @@ can read, and it's entirely optional. See [Cost](#cost).
24
24
 
25
25
  ## What it checks
26
26
 
27
- 31 checks across 6 categories, run against real AWS accounts:
27
+ 32 checks across 6 categories, run against real AWS accounts:
28
28
 
29
29
  - **IAM**: privilege escalation paths, wildcard admin, cross-account
30
30
  trust, root usage, dormant credentials
31
31
  - **Network**: security groups and RDS instances exposed to the
32
32
  internet
33
33
  - **Storage**: public S3 buckets, via ACLs, bucket policies, or missing
34
- Block Public Access
34
+ Block Public Access; buckets with no access logging configured
35
35
  - **Encryption**: unencrypted EBS volumes, RDS instances, S3 buckets
36
36
  - **Logging**: CloudTrail coverage and encryption, GuardDuty status
37
37
  - **Usage**: permissions granted but never used, roles nobody has
@@ -138,6 +138,14 @@ uv pip install -e .
138
138
  See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the pattern used to add a
139
139
  new check.
140
140
 
141
+ ## Break Plexavo
142
+
143
+ Think you can make Plexavo miss something, or give confusing guidance?
144
+ [Report it](../../issues/new?template=break-plexavo.yml). Every
145
+ confirmed, genuinely new finding gets fixed and shipped, and you get a
146
+ permanent credit in the [Hall of Bugs](HALL_OF_BUGS.md). No bounty,
147
+ public credit only.
148
+
141
149
  ## Security
142
150
 
143
151
  Found a vulnerability in the tool itself, not a misconfiguration in your
@@ -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.6"
7
+ __version__ = "0.2.7"
@@ -1,4 +1,5 @@
1
- """Category 3: Storage Exposure — checks 19-21, all Critical.
1
+ """Category 3: Storage Exposure — checks 19-22. 19-21 are Critical
2
+ (public exposure); 22 is Medium (a visibility gap, not exposure itself).
2
3
 
3
4
  Scope note: check_19 checks BUCKET-level PublicAccessBlock only, per the
4
5
  blueprint's literal spec (GetBucketPublicAccessBlock). It does NOT check
@@ -127,8 +128,39 @@ def check_21_public_acl(s3, bucket_names: list) -> list[Finding]:
127
128
  return findings
128
129
 
129
130
 
131
+ def check_22_access_logging_disabled(s3, bucket_names: list) -> list[Finding]:
132
+ """STOR-22: bucket has no server access logging configured.
133
+
134
+ GetBucketLogging always returns 200, even when logging was never set
135
+ up — the response body simply omits the 'LoggingEnabled' key in that
136
+ case. Unlike check_19's PublicAccessBlock, there's no distinct
137
+ "not configured" exception to catch here, confirmed against the real
138
+ S3 service model before writing this.
139
+
140
+ Medium, not Critical: this is a forensics gap, not an exposure by
141
+ itself — a bucket with logging off isn't more reachable than one with
142
+ it on, it's just unreviewable after the fact if something happens."""
143
+ findings = []
144
+ for name in bucket_names:
145
+ config = s3.get_bucket_logging(Bucket=name)
146
+ if "LoggingEnabled" in config:
147
+ continue
148
+ findings.append(Finding(
149
+ check_id="STOR-22",
150
+ title="S3 Bucket Access Logging Not Enabled",
151
+ severity=Severity.MEDIUM,
152
+ resource_arn=f"arn:aws:s3:::{name}",
153
+ raw_detail=f"Bucket '{name}' has no server access logging configured. "
154
+ f"If this bucket is ever accessed, modified, or exfiltrated "
155
+ f"from, there is no record of who did what — investigation "
156
+ f"after the fact isn't possible.",
157
+ account_context=f"bucket={name}",
158
+ ))
159
+ return findings
160
+
161
+
130
162
  def run_all(session) -> list[Finding]:
131
- """Run STOR-19 through STOR-21 against every bucket in the account —
163
+ """Run STOR-19 through STOR-22 against every bucket in the account —
132
164
  not just testbed buckets. A real scan must check everything."""
133
165
  s3 = session.client("s3")
134
166
  bucket_names = list_buckets(s3)
@@ -136,4 +168,5 @@ def run_all(session) -> list[Finding]:
136
168
  findings += check_19_missing_public_access_block(s3, bucket_names)
137
169
  findings += check_20_public_bucket_policy(s3, bucket_names)
138
170
  findings += check_21_public_acl(s3, bucket_names)
171
+ findings += check_22_access_logging_disabled(s3, bucket_names)
139
172
  return findings
@@ -155,7 +155,7 @@ def _run_scan(args) -> None:
155
155
  stage("Running checks NET-01 through NET-04")
156
156
  findings += network_checks.run_all(session)
157
157
 
158
- stage("Running checks STOR-19 through STOR-21")
158
+ stage("Running checks STOR-19 through STOR-22")
159
159
  findings += storage_checks.run_all(session)
160
160
 
161
161
  stage("Running checks ENC-29 through ENC-31")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plexavo
3
- Version: 0.2.6
3
+ Version: 0.2.7
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
@@ -53,14 +53,14 @@ can read, and it's entirely optional. See [Cost](#cost).
53
53
 
54
54
  ## What it checks
55
55
 
56
- 31 checks across 6 categories, run against real AWS accounts:
56
+ 32 checks across 6 categories, run against real AWS accounts:
57
57
 
58
58
  - **IAM**: privilege escalation paths, wildcard admin, cross-account
59
59
  trust, root usage, dormant credentials
60
60
  - **Network**: security groups and RDS instances exposed to the
61
61
  internet
62
62
  - **Storage**: public S3 buckets, via ACLs, bucket policies, or missing
63
- Block Public Access
63
+ Block Public Access; buckets with no access logging configured
64
64
  - **Encryption**: unencrypted EBS volumes, RDS instances, S3 buckets
65
65
  - **Logging**: CloudTrail coverage and encryption, GuardDuty status
66
66
  - **Usage**: permissions granted but never used, roles nobody has
@@ -167,6 +167,14 @@ uv pip install -e .
167
167
  See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the pattern used to add a
168
168
  new check.
169
169
 
170
+ ## Break Plexavo
171
+
172
+ Think you can make Plexavo miss something, or give confusing guidance?
173
+ [Report it](../../issues/new?template=break-plexavo.yml). Every
174
+ confirmed, genuinely new finding gets fixed and shipped, and you get a
175
+ permanent credit in the [Hall of Bugs](HALL_OF_BUGS.md). No bounty,
176
+ public credit only.
177
+
170
178
  ## Security
171
179
 
172
180
  Found a vulnerability in the tool itself, not a misconfiguration in your
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "plexavo"
7
- version = "0.2.6"
7
+ version = "0.2.7"
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" }
@@ -12,11 +12,12 @@ from plexavo.checks import storage
12
12
 
13
13
 
14
14
  class FakeS3:
15
- def __init__(self, buckets, pab=None, policies=None, acls=None):
15
+ def __init__(self, buckets, pab=None, policies=None, acls=None, logging=None):
16
16
  self._buckets = buckets
17
17
  self._pab = pab or {}
18
18
  self._policies = policies or {}
19
19
  self._acls = acls or {}
20
+ self._logging = logging or {}
20
21
 
21
22
  def list_buckets(self):
22
23
  return {"Buckets": [{"Name": b} for b in self._buckets]}
@@ -34,6 +35,13 @@ class FakeS3:
34
35
  def get_bucket_acl(self, Bucket):
35
36
  return self._acls.get(Bucket, {"Grants": []})
36
37
 
38
+ def get_bucket_logging(self, Bucket):
39
+ # Real S3 behavior: always 200, 'LoggingEnabled' simply absent when
40
+ # logging was never configured — never raises.
41
+ if Bucket in self._logging:
42
+ return {"LoggingEnabled": self._logging[Bucket]}
43
+ return {}
44
+
37
45
 
38
46
  FULL_PAB = {"BlockPublicAcls": True, "IgnorePublicAcls": True, "BlockPublicPolicy": True, "RestrictPublicBuckets": True}
39
47
 
@@ -53,6 +61,7 @@ def run_checks(s3, buckets):
53
61
  findings += storage.check_19_missing_public_access_block(s3, buckets)
54
62
  findings += storage.check_20_public_bucket_policy(s3, buckets)
55
63
  findings += storage.check_21_public_acl(s3, buckets)
64
+ findings += storage.check_22_access_logging_disabled(s3, buckets)
56
65
  return findings
57
66
 
58
67
 
@@ -114,5 +123,23 @@ s3 = FakeS3(["owner-only-bucket"], pab={"owner-only-bucket": FULL_PAB}, acls={
114
123
  findings = run_checks(s3, ["owner-only-bucket"])
115
124
  assert_true(not any(f.check_id == "STOR-21" for f in findings), "Does NOT fire on an owner-only ACL grant")
116
125
 
126
+ print("\n=== STOR-22: bucket has no access logging configured ===")
127
+ s3 = FakeS3(["no-logging-bucket"], pab={"no-logging-bucket": FULL_PAB})
128
+ findings = run_checks(s3, ["no-logging-bucket"])
129
+ assert_true(any(f.check_id == "STOR-22" for f in findings), "Fires when access logging was never configured")
130
+
131
+ print("\n=== FALSE POSITIVE GUARD: STOR-22 does not fire when access logging is enabled ===")
132
+ s3 = FakeS3(["logged-bucket"], pab={"logged-bucket": FULL_PAB},
133
+ logging={"logged-bucket": {"TargetBucket": "log-archive", "TargetPrefix": "logged-bucket/"}})
134
+ findings = run_checks(s3, ["logged-bucket"])
135
+ assert_true(not any(f.check_id == "STOR-22" for f in findings), "Does NOT fire when LoggingEnabled is present")
136
+
137
+ print("\n=== REGRESSION: reported bug (Hall of Bugs #1, found by ThePettyReviewer) — a bucket with logging genuinely off must be flagged ===")
138
+ s3 = FakeS3(["reported-bucket"], pab={"reported-bucket": FULL_PAB})
139
+ findings = run_checks(s3, ["reported-bucket"])
140
+ matched = [f for f in findings if f.check_id == "STOR-22"]
141
+ assert_true(bool(matched), "STOR-22 fires on a bucket with logging off (previously: no check existed at all, so nothing ever fired)")
142
+ assert_true(bool(matched) and matched[0].severity.value == "Medium", "Severity is Medium — a visibility gap, not exposure like STOR-19/20/21")
143
+
117
144
  print(f"\n{'ALL PASSED' if failures == 0 else f'{failures} FAILURE(S)'}")
118
145
  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
File without changes
File without changes