Skip to content

Commit c7fbec2

Browse files
committed
Studio: require ASCII evidence for declared Latin-1/cp1252 web fetches
Latin-1 and cp1252 decode every byte to a printable character, so a high-byte binary body declared as iso-8859-1/windows-1252 decoded cleanly and slipped past the control-character binary check. Apply the existing ASCII-structure gate to those declared decodes as well. Scoped to the Latin family so legitimate non-Latin single-byte pages (Cyrillic, Greek) are not rejected.
1 parent 4eb1283 commit c7fbec2

2 files changed

Lines changed: 43 additions & 0 deletions

File tree

studio/backend/core/inference/tools.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3890,6 +3890,14 @@ def _fetch_page_text(
38903890
raw_html = alt
38913891
else:
38923892
return f"(binary content, {len(raw_bytes)} bytes; not readable as text)"
3893+
elif declared_codec in ("iso8859-1", "cp1252") and not (
3894+
_has_single_byte_text_evidence(raw_bytes)
3895+
):
3896+
# Latin-1/cp1252 map every byte to a printable character, so high-byte
3897+
# binary decodes cleanly and slips past _looks_binary; require ASCII text
3898+
# structure. Other single-byte charsets are left alone so legitimate
3899+
# non-Latin pages (Cyrillic, Greek, ...) are not rejected.
3900+
return f"(binary content, {len(raw_bytes)} bytes; not readable as text)"
38933901
except _HTTPError as e:
38943902
return f"Failed to fetch URL: HTTP {e.code} {getattr(e, 'reason', '')}"
38953903
except Exception as e:

studio/backend/tests/test_web_fetch_binary_guard.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,3 +264,38 @@ def test_text_with_a_few_stray_replacement_chars_kept(monkeypatch):
264264
out = _fetch_with(monkeypatch, body, "text/html")
265265
assert "Real article text." in out
266266
assert "binary content" not in out
267+
268+
269+
@pytest.mark.parametrize("charset", ["iso-8859-1", "latin-1", "windows-1252", "cp1252"])
270+
def test_declared_latin1_high_byte_binary_rejected(monkeypatch, charset):
271+
# Latin-1/cp1252 decode high bytes to printable chars, so binary evades the
272+
# control-char check; the ASCII-structure gate must still reject it.
273+
body = bytes(range(0xA0, 0x100)) * 40
274+
out = _fetch_with(monkeypatch, body, f"text/plain; charset={charset}")
275+
assert "binary content" in out
276+
277+
278+
@pytest.mark.parametrize("charset", ["iso-8859-1", "windows-1252"])
279+
def test_declared_latin1_real_text_still_kept(monkeypatch, charset):
280+
# Genuine accented Western text is ASCII-dominated and must not be rejected.
281+
body = ("Cafe un tres bon eleve a l ecole. MARKERWORD ".replace("e", "é") + "voila ").encode(
282+
"cp1252"
283+
) * 40
284+
out = _fetch_with(monkeypatch, body, f"text/plain; charset={charset}")
285+
assert "MARKERWORD" in out
286+
assert "binary content" not in out
287+
288+
289+
@pytest.mark.parametrize(
290+
"charset,codec,sample",
291+
[
292+
("windows-1251", "cp1251", "Это настоящая русская статья. Привет. "),
293+
("koi8-r", "koi8-r", "Это настоящая русская статья. Привет. "),
294+
("iso-8859-7", "iso-8859-7", "Αυτό είναι ένα ελληνικό άρθρο. "),
295+
],
296+
)
297+
def test_declared_non_latin_single_byte_text_kept(monkeypatch, charset, codec, sample):
298+
# Cyrillic/Greek single-byte pages are high-byte dense; the Latin-1 ASCII
299+
# gate must not touch them or legitimate non-Western text would be dropped.
300+
out = _fetch_with(monkeypatch, (sample * 40).encode(codec), f"text/plain; charset={charset}")
301+
assert "binary content" not in out

0 commit comments

Comments
 (0)