Skip to content

Commit f9d8e64

Browse files
committed
move warning to diff file
1 parent d93f4dd commit f9d8e64

File tree

4 files changed

+55
-139
lines changed

4 files changed

+55
-139
lines changed

packages/toolbox-core/src/toolbox_core/tool.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -128,17 +128,6 @@ def __init__(
128128
# map of client headers to their value/callable/coroutine
129129
self.__client_headers = client_headers
130130

131-
# ID tokens contain sensitive user information (claims). Transmitting
132-
# these over HTTP exposes the data to interception and unauthorized
133-
# access. Always use HTTPS to ensure secure communication and protect
134-
# user privacy.
135-
if self.__transport.base_url.startswith("http://") and (
136-
required_authn_params or required_authz_tokens or client_headers
137-
):
138-
warn(
139-
"Sending ID token over HTTP. User data may be exposed. Use HTTPS for secure communication."
140-
)
141-
142131
@property
143132
def _name(self) -> str:
144133
return self.__name__

packages/toolbox-core/src/toolbox_core/toolbox_transport.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# limitations under the License.
1414

1515
from typing import Mapping, Optional
16+
from warnings import warn
1617

1718
from aiohttp import ClientSession
1819

@@ -70,6 +71,14 @@ async def tools_list(
7071
async def tool_invoke(
7172
self, tool_name: str, arguments: dict, headers: Mapping[str, str]
7273
) -> dict:
74+
# ID tokens contain sensitive user information (claims). Transmitting
75+
# these over HTTP exposes the data to interception and unauthorized
76+
# access. Always use HTTPS to ensure secure communication and protect
77+
# user privacy.
78+
if self.base_url.startswith("http://") and headers:
79+
warn(
80+
"Sending data token over HTTP. User data may be exposed. Use HTTPS for secure communication."
81+
)
7382
url = f"{self.__base_url}/api/tool/{tool_name}/invoke"
7483
async with self.__session.post(
7584
url,

packages/toolbox-core/tests/test_tool.py

Lines changed: 0 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -622,130 +622,3 @@ def test_toolbox_tool_underscore_client_headers_property(toolbox_tool: ToolboxTo
622622
# Verify immutability
623623
with pytest.raises(TypeError):
624624
client_headers["new_header"] = "new_value"
625-
626-
627-
# --- Test for the HTTP Warning ---
628-
@pytest.mark.parametrize(
629-
"trigger_condition_params",
630-
[
631-
{"client_headers": {"X-Some-Header": "value"}},
632-
{"required_authn_params": {"param1": ["auth-service1"]}},
633-
{"required_authz_tokens": ["auth-service2"]},
634-
{
635-
"client_headers": {"X-Some-Header": "value"},
636-
"required_authn_params": {"param1": ["auth-service1"]},
637-
},
638-
{
639-
"client_headers": {"X-Some-Header": "value"},
640-
"required_authz_tokens": ["auth-service2"],
641-
},
642-
{
643-
"required_authn_params": {"param1": ["auth-service1"]},
644-
"required_authz_tokens": ["auth-service2"],
645-
},
646-
{
647-
"client_headers": {"X-Some-Header": "value"},
648-
"required_authn_params": {"param1": ["auth-service1"]},
649-
"required_authz_tokens": ["auth-service2"],
650-
},
651-
],
652-
ids=[
653-
"client_headers_only",
654-
"authn_params_only",
655-
"authz_tokens_only",
656-
"headers_and_authn",
657-
"headers_and_authz",
658-
"authn_and_authz",
659-
"all_three_conditions",
660-
],
661-
)
662-
def test_tool_init_http_warning_when_sensitive_info_over_http(
663-
http_session: ClientSession,
664-
sample_tool_params: list[ParameterSchema],
665-
sample_tool_description: str,
666-
trigger_condition_params: dict,
667-
):
668-
"""
669-
Tests that a UserWarning is issued if client headers, auth params, or
670-
auth tokens are present and the base_url is HTTP.
671-
"""
672-
expected_warning_message: str = (
673-
"Sending ID token over HTTP. User data may be exposed. "
674-
"Use HTTPS for secure communication."
675-
)
676-
transport = ToolboxTransport(TEST_BASE_URL, http_session)
677-
init_kwargs = {
678-
"transport": transport,
679-
"name": "http_warning_tool",
680-
"description": sample_tool_description,
681-
"params": sample_tool_params,
682-
"required_authn_params": {},
683-
"required_authz_tokens": [],
684-
"auth_service_token_getters": {},
685-
"bound_params": {},
686-
"client_headers": {},
687-
}
688-
# Apply the specific conditions for this parametrized test
689-
init_kwargs.update(trigger_condition_params)
690-
691-
with pytest.warns(UserWarning, match=expected_warning_message):
692-
ToolboxTool(**init_kwargs)
693-
694-
695-
def test_tool_init_no_http_warning_if_https(
696-
http_session: ClientSession,
697-
sample_tool_params: list[ParameterSchema],
698-
sample_tool_description: str,
699-
static_client_header: dict,
700-
):
701-
"""
702-
Tests that NO UserWarning is issued if client headers are present but
703-
the base_url is HTTPS.
704-
"""
705-
with catch_warnings(record=True) as record:
706-
simplefilter("always")
707-
transport = ToolboxTransport(HTTPS_BASE_URL, http_session)
708-
709-
ToolboxTool(
710-
transport=transport,
711-
name="https_tool",
712-
description=sample_tool_description,
713-
params=sample_tool_params,
714-
required_authn_params={},
715-
required_authz_tokens=[],
716-
auth_service_token_getters={},
717-
bound_params={},
718-
client_headers=static_client_header,
719-
)
720-
assert (
721-
len(record) == 0
722-
), f"Expected no warnings, but got: {[f'{w.category.__name__}: {w.message}' for w in record]}"
723-
724-
725-
def test_tool_init_no_http_warning_if_no_sensitive_info_on_http(
726-
http_session: ClientSession,
727-
sample_tool_params: list[ParameterSchema],
728-
sample_tool_description: str,
729-
):
730-
"""
731-
Tests that NO UserWarning is issued if the URL is HTTP but there are
732-
no client headers, auth params, or auth tokens.
733-
"""
734-
with catch_warnings(record=True) as record:
735-
simplefilter("always")
736-
transport = ToolboxTransport(TEST_BASE_URL, http_session)
737-
738-
ToolboxTool(
739-
transport=transport,
740-
name="http_tool_no_sensitive",
741-
description=sample_tool_description,
742-
params=sample_tool_params,
743-
required_authn_params={},
744-
required_authz_tokens=[],
745-
auth_service_token_getters={},
746-
bound_params={},
747-
client_headers={},
748-
)
749-
assert (
750-
len(record) == 0
751-
), f"Expected no warnings, but got: {[f'{w.category.__name__}: {w.message}' for w in record]}"

packages/toolbox-core/tests/test_toolbox_transport.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from typing import AsyncGenerator, Optional, Union
15+
from typing import AsyncGenerator, Mapping, Optional, Union
1616
from unittest.mock import AsyncMock
1717

1818
import pytest
@@ -154,6 +154,48 @@ async def test_tool_invoke_failure(http_session: ClientSession):
154154
assert str(exc_info.value) == "Invalid arguments"
155155

156156

157+
@pytest.mark.asyncio
158+
@pytest.mark.parametrize(
159+
"base_url, headers, should_warn",
160+
[
161+
(
162+
"http://fake-toolbox-server.com",
163+
{"Authorization": "Bearer token"},
164+
True,
165+
),
166+
(
167+
"https://fake-toolbox-server.com",
168+
{"Authorization": "Bearer token"},
169+
False,
170+
),
171+
("http://fake-toolbox-server.com", {}, False),
172+
("http://fake-toolbox-server.com", None, False),
173+
],
174+
)
175+
async def test_tool_invoke_http_warning(
176+
http_session: ClientSession,
177+
base_url: str,
178+
headers: Optional[Mapping[str, str]],
179+
should_warn: bool,
180+
):
181+
"""Tests the HTTP security warning logic in tool_invoke."""
182+
url = f"{base_url}/api/tool/{TEST_TOOL_NAME}/invoke"
183+
args = {"param1": "value1"}
184+
response_payload = {"result": "success"}
185+
transport = ToolboxTransport(base_url, http_session)
186+
187+
with aioresponses() as m:
188+
m.post(url, status=200, payload=response_payload)
189+
190+
if should_warn:
191+
with pytest.warns(UserWarning, match="Sending data token over HTTP"):
192+
await transport.tool_invoke(TEST_TOOL_NAME, args, headers)
193+
else:
194+
# By not using pytest.warns, we assert that no warnings are raised.
195+
# The test will fail if an unexpected UserWarning occurs.
196+
await transport.tool_invoke(TEST_TOOL_NAME, args, headers)
197+
198+
157199
@pytest.mark.asyncio
158200
async def test_close_does_not_close_unmanaged_session():
159201
"""
@@ -175,6 +217,9 @@ async def test_close_closes_managed_session():
175217
managed internally by the transport.
176218
"""
177219
transport = ToolboxTransport(TEST_BASE_URL, session=None)
220+
# Access the internal session before closing to check its state
221+
internal_session = transport._ToolboxTransport__session
222+
assert internal_session.closed is False
178223

179224
await transport.close()
180225
internal_session = transport._ToolboxTransport__session

0 commit comments

Comments
 (0)