diff --git a/pyproject.toml b/pyproject.toml
index 46edcbc694..5250591d8c 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,6 +41,7 @@ dependencies = [
"graphviz>=0.20.2,<1",
"httpx>=0.27,<1",
"jsonschema>=4.23,<5",
+ "nh3>=0.3,<1",
"opentelemetry-api>=1.39,<=1.42.1",
"opentelemetry-sdk>=1.39,<=1.42.1",
"packaging>=21",
diff --git a/src/google/adk/cli/api_server.py b/src/google/adk/cli/api_server.py
index b61599f6f6..71c724c644 100644
--- a/src/google/adk/cli/api_server.py
+++ b/src/google/adk/cli/api_server.py
@@ -51,6 +51,7 @@
from fastapi.websockets import WebSocket
from fastapi.websockets import WebSocketDisconnect
from google.genai import types
+import nh3
from opentelemetry import trace
import opentelemetry.sdk.environment_variables as otel_env
from opentelemetry.sdk.trace import export as export_lib
@@ -105,6 +106,21 @@
_REGEX_PREFIX = "regex:"
+def _sanitize_html_artifact(artifact: types.Part) -> types.Part:
+ """Sanitize HTML artifact data before returning it to a browser."""
+ inline_data = artifact.inline_data
+ if not inline_data or inline_data.mime_type != "text/html":
+ return artifact
+
+ try:
+ html = inline_data.data.decode("utf-8")
+ except UnicodeDecodeError as exc:
+ raise HTTPException(status_code=422, detail="Invalid HTML encoding") from exc
+
+ inline_data.data = nh3.clean(html).encode("utf-8")
+ return artifact
+
+
def _parse_cors_origins(
allow_origins: list[str],
) -> tuple[list[str], Optional[str]]:
@@ -1684,7 +1700,7 @@ async def load_artifact_version(
)
if not artifact:
raise HTTPException(status_code=404, detail="Artifact not found")
- return artifact
+ return _sanitize_html_artifact(artifact)
@app.get(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts",
@@ -1734,7 +1750,7 @@ async def load_artifact(
)
if not artifact:
raise HTTPException(status_code=404, detail="Artifact not found")
- return artifact
+ return _sanitize_html_artifact(artifact)
@app.delete(
"/apps/{app_name}/users/{user_id}/sessions/{session_id}/artifacts/{artifact_name:path}",
diff --git a/tests/unittests/cli/test_html_sanitization.py b/tests/unittests/cli/test_html_sanitization.py
new file mode 100644
index 0000000000..1f2ca6787e
--- /dev/null
+++ b/tests/unittests/cli/test_html_sanitization.py
@@ -0,0 +1,62 @@
+# Copyright 2026 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import annotations
+
+from google.adk.cli.api_server import _sanitize_html_artifact
+from google.genai import types
+import pytest
+
+
+@pytest.mark.parametrize(
+ "payload",
+ (
+ "",
+ '',
+ '
Hello world
", + mime_type="text/html", + ) + + sanitized = _sanitize_html_artifact(artifact).inline_data.data.decode() + + assert "