From 8af8c2eeab324604e7938e4f61726c74ec8a36b6 Mon Sep 17 00:00:00 2001 From: oskar Date: Thu, 27 Aug 2026 15:51:39 +0200 Subject: [PATCH] fix(kb): check_okf.py flags control bytes (NUL etc.) outside \t\n\r MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The validator only ever parsed frontmatter fields — it never scanned body content, so a file with literal NUL bytes pasted in from a psql SELECT (kb/audits/wiki-kompilat-recon-2026-08-26.md) passed silently. Adds a whole-file scan for control bytes other than tab/newline/CR, one error per offending line. Covered by scripts/kb/tests/test_check_okf.py. --- scripts/kb/check_okf.py | 14 +++++++ scripts/kb/tests/test_check_okf.py | 62 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 scripts/kb/tests/test_check_okf.py diff --git a/scripts/kb/check_okf.py b/scripts/kb/check_okf.py index bbf96e7..976d269 100755 --- a/scripts/kb/check_okf.py +++ b/scripts/kb/check_okf.py @@ -18,6 +18,8 @@ reguły tego repo: 10. Wpisy `contradicts` wyglądające jak ścieżka .md też muszą istnieć; pozostałe wpisy to wolny tekst. 11. `stub` — o ile obecne — musi być boolem. + 12. Brak bajtów kontrolnych poza \t \n \r w całym pliku (łapie wklejki NUL/inne + binarne śmieci wklejone z zewnętrznych źródeł, np. wyników SELECT-ów). Zakres domyślny: kb/ oraz docs/sessions/, z wyłączeniem README-wskaźników (POINTER_GLOBS) — te są nawigacją do kb-doca, nie dokumentami KB, i celowo nie @@ -71,6 +73,7 @@ POINTER_GLOBS = ( EXCLUDE_DIRS = ("build",) DATE_RE = re.compile(r"^\d{4}-\d{2}-\d{2}$") +CONTROL_CHAR_RE = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]") def is_pointer(rel: str) -> bool: @@ -232,6 +235,17 @@ def check_file(path: Path, root: Path) -> list[str]: f"{rel}: `contradicts` wskazuje na nieistniejący plik: {item}" ) + seen_lines: set[int] = set() + for m in CONTROL_CHAR_RE.finditer(text): + line_no = text.count("\n", 0, m.start()) + 1 + if line_no in seen_lines: + continue + seen_lines.add(line_no) + errors.append( + f"{rel}: bajt kontrolny {ord(m.group()):#04x} w linii {line_no} " + "(dozwolone tylko \\t \\n \\r)" + ) + return errors diff --git a/scripts/kb/tests/test_check_okf.py b/scripts/kb/tests/test_check_okf.py new file mode 100644 index 0000000..246633a --- /dev/null +++ b/scripts/kb/tests/test_check_okf.py @@ -0,0 +1,62 @@ +"""Tests for check_okf.py — the OKF v0.1 frontmatter validator. + +Covers the control-character check added 2026-08-27: a file that otherwise has +valid frontmatter must still fail if its body contains a NUL byte or other +control byte outside \\t \\n \\r (the class of bug found in +kb/audits/wiki-kompilat-recon-2026-08-26.md — raw bytes pasted in from a psql +SELECT over the mail corpus). +""" +from __future__ import annotations + +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) +import check_okf # noqa: E402 + +VALID_FRONTMATTER = """--- +okf: "0.1" +type: decision +visibility: private +status: active +updated: 2026-08-27 +links: [] +--- +""" + + +def _write(tmp_path: Path, body: str) -> Path: + p = tmp_path / "doc.md" + p.write_text(VALID_FRONTMATTER + body, encoding="utf-8") + return p + + +def test_clean_file_has_no_control_char_errors(tmp_path): + path = _write(tmp_path, "# Title\n\nZwykła treść z \t tabulatorem i \r\n końcem linii.\n") + errors = check_okf.check_file(path, tmp_path) + assert errors == [] + + +def test_nul_byte_in_body_is_flagged(tmp_path): + path = _write(tmp_path, "# Title\n\nZawiera bajt NUL: \x00 w tym miejscu.\n") + errors = check_okf.check_file(path, tmp_path) + assert any("bajt kontrolny" in e and "0x0" in e for e in errors) + + +def test_control_char_error_reports_correct_line(tmp_path): + body = "linia 1\nlinia 2\nzepsuta \x00 linia 3\nlinia 4\n" + path = _write(tmp_path, body) + errors = check_okf.check_file(path, tmp_path) + control_errors = [e for e in errors if "bajt kontrolny" in e] + assert len(control_errors) == 1 + # Frontmatter occupies 7 lines before the body starts. + frontmatter_lines = VALID_FRONTMATTER.count("\n") + expected_line = frontmatter_lines + 3 + assert f"w linii {expected_line}" in control_errors[0] + + +def test_multiple_control_chars_same_line_reported_once(tmp_path): + path = _write(tmp_path, "para \x00 z dwoma \x00 bajtami na tej samej linii\n") + errors = check_okf.check_file(path, tmp_path) + control_errors = [e for e in errors if "bajt kontrolny" in e] + assert len(control_errors) == 1