cloud-audit 2.2.0__tar.gz → 2.2.1__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.
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/CHANGELOG.md +41 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/PKG-INFO +1 -1
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/pyproject.toml +1 -1
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/ses_phishing.py +57 -46
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/trufflehog_ua.py +30 -15
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_ses_phishing.py +96 -32
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_trufflehog_ua.py +18 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.cloud-audit.example.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/FUNDING.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/dependabot.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/workflows/ci.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/workflows/docs.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/workflows/example-scan.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.github/workflows/release.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.gitignore +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.mcp.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/.pre-commit-hooks.yaml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/CODEOWNERS +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/CODE_OF_CONDUCT.md +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/CONTRIBUTING.md +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/Dockerfile +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/LICENSE +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/Makefile +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/README.md +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/ROADMAP.md +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/SECURITY.md +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/action.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/assets/demo.gif +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/assets/logo-nobg.png +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/assets/logo.png +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/assets/report-preview.png +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/assets/social-preview.png +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/examples/daily-scan-with-diff.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/examples/github-actions.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/examples/post-deploy-scan.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/mkdocs.yml +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/overrides/main.html +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/scripts/generate_demo_gif.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/scripts/generate_report_screenshot.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/server.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/__main__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/cli.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/engine.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/bsi_c5_2020.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/cis_aws_v3.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/hipaa_security.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/iso27001_2022.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/nis2_directive.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/soc2_type2.json +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/config.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/correlate.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/cost_model.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/diff.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/history.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/mcp_server.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/models.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/account.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/backup.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/bedrock.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/cloudtrail.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/cloudwatch.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/config_.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/ec2.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/ecs.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/efs.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/eip.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/guardduty.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/iam.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/inspector.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/kms.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/lambda_.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/rds.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/s3.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/sagemaker.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/secrets.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/securityhub.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/ssm.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/vpc.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/checks/waf.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/iam_analyzer.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/iam_trust_graph.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/provider.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/cloudtrail_tampering.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/cryptomining_role.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/datazone_overgrant.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/lambda_function_url.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/mmdsv1_in_use.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/quarantine_policy.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/roles_anywhere_abuse.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/whoami_confusion.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/base.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/py.typed +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/compliance_html.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/compliance_markdown.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/diff_markdown.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/html.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/markdown.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/sarif.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/reports/templates/report.html.j2 +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/root_cause.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/scanner.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/simulate.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_bedrock.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_cis_checks.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_cloudtrail.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_cloudwatch.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_config.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_ec2.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_ecs.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_eip.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_guardduty.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_iam.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_iam_analyzer.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_iam_trust_graph.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_kms.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_lambda.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_rds.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_s3.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_sagemaker.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_secrets.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_ssm.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/test_vpc.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/__init__.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_cloudtrail_tampering.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_cryptomining_role.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_datazone_overgrant.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_lambda_function_url.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_mmdsv1_in_use.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_quarantine_policy.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_roles_anywhere_abuse.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/aws/threat_feed/test_whoami_confusion.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/conftest.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_cli.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_cli_scan.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_compliance_frameworks.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_config.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_correlate.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_cost_model.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_diff.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_history.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_html.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_markdown.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_mcp_server.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_models.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_provider.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_root_cause.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_sarif.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_scanner.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_simulate.py +0 -0
- {cloud_audit-2.2.0 → cloud_audit-2.2.1}/tests/test_soc2_framework.py +0 -0
|
@@ -7,6 +7,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [2.2.1] - 2026-05-12
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **TF-001 (SES phishing setup)** - severity escalation logic rewritten.
|
|
15
|
+
HIGH now requires BOTH out-of-sandbox AND a burst of >=2 recent
|
|
16
|
+
identity verifications in the same account scan. The previous
|
|
17
|
+
"email identity without matching domain" escalation has been removed:
|
|
18
|
+
it modeled the wrong attacker behaviour. Wiz's September 2025 research
|
|
19
|
+
documented attackers *"adding multiple domains as verified identities
|
|
20
|
+
using the CreateEmailIdentity API"* in quick succession - a burst
|
|
21
|
+
pattern, not a single typosquat email. The new logic matches what
|
|
22
|
+
the source incident actually documented.
|
|
23
|
+
|
|
24
|
+
- **TF-004 (leaked-creds scanner UA)** - removed `cloudgrappler` and
|
|
25
|
+
`detention-dodger` from the user-agent signature list. Both are
|
|
26
|
+
Permiso DEFENSIVE tools - their UA appearing in CloudTrail means a
|
|
27
|
+
defender is running them against the account, not that the account
|
|
28
|
+
is under attack. The detector now only matches OFFENSIVE scanner
|
|
29
|
+
signatures (`trufflehog`, `gitleaks`, `noseyparker`, `secretscanner`).
|
|
30
|
+
Module docstring updated with an explicit detection caveat: scanners
|
|
31
|
+
using stock AWS SDK / boto3 / aws-cli default user-agents look
|
|
32
|
+
identical to legitimate traffic and will not trigger this pattern.
|
|
33
|
+
|
|
34
|
+
- **TF-004 references** - replaced a fabricated TruffleHog blog URL in
|
|
35
|
+
the references list with the verified BleepingComputer / Kaspersky
|
|
36
|
+
May 2026 SES abuse coverage and the official TruffleHog GitHub repo.
|
|
37
|
+
|
|
38
|
+
### Tests
|
|
39
|
+
|
|
40
|
+
- 742 -> 747 (+5 net). New regression tests:
|
|
41
|
+
- `test_email_no_matching_domain_does_not_escalate` (TF-001) proves
|
|
42
|
+
the removed typosquat heuristic does not return.
|
|
43
|
+
- `test_burst_out_of_sandbox_escalates_to_high` and
|
|
44
|
+
`test_burst_in_sandbox_stays_medium` cover the new escalation rule.
|
|
45
|
+
- `test_burst_only_counts_recent_identities` verifies the burst
|
|
46
|
+
counter respects the 14-day window.
|
|
47
|
+
- `test_cloudgrappler_ua_not_flagged` and
|
|
48
|
+
`test_detention_dodger_ua_not_flagged` (TF-004) prove defensive
|
|
49
|
+
tools are now excluded.
|
|
50
|
+
|
|
10
51
|
## [2.2.0] - 2026-05-12
|
|
11
52
|
|
|
12
53
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloud-audit
|
|
3
|
-
Version: 2.2.
|
|
3
|
+
Version: 2.2.1
|
|
4
4
|
Summary: Open-source AWS security scanner. Threat Feed v1 (10 active-abuse patterns from 2025-2026 incidents), 64 IAM escalation methods, What-If simulator, security trends, AI-SPM (Bedrock/SageMaker), 6 compliance frameworks, 31 attack chain rules, breach cost estimation, and MCP server. Every finding includes CLI + Terraform remediation.
|
|
5
5
|
Project-URL: Homepage, https://haitmg.pl/cloud-audit/
|
|
6
6
|
Project-URL: Documentation, https://haitmg.pl/cloud-audit/
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "cloud-audit"
|
|
7
|
-
version = "2.2.
|
|
7
|
+
version = "2.2.1"
|
|
8
8
|
description = "Open-source AWS security scanner. Threat Feed v1 (10 active-abuse patterns from 2025-2026 incidents), 64 IAM escalation methods, What-If simulator, security trends, AI-SPM (Bedrock/SageMaker), 6 compliance frameworks, 31 attack chain rules, breach cost estimation, and MCP server. Every finding includes CLI + Terraform remediation."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/ses_phishing.py
RENAMED
|
@@ -1,22 +1,28 @@
|
|
|
1
1
|
"""TF-001: SES phishing setup precursors.
|
|
2
2
|
|
|
3
|
-
The SES abuse campaigns documented by Wiz (
|
|
3
|
+
The SES abuse campaigns documented by Wiz (September 2025) and BleepingComputer
|
|
4
4
|
(May 2026) follow a consistent pattern: an attacker compromises AWS
|
|
5
|
-
credentials, calls GetSendQuota to confirm out-of-sandbox status,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
credentials, calls GetSendQuota to confirm out-of-sandbox status, then -
|
|
6
|
+
per Wiz's direct quote - "adds multiple domains as verified identities
|
|
7
|
+
using the CreateEmailIdentity API" so they can blast phishing through SES
|
|
8
|
+
from common prefixes (admin@, billing@, sales@, noreply@) on those domains.
|
|
9
9
|
|
|
10
10
|
We surface the precursor that is visible from the control plane: SES email
|
|
11
|
-
identities verified RECENTLY
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
-
|
|
11
|
+
or domain identities verified RECENTLY. We do not have visibility into
|
|
12
|
+
whether the identity is benign (legitimate marketing setup) or malicious -
|
|
13
|
+
we flag at MEDIUM and let the operator triage.
|
|
14
|
+
|
|
15
|
+
Severity escalates to HIGH when BOTH of these are true:
|
|
16
|
+
- The account has production sending enabled (out of SES sandbox), so
|
|
17
|
+
abuse would reach external recipients without the per-recipient
|
|
18
|
+
verify-first restriction.
|
|
19
|
+
- The account has TWO OR MORE identities verified in the recent window
|
|
20
|
+
(the "burst" pattern Wiz documented - attackers add multiple domains
|
|
21
|
+
in quick succession, not one).
|
|
22
|
+
|
|
23
|
+
Severity does NOT escalate on "email identity without matching domain"
|
|
24
|
+
(an earlier draft of this detector used that signal - it was wrong;
|
|
25
|
+
Wiz documented attackers verifying domains, not single-email typosquats).
|
|
20
26
|
|
|
21
27
|
References:
|
|
22
28
|
- https://www.wiz.io/blog/wiz-discovers-cloud-email-abuse-campaign
|
|
@@ -51,20 +57,25 @@ _RECENT_DAYS = 14
|
|
|
51
57
|
14 days catches campaign-style bursts without flagging legitimate identities
|
|
52
58
|
that have been around for months."""
|
|
53
59
|
|
|
60
|
+
_BURST_THRESHOLD = 2
|
|
61
|
+
"""Number of recent verifications that triggers HIGH severity escalation.
|
|
62
|
+
|
|
63
|
+
Wiz documented attackers "adding multiple domains as verified identities"
|
|
64
|
+
in quick succession. Two or more recent verifications in the same account
|
|
65
|
+
matches that burst signature."""
|
|
66
|
+
|
|
54
67
|
|
|
55
68
|
def _build_finding(
|
|
56
69
|
identity_name: str,
|
|
57
70
|
identity_type: str,
|
|
58
71
|
region: str,
|
|
59
|
-
created_at: datetime
|
|
72
|
+
created_at: datetime,
|
|
60
73
|
out_of_sandbox: bool,
|
|
61
|
-
|
|
74
|
+
recent_burst_count: int,
|
|
62
75
|
) -> Finding:
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
age_days = (datetime.now(timezone.utc) - created_at).days
|
|
67
|
-
age_str = f"verified {age_days} days ago"
|
|
76
|
+
is_burst = recent_burst_count >= _BURST_THRESHOLD
|
|
77
|
+
severity = Severity.HIGH if (out_of_sandbox and is_burst) else PATTERN_SEVERITY
|
|
78
|
+
age_days = (datetime.now(timezone.utc) - created_at).days
|
|
68
79
|
|
|
69
80
|
sandbox_note = (
|
|
70
81
|
" The account has production sending enabled (out of SES sandbox), so messages "
|
|
@@ -73,11 +84,11 @@ def _build_finding(
|
|
|
73
84
|
if out_of_sandbox
|
|
74
85
|
else " The account is still in SES sandbox - external impact limited."
|
|
75
86
|
)
|
|
76
|
-
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
if
|
|
87
|
+
burst_note = (
|
|
88
|
+
f" {recent_burst_count} identities have been verified in this account in the last "
|
|
89
|
+
f"{_RECENT_DAYS} days - a burst pattern matching Wiz's documented incident where "
|
|
90
|
+
"attackers added multiple domains as verified identities in quick succession."
|
|
91
|
+
if is_burst
|
|
81
92
|
else ""
|
|
82
93
|
)
|
|
83
94
|
return Finding(
|
|
@@ -89,12 +100,12 @@ def _build_finding(
|
|
|
89
100
|
resource_id=f"ses:{region}:{identity_name}",
|
|
90
101
|
region=region,
|
|
91
102
|
description=(
|
|
92
|
-
f"SES identity '{identity_name}'
|
|
93
|
-
"
|
|
94
|
-
"consistent SES abuse pattern in stolen-credential incidents:
|
|
95
|
-
"
|
|
96
|
-
f"reputation.{sandbox_note}{
|
|
97
|
-
"by an authorized operator."
|
|
103
|
+
f"SES identity '{identity_name}' was verified {age_days} days ago in {region}. "
|
|
104
|
+
"The Wiz September 2025 research and BleepingComputer May 2026 follow-up "
|
|
105
|
+
"document a consistent SES abuse pattern in stolen-credential incidents: "
|
|
106
|
+
"attackers verify SES identities and blast phishing through SES so messages "
|
|
107
|
+
f"carry AWS IP reputation.{sandbox_note}{burst_note} Confirm the verification "
|
|
108
|
+
"was performed by an authorized operator."
|
|
98
109
|
),
|
|
99
110
|
recommendation=(
|
|
100
111
|
"(1) Confirm the verification was authorized by your team. (2) If unexpected, "
|
|
@@ -163,39 +174,39 @@ def _scan_region(provider: AWSProvider, region: str) -> tuple[int, list[Finding]
|
|
|
163
174
|
return 0, []
|
|
164
175
|
raise
|
|
165
176
|
|
|
166
|
-
# Build set of verified domains (for typosquat detection)
|
|
167
|
-
verified_domains = {i.get("IdentityName", "") for i in identities if i.get("IdentityType") == "DOMAIN"}
|
|
168
|
-
|
|
169
177
|
cutoff = datetime.now(timezone.utc) - timedelta(days=_RECENT_DAYS)
|
|
170
178
|
|
|
179
|
+
# First pass: collect every recently-verified identity with its CreatedTimestamp.
|
|
180
|
+
# The burst count is the SAME for every finding emitted from a single account scan -
|
|
181
|
+
# it reflects the total recent activity in the SES inventory, which is the signature
|
|
182
|
+
# Wiz documented (attackers add multiple identities, not one). We must count first
|
|
183
|
+
# so that the count is correct on every emitted finding.
|
|
184
|
+
recent_entries: list[tuple[str, datetime]] = []
|
|
171
185
|
for identity in identities:
|
|
172
186
|
scanned += 1
|
|
173
187
|
name = identity.get("IdentityName", "")
|
|
174
188
|
if not name:
|
|
175
189
|
continue
|
|
176
190
|
if not identity.get("VerifiedForSendingStatus", False):
|
|
177
|
-
continue
|
|
178
|
-
|
|
179
|
-
# Look up details for created timestamp
|
|
191
|
+
continue
|
|
180
192
|
try:
|
|
181
193
|
detail = ses.get_email_identity(EmailIdentity=name)
|
|
182
194
|
except Exception:
|
|
183
195
|
continue
|
|
184
196
|
created_at = detail.get("CreatedTimestamp")
|
|
185
197
|
if not isinstance(created_at, datetime):
|
|
186
|
-
# Some boto3 versions return raw datetime, others may return a string.
|
|
187
|
-
# If we can't parse, skip rather than over-flag.
|
|
188
198
|
continue
|
|
189
199
|
if created_at.tzinfo is None:
|
|
190
200
|
created_at = created_at.replace(tzinfo=timezone.utc)
|
|
191
201
|
if created_at < cutoff:
|
|
192
|
-
continue
|
|
202
|
+
continue
|
|
203
|
+
recent_entries.append((name, created_at))
|
|
193
204
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
is_email_no_match = domain_part not in verified_domains
|
|
205
|
+
recent_burst_count = len(recent_entries)
|
|
206
|
+
if recent_burst_count == 0:
|
|
207
|
+
return scanned, []
|
|
198
208
|
|
|
209
|
+
for name, created_at in recent_entries:
|
|
199
210
|
findings.append(
|
|
200
211
|
_build_finding(
|
|
201
212
|
identity_name=name,
|
|
@@ -203,7 +214,7 @@ def _scan_region(provider: AWSProvider, region: str) -> tuple[int, list[Finding]
|
|
|
203
214
|
region=region,
|
|
204
215
|
created_at=created_at,
|
|
205
216
|
out_of_sandbox=out_of_sandbox,
|
|
206
|
-
|
|
217
|
+
recent_burst_count=recent_burst_count,
|
|
207
218
|
)
|
|
208
219
|
)
|
|
209
220
|
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/trufflehog_ua.py
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""TF-004:
|
|
1
|
+
"""TF-004: leaked-creds-discovery scanner user-agent in CloudTrail.
|
|
2
2
|
|
|
3
3
|
When credentials leak to public sources (GitHub commits, paste sites,
|
|
4
4
|
shipping logs), automated discovery tools immediately scrape and validate
|
|
@@ -6,19 +6,32 @@ them. The most common validation is `aws sts get-caller-identity` because
|
|
|
6
6
|
it requires zero permissions to call but tells the attacker who the
|
|
7
7
|
credentials belong to.
|
|
8
8
|
|
|
9
|
-
TruffleHog
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
TruffleHog, gitleaks, NoseyParker and similar OFFENSIVE leaked-credentials
|
|
10
|
+
scanners *can* leave a recognisable user-agent substring when their HTTP
|
|
11
|
+
clients are invoked with a custom UA (e.g. TruffleHog's `--user-agent-suffix`
|
|
12
|
+
flag). TF-004 catches those cases.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
DETECTION LIMITATION (be honest with yourself): when these scanners use the
|
|
15
|
+
stock AWS SDK / boto3 / aws-cli default user-agent, their calls look
|
|
16
|
+
identical to legitimate traffic and TF-004 will NOT flag them. Absence of a
|
|
17
|
+
hit does not mean nothing scanned your keys; presence of a hit is a strong
|
|
18
|
+
signal that something did. Pair this with TF-003 (quarantine policy) and
|
|
19
|
+
behavioural CloudTrail monitoring (e.g. Prowler's
|
|
20
|
+
`cloudtrail_threat_detection_enumeration`) for fuller coverage.
|
|
21
|
+
|
|
22
|
+
We deliberately exclude DEFENSIVE tools like Permiso's CloudGrappler and
|
|
23
|
+
DetentionDodger from the signature list - those running against your account
|
|
24
|
+
are your own (or your auditor's) and should not generate findings.
|
|
25
|
+
|
|
26
|
+
Seeing such a UA in CloudTrail = your credentials were likely validated by
|
|
27
|
+
an external offensive scanner = treat as confirmed exposure. We surface
|
|
28
|
+
CRITICAL because the next step in the attack chain is typically
|
|
29
|
+
CreateFunction / RunInstances / VerifyEmailIdentity within minutes.
|
|
18
30
|
|
|
19
31
|
References:
|
|
20
|
-
- https://
|
|
32
|
+
- https://www.bleepingcomputer.com/news/security/researchers-report-amazon-ses-abused-in-phishing-to-evade-detection/
|
|
21
33
|
- https://permiso.io/blog/introducing-detention-dodger
|
|
34
|
+
- https://github.com/trufflesecurity/trufflehog
|
|
22
35
|
- https://github.com/Cybr-Inc/fwdcloudsec-2025-summaries
|
|
23
36
|
"""
|
|
24
37
|
|
|
@@ -40,17 +53,19 @@ PATTERN_SEVERITY = Severity.CRITICAL
|
|
|
40
53
|
DOC_URL = "https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-record-contents.html"
|
|
41
54
|
|
|
42
55
|
_REFERENCES = [
|
|
43
|
-
"https://
|
|
56
|
+
"https://www.bleepingcomputer.com/news/security/researchers-report-amazon-ses-abused-in-phishing-to-evade-detection/",
|
|
44
57
|
"https://permiso.io/blog/introducing-detention-dodger",
|
|
58
|
+
"https://github.com/trufflesecurity/trufflehog",
|
|
45
59
|
]
|
|
46
60
|
|
|
47
|
-
# Substrings (case-insensitive) in CloudTrail userAgent that indicate
|
|
48
|
-
#
|
|
61
|
+
# Substrings (case-insensitive) in CloudTrail userAgent that indicate an
|
|
62
|
+
# OFFENSIVE leaked-credentials discovery scanner. DEFENSIVE tools
|
|
63
|
+
# (CloudGrappler, DetentionDodger) are intentionally excluded - operators
|
|
64
|
+
# running them against their own account are not the threat. Easy to extend
|
|
65
|
+
# as new offensive tools surface; resist the urge to add defensive ones.
|
|
49
66
|
_SCANNER_UA_SIGNATURES = (
|
|
50
67
|
"trufflehog",
|
|
51
68
|
"gitleaks",
|
|
52
|
-
"cloudgrappler",
|
|
53
|
-
"detention-dodger",
|
|
54
69
|
"noseyparker",
|
|
55
70
|
"secretscanner",
|
|
56
71
|
)
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
"""Tests for TF-001: SES phishing setup precursor detector.
|
|
1
|
+
"""Tests for TF-001: SES phishing setup precursor detector.
|
|
2
|
+
|
|
3
|
+
v2.2.1: severity escalation rewritten. HIGH now requires BOTH out-of-sandbox
|
|
4
|
+
AND a burst of >=2 recent verifications in the same account scan (matches
|
|
5
|
+
Wiz's documented "multiple domains" pattern). The earlier "email identity
|
|
6
|
+
without matching domain" signal was removed - it pointed at the wrong
|
|
7
|
+
attacker behaviour (Wiz documented attackers adding domains, not single
|
|
8
|
+
typosquats).
|
|
9
|
+
"""
|
|
2
10
|
|
|
3
11
|
from __future__ import annotations
|
|
4
12
|
|
|
@@ -49,6 +57,8 @@ def _identity(name: str, identity_type: str = "EMAIL_ADDRESS", verified: bool =
|
|
|
49
57
|
|
|
50
58
|
|
|
51
59
|
# -----------------------------------------------------------------------------
|
|
60
|
+
# Detection tests
|
|
61
|
+
# -----------------------------------------------------------------------------
|
|
52
62
|
|
|
53
63
|
|
|
54
64
|
def test_no_identities_no_findings() -> None:
|
|
@@ -59,7 +69,7 @@ def test_no_identities_no_findings() -> None:
|
|
|
59
69
|
|
|
60
70
|
|
|
61
71
|
def test_old_identity_not_flagged() -> None:
|
|
62
|
-
"""Identity verified 60 days ago =
|
|
72
|
+
"""Identity verified 60 days ago = outside the recent window, no flag."""
|
|
63
73
|
name = "newsletter@company.example"
|
|
64
74
|
ses = _ses_client(
|
|
65
75
|
out_of_sandbox=True,
|
|
@@ -70,11 +80,11 @@ def test_old_identity_not_flagged() -> None:
|
|
|
70
80
|
assert result.findings == []
|
|
71
81
|
|
|
72
82
|
|
|
73
|
-
def
|
|
74
|
-
"""
|
|
75
|
-
name = "
|
|
83
|
+
def test_single_recent_identity_out_of_sandbox_medium() -> None:
|
|
84
|
+
"""One recent identity, even with production sending, is MEDIUM - not a burst yet."""
|
|
85
|
+
name = "team@trusted.example"
|
|
76
86
|
ses = _ses_client(
|
|
77
|
-
out_of_sandbox=
|
|
87
|
+
out_of_sandbox=True,
|
|
78
88
|
identities=[_identity(name)],
|
|
79
89
|
details={name: {"CreatedTimestamp": _now() - timedelta(days=2)}},
|
|
80
90
|
)
|
|
@@ -84,45 +94,79 @@ def test_recent_identity_in_sandbox_medium() -> None:
|
|
|
84
94
|
assert f.severity == Severity.MEDIUM
|
|
85
95
|
assert f.category == Category.THREAT
|
|
86
96
|
assert f.threat_pattern_id == ses_phishing.PATTERN_ID
|
|
87
|
-
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_single_recent_identity_in_sandbox_medium() -> None:
|
|
100
|
+
"""Single recent + sandbox = MEDIUM (limited blast radius)."""
|
|
101
|
+
name = "test@dev.example"
|
|
102
|
+
ses = _ses_client(
|
|
103
|
+
out_of_sandbox=False,
|
|
104
|
+
identities=[_identity(name)],
|
|
105
|
+
details={name: {"CreatedTimestamp": _now() - timedelta(days=2)}},
|
|
106
|
+
)
|
|
107
|
+
result = ses_phishing.detect(_provider(ses))
|
|
108
|
+
assert len(result.findings) == 1
|
|
109
|
+
f = result.findings[0]
|
|
110
|
+
assert f.severity == Severity.MEDIUM
|
|
88
111
|
assert "sandbox" in f.description.lower()
|
|
89
112
|
|
|
90
113
|
|
|
91
|
-
def
|
|
92
|
-
"""
|
|
93
|
-
email = "alerts@trusted.example"
|
|
94
|
-
domain = "trusted.example"
|
|
114
|
+
def test_burst_out_of_sandbox_escalates_to_high() -> None:
|
|
115
|
+
"""TWO+ recent verifications + production sending = HIGH (Wiz burst pattern)."""
|
|
95
116
|
ses = _ses_client(
|
|
96
117
|
out_of_sandbox=True,
|
|
97
118
|
identities=[
|
|
98
|
-
_identity(
|
|
99
|
-
_identity(
|
|
119
|
+
_identity("domain1.example", identity_type="DOMAIN"),
|
|
120
|
+
_identity("domain2.example", identity_type="DOMAIN"),
|
|
100
121
|
],
|
|
101
122
|
details={
|
|
102
|
-
|
|
103
|
-
|
|
123
|
+
"domain1.example": {"CreatedTimestamp": _now() - timedelta(days=1)},
|
|
124
|
+
"domain2.example": {"CreatedTimestamp": _now() - timedelta(days=3)},
|
|
104
125
|
},
|
|
105
126
|
)
|
|
106
127
|
result = ses_phishing.detect(_provider(ses))
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
128
|
+
assert len(result.findings) == 2
|
|
129
|
+
# BOTH findings escalate - the burst count is account-scoped, not per-finding
|
|
130
|
+
for f in result.findings:
|
|
131
|
+
assert f.severity == Severity.HIGH
|
|
132
|
+
assert "burst pattern" in f.description.lower()
|
|
133
|
+
assert "wiz" in f.description.lower()
|
|
111
134
|
|
|
112
135
|
|
|
113
|
-
def
|
|
114
|
-
"""
|
|
115
|
-
|
|
136
|
+
def test_burst_in_sandbox_stays_medium() -> None:
|
|
137
|
+
"""Burst but account still in sandbox = MEDIUM (no external blast radius)."""
|
|
138
|
+
ses = _ses_client(
|
|
139
|
+
out_of_sandbox=False,
|
|
140
|
+
identities=[
|
|
141
|
+
_identity("d1.example", identity_type="DOMAIN"),
|
|
142
|
+
_identity("d2.example", identity_type="DOMAIN"),
|
|
143
|
+
],
|
|
144
|
+
details={
|
|
145
|
+
"d1.example": {"CreatedTimestamp": _now() - timedelta(days=1)},
|
|
146
|
+
"d2.example": {"CreatedTimestamp": _now() - timedelta(days=2)},
|
|
147
|
+
},
|
|
148
|
+
)
|
|
149
|
+
result = ses_phishing.detect(_provider(ses))
|
|
150
|
+
assert len(result.findings) == 2
|
|
151
|
+
for f in result.findings:
|
|
152
|
+
assert f.severity == Severity.MEDIUM
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_email_no_matching_domain_does_not_escalate() -> None:
|
|
156
|
+
"""v2.2.1 regression check: the removed 'typosquat' heuristic must not return.
|
|
157
|
+
|
|
158
|
+
Wiz documented attackers verifying DOMAINS in bursts, not single emails
|
|
159
|
+
without a matching domain. A single email identity, even out-of-sandbox,
|
|
160
|
+
must stay MEDIUM unless the burst threshold is met.
|
|
161
|
+
"""
|
|
116
162
|
ses = _ses_client(
|
|
117
163
|
out_of_sandbox=True,
|
|
118
|
-
identities=[_identity(
|
|
119
|
-
details={
|
|
164
|
+
identities=[_identity("support@typosquat.example")],
|
|
165
|
+
details={"support@typosquat.example": {"CreatedTimestamp": _now() - timedelta(days=1)}},
|
|
120
166
|
)
|
|
121
167
|
result = ses_phishing.detect(_provider(ses))
|
|
122
168
|
assert len(result.findings) == 1
|
|
123
|
-
|
|
124
|
-
assert f.severity == Severity.HIGH
|
|
125
|
-
assert "typosquat" in f.description.lower()
|
|
169
|
+
assert result.findings[0].severity == Severity.MEDIUM
|
|
126
170
|
|
|
127
171
|
|
|
128
172
|
def test_pending_identity_not_flagged() -> None:
|
|
@@ -137,6 +181,30 @@ def test_pending_identity_not_flagged() -> None:
|
|
|
137
181
|
assert result.findings == []
|
|
138
182
|
|
|
139
183
|
|
|
184
|
+
def test_burst_only_counts_recent_identities() -> None:
|
|
185
|
+
"""Old (>14d) identities do not contribute to the burst count.
|
|
186
|
+
|
|
187
|
+
Account has 1 recent identity and 1 old identity. Old one is filtered
|
|
188
|
+
out before the burst check, so burst_count = 1, severity stays MEDIUM
|
|
189
|
+
even out-of-sandbox.
|
|
190
|
+
"""
|
|
191
|
+
ses = _ses_client(
|
|
192
|
+
out_of_sandbox=True,
|
|
193
|
+
identities=[
|
|
194
|
+
_identity("recent.example", identity_type="DOMAIN"),
|
|
195
|
+
_identity("old.example", identity_type="DOMAIN"),
|
|
196
|
+
],
|
|
197
|
+
details={
|
|
198
|
+
"recent.example": {"CreatedTimestamp": _now() - timedelta(days=2)},
|
|
199
|
+
"old.example": {"CreatedTimestamp": _now() - timedelta(days=200)},
|
|
200
|
+
},
|
|
201
|
+
)
|
|
202
|
+
result = ses_phishing.detect(_provider(ses))
|
|
203
|
+
assert len(result.findings) == 1
|
|
204
|
+
assert "recent.example" in result.findings[0].resource_id
|
|
205
|
+
assert result.findings[0].severity == Severity.MEDIUM
|
|
206
|
+
|
|
207
|
+
|
|
140
208
|
def test_remediation_includes_delete_and_audit() -> None:
|
|
141
209
|
name = "phish-staging@bad.example"
|
|
142
210
|
ses = _ses_client(
|
|
@@ -174,11 +242,7 @@ def test_pattern_metadata_exposed() -> None:
|
|
|
174
242
|
|
|
175
243
|
|
|
176
244
|
def test_provider_client_failure_swallowed_per_region() -> None:
|
|
177
|
-
"""sesv2 client init failure in a region is swallowed (region opt-in is the common cause).
|
|
178
|
-
|
|
179
|
-
This differs from patterns where AWS perms errors should surface - SES is region-scoped
|
|
180
|
-
and many regions don't have sesv2 at all. We trade error visibility for noise reduction.
|
|
181
|
-
"""
|
|
245
|
+
"""sesv2 client init failure in a region is swallowed (region opt-in is the common cause)."""
|
|
182
246
|
p = MagicMock(spec=AWSProvider)
|
|
183
247
|
p.regions = ["us-east-1"]
|
|
184
248
|
p.client.side_effect = RuntimeError("boom")
|
|
@@ -136,3 +136,21 @@ def test_pattern_metadata_exposed() -> None:
|
|
|
136
136
|
assert trufflehog_ua.PATTERN_ID == "TF-004-trufflehog-ua-cloudtrail"
|
|
137
137
|
assert trufflehog_ua.CHECK_ID == "aws-tf-004"
|
|
138
138
|
assert trufflehog_ua.PATTERN_SEVERITY == Severity.CRITICAL
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_cloudgrappler_ua_not_flagged() -> None:
|
|
142
|
+
"""v2.2.1: defensive tools (Permiso CloudGrappler) must NOT be flagged.
|
|
143
|
+
|
|
144
|
+
Their UA appearing in CloudTrail means a defender is running them
|
|
145
|
+
against the account, not that the account is under attack.
|
|
146
|
+
"""
|
|
147
|
+
ct = _ct_with_events(events=[_event("cloudgrappler/1.0")])
|
|
148
|
+
result = trufflehog_ua.detect(_provider(ct))
|
|
149
|
+
assert result.findings == []
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def test_detention_dodger_ua_not_flagged() -> None:
|
|
153
|
+
"""v2.2.1: defensive tools (Permiso DetentionDodger) must NOT be flagged."""
|
|
154
|
+
ct = _ct_with_events(events=[_event("detention-dodger/1.0")])
|
|
155
|
+
result = trufflehog_ua.detect(_provider(ct))
|
|
156
|
+
assert result.findings == []
|
|
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
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/bsi_c5_2020.json
RENAMED
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/cis_aws_v3.json
RENAMED
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/hipaa_security.json
RENAMED
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/iso27001_2022.json
RENAMED
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/nis2_directive.json
RENAMED
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/compliance/frameworks/soc2_type2.json
RENAMED
|
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
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cloud_audit-2.2.0 → cloud_audit-2.2.1}/src/cloud_audit/providers/aws/threat_feed/mmdsv1_in_use.py
RENAMED
|
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
|
|
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
|