Skip to content

Commit b689c82

Browse files
authored
fix(python): add encoding=utf-8 to file reads and writes in sandbox.py (#1912)
* fix(python): add encoding=utf-8 to all file reads and writes in sandbox.py read_text(), fdopen() without encoding use the system locale, which can differ on non-UTF-8 systems. All config files (metadata.json, oidc_token.json, active_gateway) are UTF-8 text. * fix(python): catch UnicodeDecodeError in _read_oidc_token_bundle, add encoding tests Corrupt or non-UTF-8 oidc_token.json now returns None consistently. Add regression tests for non-ASCII UTF-8 paths in metadata, oidc token, and active_gateway files. * fix(python): use ensure_ascii=False in encoding tests, add from_active_cluster UTF-8 bytes test * fix(python): assert non-ASCII cluster name and endpoint in encoding test * fix(python): wrap long write_bytes line for ruff format * fix(python): apply ruff format to sandbox_test.py
1 parent b6428cb commit b689c82

2 files changed

Lines changed: 73 additions & 5 deletions

File tree

‎python/openshell/sandbox.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def from_active_cluster(
318318
gateway_dir = _xdg_config_home() / "openshell" / "gateways" / cluster_name
319319
metadata_path = gateway_dir / "metadata.json"
320320
try:
321-
metadata = json.loads(metadata_path.read_text())
321+
metadata = json.loads(metadata_path.read_text(encoding="utf-8"))
322322
except FileNotFoundError:
323323
raise SandboxError(f"gateway '{cluster_name}' not found") from None
324324
if "gateway_endpoint" not in metadata:
@@ -846,10 +846,10 @@ def _read_oidc_token_bundle(gateway_dir: pathlib.Path) -> dict | None:
846846
"""
847847
token_path = gateway_dir / "oidc_token.json"
848848
try:
849-
return json.loads(token_path.read_text())
849+
return json.loads(token_path.read_text(encoding="utf-8"))
850850
except FileNotFoundError:
851851
return None
852-
except (OSError, json.JSONDecodeError):
852+
except (OSError, UnicodeDecodeError, json.JSONDecodeError):
853853
return None
854854

855855

@@ -1299,7 +1299,7 @@ def _write_to_disk(self, bundle: dict) -> None:
12991299
)
13001300
tmp_path = pathlib.Path(tmp_name)
13011301
try:
1302-
with os.fdopen(fd, "w") as f:
1302+
with os.fdopen(fd, "w", encoding="utf-8") as f:
13031303
f.write(payload)
13041304
with contextlib.suppress(OSError):
13051305
tmp_path.chmod(0o600)
@@ -1374,7 +1374,7 @@ def _resolve_active_cluster() -> str:
13741374
return env_gateway
13751375
active_file = _xdg_config_home() / "openshell" / "active_gateway"
13761376
try:
1377-
value = active_file.read_text().strip()
1377+
value = active_file.read_text(encoding="utf-8").strip()
13781378
except FileNotFoundError:
13791379
raise SandboxError("no active gateway configured") from None
13801380
if value == "":

‎python/openshell/sandbox_test.py‎

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
_make_cluster_bearer_provider,
2525
_normalize_bearer,
2626
_OidcRefresher,
27+
_read_oidc_token_bundle,
2728
)
2829

2930

@@ -1345,3 +1346,70 @@ def test_inference_set_cluster_forwards_no_verify_flag() -> None:
13451346

13461347
assert stub.request is not None
13471348
assert stub.request.no_verify is True
1349+
1350+
1351+
# ---------------------------------------------------------------------------
1352+
# Encoding regression tests (utf-8 explicit on all config file reads/writes)
1353+
# ---------------------------------------------------------------------------
1354+
1355+
1356+
def test_read_oidc_token_bundle_parses_non_ascii_utf8(tmp_path: Path) -> None:
1357+
gateway_dir = tmp_path / "gw"
1358+
gateway_dir.mkdir()
1359+
payload = {"refresh_token": "tok", "issuer": "https://example.com/é"}
1360+
(gateway_dir / "oidc_token.json").write_bytes(
1361+
json.dumps(payload, ensure_ascii=False).encode("utf-8")
1362+
)
1363+
result = _read_oidc_token_bundle(gateway_dir)
1364+
assert result == payload
1365+
1366+
1367+
def test_read_oidc_token_bundle_returns_none_on_corrupt_bytes(tmp_path: Path) -> None:
1368+
gateway_dir = tmp_path / "gw"
1369+
gateway_dir.mkdir()
1370+
(gateway_dir / "oidc_token.json").write_bytes(b"\xff\xfe not utf-8")
1371+
assert _read_oidc_token_bundle(gateway_dir) is None
1372+
1373+
1374+
def test_load_cluster_bearer_token_handles_non_ascii_utf8_oidc(tmp_path: Path) -> None:
1375+
gateway_dir = tmp_path / "gw"
1376+
gateway_dir.mkdir()
1377+
bundle = {
1378+
"access_token": "accéss",
1379+
"refresh_token": "ref",
1380+
"expiry": "2099-01-01T00:00:00Z",
1381+
"issuer": "https://example.com",
1382+
"client_id": "c",
1383+
"client_secret": "s",
1384+
}
1385+
(gateway_dir / "oidc_token.json").write_bytes(
1386+
json.dumps(bundle, ensure_ascii=False).encode("utf-8")
1387+
)
1388+
token = _load_cluster_bearer_token(gateway_dir)
1389+
assert token == "accéss"
1390+
1391+
1392+
def test_from_active_cluster_reads_utf8_bytes_from_active_gateway_and_metadata(
1393+
tmp_path: Path,
1394+
monkeypatch: Any,
1395+
) -> None:
1396+
gateway_name = "gw-é"
1397+
gateway_dir = tmp_path / "openshell" / "gateways" / gateway_name
1398+
gateway_dir.mkdir(parents=True)
1399+
(tmp_path / "openshell" / "active_gateway").write_bytes(
1400+
gateway_name.encode("utf-8")
1401+
)
1402+
meta = {"gateway_endpoint": "http://tést.example:8080"}
1403+
(gateway_dir / "metadata.json").write_bytes(
1404+
json.dumps(meta, ensure_ascii=False).encode("utf-8")
1405+
)
1406+
1407+
monkeypatch.setenv("XDG_CONFIG_HOME", str(tmp_path))
1408+
monkeypatch.delenv("OPENSHELL_GATEWAY", raising=False)
1409+
1410+
client = SandboxClient.from_active_cluster()
1411+
try:
1412+
assert client._cluster_name == gateway_name
1413+
assert client._endpoint == "tést.example:8080"
1414+
finally:
1415+
client.close()

0 commit comments

Comments
 (0)