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.
- {plexavo-0.2.6 → plexavo-0.2.7}/PKG-INFO +11 -3
- {plexavo-0.2.6 → plexavo-0.2.7}/README.md +10 -2
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/__init__.py +1 -1
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/storage.py +35 -2
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/cli.py +1 -1
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/PKG-INFO +11 -3
- {plexavo-0.2.6 → plexavo-0.2.7}/pyproject.toml +1 -1
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_storage_offline.py +28 -1
- {plexavo-0.2.6 → plexavo-0.2.7}/LICENSE +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/__main__.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/auth.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/aws_profile_setup.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/__init__.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/encryption.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/iam.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/iam_hygiene.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/logging.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/network.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/checks/usage.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/findings.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/interactive.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/principals.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/__init__.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/ai_narration.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-Bold.ttf +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-BoldOblique.ttf +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans-Oblique.ttf +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/DejaVuSans.ttf +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/fonts/GEIST-FONT-LICENSE.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/html_report.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/pdf.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/report/templates/report.html.j2 +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo/scoring.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/SOURCES.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/dependency_links.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/entry_points.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/requires.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/plexavo.egg-info/top_level.txt +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/setup.cfg +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_ai_narration_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_auth_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_cli_entrypoint.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_encryption_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_iam_hygiene_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_iam_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_logging_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_network_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_report_offline.py +0 -0
- {plexavo-0.2.6 → plexavo-0.2.7}/tests/test_scoring.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""Category 3: Storage Exposure — checks 19-21
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|