From f2e7a2469b8518c7b5cae8c1515d2c582514d8d8 Mon Sep 17 00:00:00 2001 From: donmanue Date: Mon, 22 Sep 2025 16:08:54 -0700 Subject: [PATCH] Added request header allowlist support --- pyproject.toml | 4 +- src/bedrock_agentcore/runtime/app.py | 24 +- src/bedrock_agentcore/runtime/context.py | 17 +- src/bedrock_agentcore/runtime/models.py | 2 + tests/bedrock_agentcore/runtime/test_app.py | 350 ++++++++++++++++++ .../bedrock_agentcore/runtime/test_context.py | 100 ++++- uv.lock | 24 +- 7 files changed, 504 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 6ea8d74..1330aae 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,8 @@ classifiers = [ "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ - "boto3>=1.39.7", - "botocore>=1.39.7", + "boto3>=1.40.35", + "botocore>=1.40.35", "pydantic>=2.0.0,<3.0.0", "urllib3>=1.26.0", "starlette>=0.46.2", diff --git a/src/bedrock_agentcore/runtime/app.py b/src/bedrock_agentcore/runtime/app.py index 8099979..41e0c34 100644 --- a/src/bedrock_agentcore/runtime/app.py +++ b/src/bedrock_agentcore/runtime/app.py @@ -21,6 +21,8 @@ from .context import BedrockAgentCoreContext, RequestContext from .models import ( ACCESS_TOKEN_HEADER, + AUTHORIZATION_HEADER, + CUSTOM_HEADER_PREFIX, REQUEST_ID_HEADER, SESSION_HEADER, TASK_ACTION_CLEAR_FORCED_STATUS, @@ -279,7 +281,27 @@ def _build_request_context(self, request) -> RequestContext: if agent_identity_token: BedrockAgentCoreContext.set_workload_access_token(agent_identity_token) - return RequestContext(session_id=session_id) + # Collect relevant request headers (Authorization + Custom headers) + request_headers = {} + + # Add Authorization header if present + authorization_header = headers.get(AUTHORIZATION_HEADER) + if authorization_header is not None: + request_headers[AUTHORIZATION_HEADER] = authorization_header + + # Add custom headers with the specified prefix + for header_name, header_value in headers.items(): + if header_name.lower().startswith(CUSTOM_HEADER_PREFIX.lower()): + request_headers[header_name] = header_value + + # Set in context if any headers were found + if request_headers: + BedrockAgentCoreContext.set_request_headers(request_headers) + + # Get the headers from context to pass to RequestContext + req_headers = BedrockAgentCoreContext.get_request_headers() + + return RequestContext(session_id=session_id, request_headers=req_headers) except Exception as e: self.logger.warning("Failed to build request context: %s: %s", type(e).__name__, e) request_id = str(uuid.uuid4()) diff --git a/src/bedrock_agentcore/runtime/context.py b/src/bedrock_agentcore/runtime/context.py index 640d43b..a62185f 100644 --- a/src/bedrock_agentcore/runtime/context.py +++ b/src/bedrock_agentcore/runtime/context.py @@ -4,7 +4,7 @@ """ from contextvars import ContextVar -from typing import Optional +from typing import Dict, Optional from pydantic import BaseModel, Field @@ -13,6 +13,7 @@ class RequestContext(BaseModel): """Request context containing metadata from HTTP requests.""" session_id: Optional[str] = Field(None) + request_headers: Optional[Dict[str, str]] = Field(None) class BedrockAgentCoreContext: @@ -21,6 +22,7 @@ class BedrockAgentCoreContext: _workload_access_token: ContextVar[Optional[str]] = ContextVar("workload_access_token") _request_id: ContextVar[Optional[str]] = ContextVar("request_id") _session_id: ContextVar[Optional[str]] = ContextVar("session_id") + _request_headers: ContextVar[Optional[Dict[str, str]]] = ContextVar("request_headers") @classmethod def set_workload_access_token(cls, token: str): @@ -56,3 +58,16 @@ def get_session_id(cls) -> Optional[str]: return cls._session_id.get() except LookupError: return None + + @classmethod + def set_request_headers(cls, headers: Dict[str, str]): + """Set request headers in the context.""" + cls._request_headers.set(headers) + + @classmethod + def get_request_headers(cls) -> Optional[Dict[str, str]]: + """Get request headers from the context.""" + try: + return cls._request_headers.get() + except LookupError: + return None diff --git a/src/bedrock_agentcore/runtime/models.py b/src/bedrock_agentcore/runtime/models.py index 4555de4..7d97366 100644 --- a/src/bedrock_agentcore/runtime/models.py +++ b/src/bedrock_agentcore/runtime/models.py @@ -17,6 +17,8 @@ class PingStatus(str, Enum): SESSION_HEADER = "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id" REQUEST_ID_HEADER = "X-Amzn-Bedrock-AgentCore-Runtime-Request-Id" ACCESS_TOKEN_HEADER = "WorkloadAccessToken" # nosec +AUTHORIZATION_HEADER = "Authorization" +CUSTOM_HEADER_PREFIX = "X-Amzn-Bedrock-AgentCore-Runtime-Custom-" # Task action constants TASK_ACTION_PING_STATUS = "ping_status" diff --git a/tests/bedrock_agentcore/runtime/test_app.py b/tests/bedrock_agentcore/runtime/test_app.py index afd460d..e1f6817 100644 --- a/tests/bedrock_agentcore/runtime/test_app.py +++ b/tests/bedrock_agentcore/runtime/test_app.py @@ -1557,3 +1557,353 @@ def format_in_new_context(): assert "requestId" not in log_data assert "sessionId" not in log_data assert "timestamp" in log_data + + +class TestRequestHeadersExtraction: + """Test request headers extraction and context building.""" + + def test_build_request_context_with_authorization_header(self): + """Test _build_request_context extracts Authorization header.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = {"Authorization": "Bearer test-auth-token", "Content-Type": "application/json"} + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + assert context.request_headers["Authorization"] == "Bearer test-auth-token" + assert "Content-Type" not in context.request_headers # Only Auth and Custom headers + + def test_build_request_context_with_custom_headers(self): + """Test _build_request_context extracts custom headers with correct prefix.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header1": "value1", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header2": "value2", + "X-Other-Header": "should-not-include", + "Content-Type": "application/json", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header1"] == "value1" + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header2"] == "value2" + assert "X-Other-Header" not in context.request_headers + assert "Content-Type" not in context.request_headers + + def test_build_request_context_with_both_auth_and_custom_headers(self): + """Test _build_request_context with both Authorization and custom headers.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Authorization": "Bearer combined-token", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-UserAgent": "test-agent/1.0", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-ClientId": "client-123", + "Content-Type": "application/json", + "X-Other-Header": "ignored", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + expected_headers = { + "Authorization": "Bearer combined-token", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-UserAgent": "test-agent/1.0", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-ClientId": "client-123", + } + + assert context.request_headers == expected_headers + assert len(context.request_headers) == 3 + + def test_build_request_context_with_no_relevant_headers(self): + """Test _build_request_context when no Authorization or custom headers present.""" + import contextvars + + # Run in fresh context to avoid cross-test contamination + ctx = contextvars.Context() + + def test_in_new_context(): + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Content-Type": "application/json", + "Accept": "application/json", + "X-Other-Header": "not-relevant", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + return context.request_headers + + result = ctx.run(test_in_new_context) + assert result is None + + def test_build_request_context_with_empty_headers(self): + """Test _build_request_context with completely empty headers.""" + import contextvars + + # Run in fresh context to avoid cross-test contamination + ctx = contextvars.Context() + + def test_in_new_context(): + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = {} + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + return context.request_headers + + result = ctx.run(test_in_new_context) + assert result is None + + def test_build_request_context_header_case_insensitive_prefix_matching(self): + """Test that custom header prefix matching is case insensitive.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "x-amzn-bedrock-agentcore-runtime-custom-lowercase": "lower-value", + "X-AMZN-BEDROCK-AGENTCORE-RUNTIME-CUSTOM-UPPERCASE": "upper-value", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-MixedCase": "mixed-value", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + assert len(context.request_headers) == 3 + assert "lower-value" in context.request_headers.values() + assert "upper-value" in context.request_headers.values() + assert "mixed-value" in context.request_headers.values() + + def test_build_request_context_headers_set_in_bedrock_context(self): + """Test that headers are properly set in BedrockAgentCoreContext.""" + from bedrock_agentcore.runtime.context import BedrockAgentCoreContext + + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Authorization": "Bearer context-test-token", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Test": "context-test-value", + "X-Amzn-Bedrock-AgentCore-Runtime-Request-Id": "test-request-123", + "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": "test-session-456", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + # Check that BedrockAgentCoreContext has the headers + bedrock_context_headers = BedrockAgentCoreContext.get_request_headers() + assert bedrock_context_headers is not None + assert bedrock_context_headers["Authorization"] == "Bearer context-test-token" + assert bedrock_context_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Test"] == "context-test-value" + + # Check that RequestContext also has the headers + assert context.request_headers == bedrock_context_headers + + def test_invocation_with_request_headers_in_context(self): + """Test end-to-end invocation where handler receives headers via context.""" + app = BedrockAgentCoreApp() + + received_headers = None + + @app.entrypoint + def handler(payload, context): + nonlocal received_headers + received_headers = context.request_headers + return {"status": "ok", "headers_received": context.request_headers is not None} + + client = TestClient(app) + headers = { + "Authorization": "Bearer integration-test-token", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-ClientId": "integration-client-123", + "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id": "integration-session", + } + + response = client.post("/invocations", json={"test": "data"}, headers=headers) + + assert response.status_code == 200 + result = response.json() + assert result["status"] == "ok" + assert result["headers_received"] is True + + # Check that the handler actually received the headers + assert received_headers is not None + + # HTTP headers are case-insensitive - find by case-insensitive search + auth_key = next((k for k in received_headers.keys() if k.lower() == "authorization"), None) + client_id_key = next( + (k for k in received_headers.keys() if k.lower() == "x-amzn-bedrock-agentcore-runtime-custom-clientid"), + None, + ) + + available_headers = list(received_headers.keys()) + assert auth_key is not None, f"Authorization header not found. Available headers: {available_headers}" + assert client_id_key is not None, f"Custom ClientId header not found. Available headers: {available_headers}" + + assert received_headers[auth_key] == "Bearer integration-test-token" + assert received_headers[client_id_key] == "integration-client-123" + + def test_invocation_without_headers_in_context(self): + """Test invocation where no relevant headers are provided.""" + import contextvars + + # Run in fresh context to avoid cross-test contamination + ctx = contextvars.Context() + + def test_in_new_context(): + app = BedrockAgentCoreApp() + + received_headers = None + + @app.entrypoint + def handler(payload, context): + nonlocal received_headers + received_headers = context.request_headers + return {"status": "ok", "headers_received": context.request_headers is not None} + + client = TestClient(app) + headers = {"Content-Type": "application/json", "Accept": "application/json"} + + response = client.post("/invocations", json={"test": "data"}, headers=headers) + + return response, received_headers + + response, received_headers = ctx.run(test_in_new_context) + + assert response.status_code == 200 + result = response.json() + assert result["status"] == "ok" + assert result["headers_received"] is False + + # Check that no headers were received + assert received_headers is None + + def test_header_values_with_special_characters(self): + """Test headers with special characters and encoding.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Authorization": "Bearer token-with-special-chars!@#$%^&*()", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Unicode": "value-with-unicode-世界", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Spaces": "value with spaces", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Quotes": 'value-with-"quotes"', + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + assert context.request_headers["Authorization"] == "Bearer token-with-special-chars!@#$%^&*()" + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Unicode"] == "value-with-unicode-世界" + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Spaces"] == "value with spaces" + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Quotes"] == 'value-with-"quotes"' + + def test_header_prefix_boundary_cases(self): + """Test edge cases for header prefix matching.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + # Exact prefix match - should be included + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-": "empty-suffix", + # Prefix with additional content - should be included + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-LongHeaderName": "long-name", + # Similar but not exact prefix - should NOT be included + "X-Amzn-Bedrock-AgentCore-Runtime-Custo": "not-exact", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom": "missing-dash", + # Prefix as substring - should NOT be included + "PrefixX-Amzn-Bedrock-AgentCore-Runtime-Custom-": "has-prefix", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + # Should include headers with exact prefix match + assert "X-Amzn-Bedrock-AgentCore-Runtime-Custom-" in context.request_headers + assert "X-Amzn-Bedrock-AgentCore-Runtime-Custom-LongHeaderName" in context.request_headers + + # Should NOT include headers without exact prefix match + assert "X-Amzn-Bedrock-AgentCore-Runtime-Custo" not in context.request_headers + assert "X-Amzn-Bedrock-AgentCore-Runtime-Custom" not in context.request_headers + assert "PrefixX-Amzn-Bedrock-AgentCore-Runtime-Custom-" not in context.request_headers + + assert len(context.request_headers) == 2 + + def test_multiple_authorization_headers_scenario(self): + """Test scenario with multiple authorization-like headers.""" + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Authorization": "Bearer primary-token", + "X-Authorization": "Bearer secondary-token", # Should NOT be included + "Proxy-Authorization": "Bearer proxy-token", # Should NOT be included + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Auth": "Bearer custom-token", # Should be included + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + + assert context.request_headers is not None + assert context.request_headers["Authorization"] == "Bearer primary-token" + assert context.request_headers["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Auth"] == "Bearer custom-token" + + # Only standard Authorization and custom headers should be included + assert "X-Authorization" not in context.request_headers + assert "Proxy-Authorization" not in context.request_headers + assert len(context.request_headers) == 2 + + def test_empty_header_values(self): + """Test handling of empty header values.""" + import contextvars + + # Run in fresh context to avoid cross-test contamination + ctx = contextvars.Context() + + def test_in_new_context(): + app = BedrockAgentCoreApp() + + class MockRequest: + def __init__(self): + self.headers = { + "Authorization": "", # Empty authorization + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Empty": "", # Empty custom header + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Valid": "valid-value", + } + + mock_request = MockRequest() + context = app._build_request_context(mock_request) + return context.request_headers + + result = ctx.run(test_in_new_context) + + assert result is not None + # Empty values should still be included + assert result["Authorization"] == "" + assert result["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Empty"] == "" + assert result["X-Amzn-Bedrock-AgentCore-Runtime-Custom-Valid"] == "valid-value" + assert len(result) == 3 diff --git a/tests/bedrock_agentcore/runtime/test_context.py b/tests/bedrock_agentcore/runtime/test_context.py index b9e1589..0dae3cf 100644 --- a/tests/bedrock_agentcore/runtime/test_context.py +++ b/tests/bedrock_agentcore/runtime/test_context.py @@ -2,7 +2,7 @@ import contextvars -from bedrock_agentcore.runtime.context import BedrockAgentCoreContext +from bedrock_agentcore.runtime.context import BedrockAgentCoreContext, RequestContext class TestBedrockAgentCoreContext: @@ -67,3 +67,101 @@ def test_in_new_context(): result = ctx.run(test_in_new_context) assert result is None + + def test_set_and_get_request_headers(self): + """Test setting and getting request headers.""" + headers = {"Authorization": "Bearer token-123", "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Key": "custom-value"} + + BedrockAgentCoreContext.set_request_headers(headers) + result = BedrockAgentCoreContext.get_request_headers() + + assert result == headers + + def test_get_request_headers_when_none_set(self): + """Test getting request headers when none are set.""" + ctx = contextvars.Context() + + def test_in_new_context(): + return BedrockAgentCoreContext.get_request_headers() + + result = ctx.run(test_in_new_context) + assert result is None + + def test_request_headers_isolation_between_contexts(self): + """Test that request headers are isolated between different contexts.""" + headers1 = {"Authorization": "Bearer token-1"} + headers2 = {"Authorization": "Bearer token-2"} + + # Set headers in current context + BedrockAgentCoreContext.set_request_headers(headers1) + + # Run test in different context + ctx = contextvars.Context() + + def test_in_new_context(): + BedrockAgentCoreContext.set_request_headers(headers2) + return BedrockAgentCoreContext.get_request_headers() + + result_in_new_context = ctx.run(test_in_new_context) + + # Headers should be different in each context + assert BedrockAgentCoreContext.get_request_headers() == headers1 + assert result_in_new_context == headers2 + + def test_empty_request_headers(self): + """Test setting empty request headers.""" + empty_headers = {} + + BedrockAgentCoreContext.set_request_headers(empty_headers) + result = BedrockAgentCoreContext.get_request_headers() + + assert result == empty_headers + + def test_request_headers_with_various_custom_headers(self): + """Test request headers with multiple custom headers.""" + headers = { + "Authorization": "Bearer token-123", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header1": "value1", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Header2": "value2", + "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Special": "special-chars-!@#$%", + } + + BedrockAgentCoreContext.set_request_headers(headers) + result = BedrockAgentCoreContext.get_request_headers() + + assert result == headers + assert len(result) == 4 + + +class TestRequestContext: + """Test RequestContext functionality.""" + + def test_request_context_initialization_with_headers(self): + """Test RequestContext initialization with request headers.""" + headers = {"Authorization": "Bearer test-token", "X-Amzn-Bedrock-AgentCore-Runtime-Custom-Key": "custom-value"} + + context = RequestContext(session_id="test-session-123", request_headers=headers) + + assert context.session_id == "test-session-123" + assert context.request_headers == headers + + def test_request_context_initialization_without_headers(self): + """Test RequestContext initialization without request headers.""" + context = RequestContext(session_id="test-session-456") + + assert context.session_id == "test-session-456" + assert context.request_headers is None + + def test_request_context_initialization_minimal(self): + """Test RequestContext initialization with minimal data.""" + context = RequestContext() + + assert context.session_id is None + assert context.request_headers is None + + def test_request_context_with_empty_headers(self): + """Test RequestContext with empty headers dictionary.""" + context = RequestContext(session_id="test-session-789", request_headers={}) + + assert context.session_id == "test-session-789" + assert context.request_headers == {} diff --git a/uv.lock b/uv.lock index 93d87bc..fb59840 100644 --- a/uv.lock +++ b/uv.lock @@ -1,5 +1,5 @@ version = 1 -revision = 3 +revision = 2 requires-python = ">=3.10" [[package]] @@ -79,8 +79,8 @@ dev = [ [package.metadata] requires-dist = [ - { name = "boto3", specifier = ">=1.39.7" }, - { name = "botocore", specifier = ">=1.39.7" }, + { name = "boto3", specifier = ">=1.40.35" }, + { name = "botocore", specifier = ">=1.40.35" }, { name = "pydantic", specifier = ">=2.0.0,<3.0.0" }, { name = "starlette", specifier = ">=0.46.2" }, { name = "strands-agents", marker = "extra == 'strands-agents'", specifier = ">=1.1.0" }, @@ -106,30 +106,30 @@ dev = [ [[package]] name = "boto3" -version = "1.39.7" +version = "1.40.35" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, { name = "jmespath" }, { name = "s3transfer" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/9c/43/81ae62386917fa163f1d3bc59e77da56924f9824b5100fd2807e74a675fc/boto3-1.39.7.tar.gz", hash = "sha256:28daeb005e3381808e0e12995056ee8951056ddd43506c07482a15b40ae785b0", size = 111820, upload-time = "2025-07-16T16:29:52.858Z" } +sdist = { url = "https://files.pythonhosted.org/packages/08/d0/9082261eb9afbb88896fa2ce018fa10750f32572ab356f13f659761bc5b5/boto3-1.40.35.tar.gz", hash = "sha256:d718df3591c829bcca4c498abb7b09d64d1eecc4e5a2b6cef14b476501211b8a", size = 111563, upload-time = "2025-09-19T19:41:07.704Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cc/60/1d2d708f5bc125fe6fe262ea767c82020ea6deeda113e989cff5ab89a9f9/boto3-1.39.7-py3-none-any.whl", hash = "sha256:c8c0e11fff7bb85f903b860b6bfd4f509a4d749decf38bb6a409ffe5d6eb0c91", size = 139882, upload-time = "2025-07-16T16:29:51.176Z" }, + { url = "https://files.pythonhosted.org/packages/db/26/08d814db09dc46eab747c7ebe1d4af5b5158b68e1d7de82ecc71d419eab3/boto3-1.40.35-py3-none-any.whl", hash = "sha256:f4c1b01dd61e7733b453bca38b004ce030e26ee36e7a3d4a9e45a730b67bc38d", size = 139346, upload-time = "2025-09-19T19:41:05.929Z" }, ] [[package]] name = "botocore" -version = "1.39.7" +version = "1.40.35" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "jmespath" }, { name = "python-dateutil" }, { name = "urllib3" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/e7/9d/73f300c841a3c47d2baf4bf6ecb9d6de476edf91de8c3c26ceed5044e666/botocore-1.39.7.tar.gz", hash = "sha256:431e342ef97ecb387cea9df1ae8c4e0edc1b0c9c50d2e121cca77699f24f8dc1", size = 14201331, upload-time = "2025-07-16T16:29:43.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/da/6f/37f40da07f3cdde367f620874f76b828714409caf8466def65aede6bdf59/botocore-1.40.35.tar.gz", hash = "sha256:67e062752ff579c8cc25f30f9c3a84c72d692516a41a9ee1cf17735767ca78be", size = 14350022, upload-time = "2025-09-19T19:40:56.781Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/30/20/ab70de7441cbc4b8ffc5d6d9a8e02e4c703cc07accb7441ce54020e20950/botocore-1.39.7-py3-none-any.whl", hash = "sha256:1d11ba9f3cb46856bb541ed010db160093201a224d21ef854249513ae3af7e77", size = 13864168, upload-time = "2025-07-16T16:29:37.114Z" }, + { url = "https://files.pythonhosted.org/packages/42/f4/9942dfb01a8a849daac34b15d5b7ca994c52ef131db2fa3f6e6995f61e0a/botocore-1.40.35-py3-none-any.whl", hash = "sha256:c545de2cbbce161f54ca589fbb677bae14cdbfac7d5f1a27f6a620cb057c26f4", size = 14020774, upload-time = "2025-09-19T19:40:53.498Z" }, ] [[package]] @@ -1326,14 +1326,14 @@ wheels = [ [[package]] name = "s3transfer" -version = "0.13.0" +version = "0.14.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "botocore" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ed/5d/9dcc100abc6711e8247af5aa561fc07c4a046f72f659c3adea9a449e191a/s3transfer-0.13.0.tar.gz", hash = "sha256:f5e6db74eb7776a37208001113ea7aa97695368242b364d73e91c981ac522177", size = 150232, upload-time = "2025-05-22T19:24:50.245Z" } +sdist = { url = "https://files.pythonhosted.org/packages/62/74/8d69dcb7a9efe8baa2046891735e5dfe433ad558ae23d9e3c14c633d1d58/s3transfer-0.14.0.tar.gz", hash = "sha256:eff12264e7c8b4985074ccce27a3b38a485bb7f7422cc8046fee9be4983e4125", size = 151547, upload-time = "2025-09-09T19:23:31.089Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/18/17/22bf8155aa0ea2305eefa3a6402e040df7ebe512d1310165eda1e233c3f8/s3transfer-0.13.0-py3-none-any.whl", hash = "sha256:0148ef34d6dd964d0d8cf4311b2b21c474693e57c2e069ec708ce043d2b527be", size = 85152, upload-time = "2025-05-22T19:24:48.703Z" }, + { url = "https://files.pythonhosted.org/packages/48/f0/ae7ca09223a81a1d890b2557186ea015f6e0502e9b8cb8e1813f1d8cfa4e/s3transfer-0.14.0-py3-none-any.whl", hash = "sha256:ea3b790c7077558ed1f02a3072fb3cb992bbbd253392f4b6e9e8976941c7d456", size = 85712, upload-time = "2025-09-09T19:23:30.041Z" }, ] [[package]]