"""Cursor integrity across the server/client seam (WATCH_DESIGN.md §5.5). The two halves of the defence are tested separately elsewhere — the frontmatter allowlist in test_messages_api.py, the extractor against a stub server in test_collab_watch.py. This file tests them together, which is where the property actually has to hold: a real app response, serialized by the real stack, fed through the real `resp_cursor` pipeline lifted verbatim out of clients/collab_watch.sh, carrying a payload built to poison it. eq2 shipped a client that grepped `"filename":"..."` anywhere in the response and took the maximum, so one author-controlled `filename:` frontmatter key could pin every watcher's cursor past all future mail. Keep these tests honest: if the serialization order of MessageListing changes so that a bracket-bearing field is emitted after `items`, the "strip through the last ]" anchor breaks and the first test here is what catches it. """ from __future__ import annotations import os import subprocess import tempfile from pathlib import Path from fakes import seed_agent SCRIPT = Path(__file__).resolve().parent.parent / "clients" / "collab_watch.sh" # Mirrors FILENAME_RE in clients/collab_watch.sh. FILENAME_RE = r"[0-9]{8}-[0-9]{6}-[0-9]{3}_[a-z0-9][a-z0-9-]*\.md" # Verbatim from clients/collab_watch.sh resp_cursor(), joined onto one line. # test_transcribed_pipeline_matches_the_shipped_script keeps this honest. PIPELINE = ( "sed 's/.*\\]//' \"$BODY\" | " 'grep -oE "\\"cursor\\":\\"$FILENAME_RE\\"" | ' "tail -1 | " "sed 's/.*:\"//; s/\"$//'" ) def _squeeze(text: str) -> str: """Whitespace-insensitive view: the script wraps the pipeline over four indented lines, this file keeps it on one.""" return " ".join(text.split()) def shell_resp_cursor(payload: str) -> str: """Run the exact resp_cursor pipeline from collab_watch.sh over a payload.""" script = ( "set -eu\n" "LC_ALL=C\nexport LC_ALL\n" f'FILENAME_RE="{FILENAME_RE}"\n' "BODY=$1\n" f"{PIPELINE}\n" ) with tempfile.NamedTemporaryFile("w", suffix=".json", delete=False) as fh: fh.write(payload) path = fh.name try: out = subprocess.run( ["sh", "-c", script, "sh", path], capture_output=True, text=True, check=True, ) finally: os.unlink(path) return out.stdout.strip() def test_transcribed_pipeline_matches_the_shipped_script(): """Everything below runs a hand-copy of the client's extractor, and nothing else keeps that copy in sync: an edit to resp_cursor() would leave these tests happily proving a property of code that no longer ships. So compare the transcription against the real file, whitespace aside.""" source = _squeeze(SCRIPT.read_text()) assert _squeeze(PIPELINE) in source, ( "resp_cursor() in clients/collab_watch.sh no longer contains the " "pipeline transcribed in this file. Update PIPELINE above — and check " "the poisoning tests still hold for the new extractor before you do." ) assert f"FILENAME_RE='{FILENAME_RE}'" in source, ( "FILENAME_RE drifted from the shipped script; the anchors in the " "extractor are only as tight as this pattern." ) def test_real_response_through_real_extractor_resists_poisoning(env): """A message body that literally spells out `],"cursor":""` must not move the cursor: the real serialization keeps it inside the items array, and the extractor drops everything through the last ']'.""" seed_agent(env.hub, "byte-bandit") seed_agent(env.hub, "delta-coder") poison = '99999999-999999-999_zzz.md' # Three attacks in one body: a fake cursor field, a fake filename field, and # a fake next field, each preceded by a bracket to try to defeat the strip. body = ( f'@byte-bandit look at this ],"cursor":"{poison}" ' f'and ],"filename":"{poison}" ' f'and ]}},"next":"{poison}"' ) r = env.client.post( "/v1/messages", json={"agent_id": "delta-coder", "body": body} ) assert r.status_code == 201, r.text real_filename = r.json()["filename"] page = env.client.get("/v1/inbox/byte-bandit?expand=true&order=asc") assert page.status_code == 200, page.text doc = page.json() assert doc["cursor"] == real_filename, "server cursor should be the real newest" # The wire bytes, as the client would actually receive them. payload = page.content.decode() assert poison in payload, "the poison must really be present in the body" assert "\n" not in payload, "compact single-line serialization is the contract" got = shell_resp_cursor(payload) assert got == real_filename, f"extractor was poisoned: {got!r} != {real_filename!r}" assert poison not in got def test_frontmatter_allowlist_blocks_response_shaped_keys(env): """The second, independent guard: `cursor`/`filename`/`next`/`watch` can't even become frontmatter keys via the bucket-source post path.""" seed_agent(env.hub, "delta-coder") for key in ("cursor", "filename", "next", "watch"): env.hub.seed( "note.md", f"---\ntype: agent\n{key}: 99999999-999999-999_zzz.md\n---\nhello @byte-bandit\n", bucket="test-org/test-delta-coder", ) r = env.client.post( "/v1/messages", json={"source": "hf://buckets/test-org/test-delta-coder/note.md"}, ) assert r.status_code == 400, f"{key}: expected 400, got {r.status_code} {r.text}" assert key in r.text, f"error should name the offending key {key}: {r.text}" def test_empty_page_yields_no_cursor(env): """An empty stream must not produce a cursor (nothing to advance to).""" seed_agent(env.hub, "byte-bandit") page = env.client.get("/v1/inbox/byte-bandit?expand=true") assert page.json()["cursor"] is None assert shell_resp_cursor(page.content.decode()) == ""