runbooks 0.7.6__py3-none-any.whl → 0.7.9__py3-none-any.whl
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.
- runbooks/__init__.py +1 -1
- runbooks/base.py +5 -1
- runbooks/cfat/__init__.py +8 -4
- runbooks/cfat/assessment/collectors.py +171 -14
- runbooks/cfat/assessment/compliance.py +871 -0
- runbooks/cfat/assessment/runner.py +122 -11
- runbooks/cfat/models.py +6 -2
- runbooks/common/logger.py +14 -0
- runbooks/common/rich_utils.py +451 -0
- runbooks/enterprise/__init__.py +68 -0
- runbooks/enterprise/error_handling.py +411 -0
- runbooks/enterprise/logging.py +439 -0
- runbooks/enterprise/multi_tenant.py +583 -0
- runbooks/finops/README.md +468 -241
- runbooks/finops/__init__.py +39 -3
- runbooks/finops/cli.py +83 -18
- runbooks/finops/cross_validation.py +375 -0
- runbooks/finops/dashboard_runner.py +812 -164
- runbooks/finops/enhanced_dashboard_runner.py +525 -0
- runbooks/finops/finops_dashboard.py +1892 -0
- runbooks/finops/helpers.py +485 -51
- runbooks/finops/optimizer.py +823 -0
- runbooks/finops/tests/__init__.py +19 -0
- runbooks/finops/tests/results_test_finops_dashboard.xml +1 -0
- runbooks/finops/tests/run_comprehensive_tests.py +421 -0
- runbooks/finops/tests/run_tests.py +305 -0
- runbooks/finops/tests/test_finops_dashboard.py +705 -0
- runbooks/finops/tests/test_integration.py +477 -0
- runbooks/finops/tests/test_performance.py +380 -0
- runbooks/finops/tests/test_performance_benchmarks.py +500 -0
- runbooks/finops/tests/test_reference_images_validation.py +867 -0
- runbooks/finops/tests/test_single_account_features.py +715 -0
- runbooks/finops/tests/validate_test_suite.py +220 -0
- runbooks/finops/types.py +1 -1
- runbooks/hitl/enhanced_workflow_engine.py +725 -0
- runbooks/inventory/artifacts/scale-optimize-status.txt +12 -0
- runbooks/inventory/collectors/aws_comprehensive.py +442 -0
- runbooks/inventory/collectors/enterprise_scale.py +281 -0
- runbooks/inventory/core/collector.py +172 -13
- runbooks/inventory/discovery.md +1 -1
- runbooks/inventory/list_ec2_instances.py +18 -20
- runbooks/inventory/list_ssm_parameters.py +31 -3
- runbooks/inventory/organizations_discovery.py +1269 -0
- runbooks/inventory/rich_inventory_display.py +393 -0
- runbooks/inventory/run_on_multi_accounts.py +35 -19
- runbooks/inventory/runbooks.security.report_generator.log +0 -0
- runbooks/inventory/runbooks.security.run_script.log +0 -0
- runbooks/inventory/vpc_flow_analyzer.py +1030 -0
- runbooks/main.py +2215 -119
- runbooks/metrics/dora_metrics_engine.py +599 -0
- runbooks/operate/__init__.py +2 -2
- runbooks/operate/base.py +122 -10
- runbooks/operate/deployment_framework.py +1032 -0
- runbooks/operate/deployment_validator.py +853 -0
- runbooks/operate/dynamodb_operations.py +10 -6
- runbooks/operate/ec2_operations.py +319 -11
- runbooks/operate/executive_dashboard.py +779 -0
- runbooks/operate/mcp_integration.py +750 -0
- runbooks/operate/nat_gateway_operations.py +1120 -0
- runbooks/operate/networking_cost_heatmap.py +685 -0
- runbooks/operate/privatelink_operations.py +940 -0
- runbooks/operate/s3_operations.py +10 -6
- runbooks/operate/vpc_endpoints.py +644 -0
- runbooks/operate/vpc_operations.py +1038 -0
- runbooks/remediation/__init__.py +2 -2
- runbooks/remediation/acm_remediation.py +1 -1
- runbooks/remediation/base.py +1 -1
- runbooks/remediation/cloudtrail_remediation.py +1 -1
- runbooks/remediation/cognito_remediation.py +1 -1
- runbooks/remediation/dynamodb_remediation.py +1 -1
- runbooks/remediation/ec2_remediation.py +1 -1
- runbooks/remediation/ec2_unattached_ebs_volumes.py +1 -1
- runbooks/remediation/kms_enable_key_rotation.py +1 -1
- runbooks/remediation/kms_remediation.py +1 -1
- runbooks/remediation/lambda_remediation.py +1 -1
- runbooks/remediation/multi_account.py +1 -1
- runbooks/remediation/rds_remediation.py +1 -1
- runbooks/remediation/s3_block_public_access.py +1 -1
- runbooks/remediation/s3_enable_access_logging.py +1 -1
- runbooks/remediation/s3_encryption.py +1 -1
- runbooks/remediation/s3_remediation.py +1 -1
- runbooks/remediation/vpc_remediation.py +475 -0
- runbooks/security/__init__.py +3 -1
- runbooks/security/compliance_automation.py +632 -0
- runbooks/security/report_generator.py +10 -0
- runbooks/security/run_script.py +31 -5
- runbooks/security/security_baseline_tester.py +169 -30
- runbooks/security/security_export.py +477 -0
- runbooks/validation/__init__.py +10 -0
- runbooks/validation/benchmark.py +484 -0
- runbooks/validation/cli.py +356 -0
- runbooks/validation/mcp_validator.py +768 -0
- runbooks/vpc/__init__.py +38 -0
- runbooks/vpc/config.py +212 -0
- runbooks/vpc/cost_engine.py +347 -0
- runbooks/vpc/heatmap_engine.py +605 -0
- runbooks/vpc/manager_interface.py +634 -0
- runbooks/vpc/networking_wrapper.py +1260 -0
- runbooks/vpc/rich_formatters.py +679 -0
- runbooks/vpc/tests/__init__.py +5 -0
- runbooks/vpc/tests/conftest.py +356 -0
- runbooks/vpc/tests/test_cli_integration.py +530 -0
- runbooks/vpc/tests/test_config.py +458 -0
- runbooks/vpc/tests/test_cost_engine.py +479 -0
- runbooks/vpc/tests/test_networking_wrapper.py +512 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/METADATA +40 -12
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/RECORD +111 -50
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/WHEEL +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/entry_points.txt +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/licenses/LICENSE +0 -0
- {runbooks-0.7.6.dist-info → runbooks-0.7.9.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,356 @@
|
|
1
|
+
"""
|
2
|
+
VPC Testing Configuration and Fixtures
|
3
|
+
|
4
|
+
Provides specialized fixtures for VPC networking component testing
|
5
|
+
with comprehensive AWS service mocking and test data.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
import sys
|
11
|
+
import tempfile
|
12
|
+
from datetime import datetime, timedelta
|
13
|
+
from pathlib import Path
|
14
|
+
from typing import Any, Dict, List, Optional
|
15
|
+
from unittest.mock import MagicMock, Mock, patch
|
16
|
+
|
17
|
+
import boto3
|
18
|
+
import pytest
|
19
|
+
from moto import mock_aws
|
20
|
+
from rich.console import Console
|
21
|
+
|
22
|
+
# Add src to Python path
|
23
|
+
src_path = Path(__file__).parent.parent.parent.parent.parent / "src"
|
24
|
+
if str(src_path) not in sys.path:
|
25
|
+
sys.path.insert(0, str(src_path))
|
26
|
+
|
27
|
+
from runbooks.vpc.config import AWSCostModel, OptimizationThresholds, VPCNetworkingConfig
|
28
|
+
from runbooks.vpc.cost_engine import NetworkingCostEngine
|
29
|
+
from runbooks.vpc.networking_wrapper import VPCNetworkingWrapper
|
30
|
+
|
31
|
+
|
32
|
+
@pytest.fixture(scope="session")
|
33
|
+
def aws_credentials():
|
34
|
+
"""Mock AWS credentials for VPC testing."""
|
35
|
+
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
|
36
|
+
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
|
37
|
+
os.environ["AWS_SECURITY_TOKEN"] = "testing"
|
38
|
+
os.environ["AWS_SESSION_TOKEN"] = "testing"
|
39
|
+
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
|
40
|
+
|
41
|
+
|
42
|
+
@pytest.fixture
|
43
|
+
def vpc_test_profiles():
|
44
|
+
"""Test profile configurations for VPC testing."""
|
45
|
+
return {
|
46
|
+
"billing_profile": "test-billing-profile",
|
47
|
+
"management_profile": "test-management-profile",
|
48
|
+
"centralised_ops_profile": "test-ops-profile",
|
49
|
+
"single_account_profile": "test-single-account-profile",
|
50
|
+
}
|
51
|
+
|
52
|
+
|
53
|
+
@pytest.fixture
|
54
|
+
def vpc_test_config():
|
55
|
+
"""Standard VPC test configuration."""
|
56
|
+
return VPCNetworkingConfig(
|
57
|
+
default_region="us-east-1",
|
58
|
+
billing_profile="test-billing-profile",
|
59
|
+
default_analysis_days=30,
|
60
|
+
default_output_format="json",
|
61
|
+
enable_cost_approval_workflow=True,
|
62
|
+
enable_mcp_validation=False,
|
63
|
+
)
|
64
|
+
|
65
|
+
|
66
|
+
@pytest.fixture
|
67
|
+
def mock_console():
|
68
|
+
"""Mock Rich Console for testing output."""
|
69
|
+
console = Mock(spec=Console)
|
70
|
+
console.print = Mock()
|
71
|
+
|
72
|
+
# Mock status context manager
|
73
|
+
mock_status = Mock()
|
74
|
+
mock_status.__enter__ = Mock(return_value=mock_status)
|
75
|
+
mock_status.__exit__ = Mock(return_value=None)
|
76
|
+
console.status = Mock(return_value=mock_status)
|
77
|
+
|
78
|
+
return console
|
79
|
+
|
80
|
+
|
81
|
+
@pytest.fixture
|
82
|
+
def sample_nat_gateways():
|
83
|
+
"""Sample NAT Gateway data for testing."""
|
84
|
+
return [
|
85
|
+
{
|
86
|
+
"NatGatewayId": "nat-0123456789abcdef0",
|
87
|
+
"State": "available",
|
88
|
+
"VpcId": "vpc-0123456789abcdef0",
|
89
|
+
"SubnetId": "subnet-0123456789abcdef0",
|
90
|
+
"CreationTime": datetime.now(),
|
91
|
+
"NatGatewayAddresses": [
|
92
|
+
{
|
93
|
+
"AllocationId": "eipalloc-0123456789abcdef0",
|
94
|
+
"NetworkInterfaceId": "eni-0123456789abcdef0",
|
95
|
+
"PrivateIp": "10.0.1.5",
|
96
|
+
"PublicIp": "203.0.113.5",
|
97
|
+
}
|
98
|
+
],
|
99
|
+
},
|
100
|
+
{
|
101
|
+
"NatGatewayId": "nat-0123456789abcdef1",
|
102
|
+
"State": "available",
|
103
|
+
"VpcId": "vpc-0123456789abcdef1",
|
104
|
+
"SubnetId": "subnet-0123456789abcdef1",
|
105
|
+
"CreationTime": datetime.now(),
|
106
|
+
"NatGatewayAddresses": [
|
107
|
+
{
|
108
|
+
"AllocationId": "eipalloc-0123456789abcdef1",
|
109
|
+
"NetworkInterfaceId": "eni-0123456789abcdef1",
|
110
|
+
"PrivateIp": "10.0.2.5",
|
111
|
+
"PublicIp": "203.0.113.6",
|
112
|
+
}
|
113
|
+
],
|
114
|
+
},
|
115
|
+
]
|
116
|
+
|
117
|
+
|
118
|
+
@pytest.fixture
|
119
|
+
def sample_vpc_endpoints():
|
120
|
+
"""Sample VPC Endpoint data for testing."""
|
121
|
+
return [
|
122
|
+
{
|
123
|
+
"VpcEndpointId": "vpce-0123456789abcdef0",
|
124
|
+
"VpcEndpointType": "Interface",
|
125
|
+
"VpcId": "vpc-0123456789abcdef0",
|
126
|
+
"ServiceName": "com.amazonaws.us-east-1.s3",
|
127
|
+
"State": "available",
|
128
|
+
"CreationTimestamp": datetime.now(),
|
129
|
+
"SubnetIds": ["subnet-0123456789abcdef0", "subnet-0123456789abcdef1"],
|
130
|
+
},
|
131
|
+
{
|
132
|
+
"VpcEndpointId": "vpce-0123456789abcdef1",
|
133
|
+
"VpcEndpointType": "Gateway",
|
134
|
+
"VpcId": "vpc-0123456789abcdef1",
|
135
|
+
"ServiceName": "com.amazonaws.us-east-1.dynamodb",
|
136
|
+
"State": "available",
|
137
|
+
"CreationTimestamp": datetime.now(),
|
138
|
+
"SubnetIds": [],
|
139
|
+
},
|
140
|
+
]
|
141
|
+
|
142
|
+
|
143
|
+
@pytest.fixture
|
144
|
+
def sample_cloudwatch_metrics():
|
145
|
+
"""Sample CloudWatch metrics data for NAT Gateway testing."""
|
146
|
+
return {
|
147
|
+
"ActiveConnectionCount": [
|
148
|
+
{"Timestamp": datetime.now() - timedelta(days=1), "Average": 150.0, "Maximum": 200.0, "Unit": "Count"},
|
149
|
+
{"Timestamp": datetime.now() - timedelta(days=2), "Average": 120.0, "Maximum": 180.0, "Unit": "Count"},
|
150
|
+
],
|
151
|
+
"BytesOutToDestination": [
|
152
|
+
{
|
153
|
+
"Timestamp": datetime.now() - timedelta(days=1),
|
154
|
+
"Sum": 5368709120.0, # 5 GB
|
155
|
+
"Unit": "Bytes",
|
156
|
+
},
|
157
|
+
{
|
158
|
+
"Timestamp": datetime.now() - timedelta(days=2),
|
159
|
+
"Sum": 3221225472.0, # 3 GB
|
160
|
+
"Unit": "Bytes",
|
161
|
+
},
|
162
|
+
],
|
163
|
+
}
|
164
|
+
|
165
|
+
|
166
|
+
@pytest.fixture
|
167
|
+
def mock_aws_vpc_comprehensive(aws_credentials, sample_nat_gateways, sample_vpc_endpoints):
|
168
|
+
"""Comprehensive AWS VPC mock with all networking components."""
|
169
|
+
with mock_aws():
|
170
|
+
# Create clients
|
171
|
+
ec2_client = boto3.client("ec2", region_name="us-east-1")
|
172
|
+
cloudwatch_client = boto3.client("cloudwatch", region_name="us-east-1")
|
173
|
+
|
174
|
+
# Create VPC infrastructure
|
175
|
+
vpc_response = ec2_client.create_vpc(CidrBlock="10.0.0.0/16")
|
176
|
+
vpc_id = vpc_response["Vpc"]["VpcId"]
|
177
|
+
|
178
|
+
# Create subnets
|
179
|
+
subnet1 = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.1.0/24", AvailabilityZone="us-east-1a")
|
180
|
+
subnet2 = ec2_client.create_subnet(VpcId=vpc_id, CidrBlock="10.0.2.0/24", AvailabilityZone="us-east-1b")
|
181
|
+
|
182
|
+
# Create Internet Gateway
|
183
|
+
igw_response = ec2_client.create_internet_gateway()
|
184
|
+
igw_id = igw_response["InternetGateway"]["InternetGatewayId"]
|
185
|
+
ec2_client.attach_internet_gateway(InternetGatewayId=igw_id, VpcId=vpc_id)
|
186
|
+
|
187
|
+
# Create Elastic IPs for NAT Gateways
|
188
|
+
eip1 = ec2_client.allocate_address(Domain="vpc")
|
189
|
+
eip2 = ec2_client.allocate_address(Domain="vpc")
|
190
|
+
|
191
|
+
# Create NAT Gateways
|
192
|
+
nat_gw1 = ec2_client.create_nat_gateway(
|
193
|
+
SubnetId=subnet1["Subnet"]["SubnetId"], AllocationId=eip1["AllocationId"]
|
194
|
+
)
|
195
|
+
nat_gw2 = ec2_client.create_nat_gateway(
|
196
|
+
SubnetId=subnet2["Subnet"]["SubnetId"], AllocationId=eip2["AllocationId"]
|
197
|
+
)
|
198
|
+
|
199
|
+
# Create VPC Endpoints
|
200
|
+
vpc_endpoint_s3 = ec2_client.create_vpc_endpoint(
|
201
|
+
VpcId=vpc_id,
|
202
|
+
ServiceName="com.amazonaws.us-east-1.s3",
|
203
|
+
VpcEndpointType="Interface",
|
204
|
+
SubnetIds=[subnet1["Subnet"]["SubnetId"], subnet2["Subnet"]["SubnetId"]],
|
205
|
+
)
|
206
|
+
|
207
|
+
vpc_endpoint_dynamodb = ec2_client.create_vpc_endpoint(
|
208
|
+
VpcId=vpc_id, ServiceName="com.amazonaws.us-east-1.dynamodb", VpcEndpointType="Gateway"
|
209
|
+
)
|
210
|
+
|
211
|
+
test_infrastructure = {
|
212
|
+
"vpc_id": vpc_id,
|
213
|
+
"subnet_ids": [subnet1["Subnet"]["SubnetId"], subnet2["Subnet"]["SubnetId"]],
|
214
|
+
"igw_id": igw_id,
|
215
|
+
"nat_gateway_ids": [nat_gw1["NatGateway"]["NatGatewayId"], nat_gw2["NatGateway"]["NatGatewayId"]],
|
216
|
+
"vpc_endpoint_ids": [
|
217
|
+
vpc_endpoint_s3["VpcEndpoint"]["VpcEndpointId"],
|
218
|
+
vpc_endpoint_dynamodb["VpcEndpoint"]["VpcEndpointId"],
|
219
|
+
],
|
220
|
+
"allocation_ids": [eip1["AllocationId"], eip2["AllocationId"]],
|
221
|
+
}
|
222
|
+
|
223
|
+
yield {"ec2_client": ec2_client, "cloudwatch_client": cloudwatch_client, "infrastructure": test_infrastructure}
|
224
|
+
|
225
|
+
|
226
|
+
@pytest.fixture
|
227
|
+
def vpc_networking_wrapper(mock_console, vpc_test_config):
|
228
|
+
"""VPC Networking Wrapper instance for testing."""
|
229
|
+
with patch("runbooks.vpc.networking_wrapper.boto3.Session") as mock_session:
|
230
|
+
# Configure mock session
|
231
|
+
mock_session_instance = Mock()
|
232
|
+
mock_session.return_value = mock_session_instance
|
233
|
+
|
234
|
+
wrapper = VPCNetworkingWrapper(
|
235
|
+
profile="test-profile",
|
236
|
+
region="us-east-1",
|
237
|
+
billing_profile="test-billing-profile",
|
238
|
+
output_format="json",
|
239
|
+
console=mock_console,
|
240
|
+
)
|
241
|
+
|
242
|
+
# Set mock session
|
243
|
+
wrapper.session = mock_session_instance
|
244
|
+
|
245
|
+
yield wrapper
|
246
|
+
|
247
|
+
|
248
|
+
@pytest.fixture
|
249
|
+
def networking_cost_engine(vpc_test_config):
|
250
|
+
"""Networking Cost Engine instance for testing."""
|
251
|
+
with patch("runbooks.vpc.cost_engine.boto3.Session") as mock_session:
|
252
|
+
mock_session_instance = Mock()
|
253
|
+
mock_session.return_value = mock_session_instance
|
254
|
+
|
255
|
+
engine = NetworkingCostEngine(session=mock_session_instance, config=vpc_test_config)
|
256
|
+
|
257
|
+
yield engine
|
258
|
+
|
259
|
+
|
260
|
+
@pytest.fixture
|
261
|
+
def performance_benchmarks():
|
262
|
+
"""Performance benchmark thresholds for testing."""
|
263
|
+
return {
|
264
|
+
"nat_gateway_analysis_max_time": 5.0, # seconds
|
265
|
+
"vpc_endpoint_analysis_max_time": 3.0, # seconds
|
266
|
+
"cost_calculation_max_time": 1.0, # seconds
|
267
|
+
"cli_response_max_time": 2.0, # seconds
|
268
|
+
"heatmap_generation_max_time": 10.0, # seconds
|
269
|
+
}
|
270
|
+
|
271
|
+
|
272
|
+
@pytest.fixture
|
273
|
+
def mock_cost_explorer_responses():
|
274
|
+
"""Mock Cost Explorer API responses for testing."""
|
275
|
+
return {
|
276
|
+
"vpc_costs": {
|
277
|
+
"ResultsByTime": [
|
278
|
+
{
|
279
|
+
"TimePeriod": {"Start": "2024-01-01", "End": "2024-01-31"},
|
280
|
+
"Total": {"BlendedCost": {"Amount": "145.67", "Unit": "USD"}},
|
281
|
+
}
|
282
|
+
]
|
283
|
+
},
|
284
|
+
"nat_gateway_costs": {
|
285
|
+
"ResultsByTime": [
|
286
|
+
{
|
287
|
+
"TimePeriod": {"Start": "2024-01-01", "End": "2024-01-31"},
|
288
|
+
"Total": {"BlendedCost": {"Amount": "89.32", "Unit": "USD"}},
|
289
|
+
}
|
290
|
+
]
|
291
|
+
},
|
292
|
+
}
|
293
|
+
|
294
|
+
|
295
|
+
@pytest.fixture
|
296
|
+
def temp_output_directory():
|
297
|
+
"""Temporary directory for test output files."""
|
298
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
299
|
+
yield Path(temp_dir)
|
300
|
+
|
301
|
+
|
302
|
+
# Utility functions for tests
|
303
|
+
|
304
|
+
|
305
|
+
@pytest.fixture
|
306
|
+
def assert_performance_benchmark():
|
307
|
+
"""Utility function to assert performance benchmarks."""
|
308
|
+
|
309
|
+
def _assert_performance(execution_time: float, benchmark_name: str, benchmarks: dict):
|
310
|
+
"""Assert that execution time meets performance benchmark."""
|
311
|
+
if benchmark_name in benchmarks:
|
312
|
+
max_time = benchmarks[benchmark_name]
|
313
|
+
assert execution_time < max_time, (
|
314
|
+
f"Performance benchmark failed: {execution_time:.2f}s > {max_time}s for {benchmark_name}"
|
315
|
+
)
|
316
|
+
return True
|
317
|
+
|
318
|
+
return _assert_performance
|
319
|
+
|
320
|
+
|
321
|
+
@pytest.fixture
|
322
|
+
def validate_vpc_structure():
|
323
|
+
"""Utility function to validate VPC analysis result structure."""
|
324
|
+
|
325
|
+
def _validate_structure(result: Dict[str, Any], expected_keys: List[str]):
|
326
|
+
"""Validate that result contains all expected keys."""
|
327
|
+
for key in expected_keys:
|
328
|
+
assert key in result, f"Missing required key: {key}"
|
329
|
+
|
330
|
+
# Validate common structure elements
|
331
|
+
assert "timestamp" in result
|
332
|
+
assert "profile" in result
|
333
|
+
assert "region" in result
|
334
|
+
|
335
|
+
return True
|
336
|
+
|
337
|
+
return _validate_structure
|
338
|
+
|
339
|
+
|
340
|
+
@pytest.fixture
|
341
|
+
def security_test_validator():
|
342
|
+
"""Utility for security validation testing."""
|
343
|
+
|
344
|
+
def _validate_security(func_call_result: Any, sensitive_patterns: List[str] = None):
|
345
|
+
"""Validate that no sensitive information is exposed."""
|
346
|
+
if sensitive_patterns is None:
|
347
|
+
sensitive_patterns = ["AKIA", "SECRET", "TOKEN", "PASSWORD"]
|
348
|
+
|
349
|
+
result_str = str(func_call_result)
|
350
|
+
|
351
|
+
for pattern in sensitive_patterns:
|
352
|
+
assert pattern not in result_str.upper(), f"Sensitive pattern '{pattern}' found in result"
|
353
|
+
|
354
|
+
return True
|
355
|
+
|
356
|
+
return _validate_security
|