From 32ca672309b7a7ddda305ca39fd766c712ac2859 Mon Sep 17 00:00:00 2001 From: echobt <154886644+echobt@users.noreply.github.com> Date: Wed, 29 Jul 2026 23:36:24 +0000 Subject: [PATCH] fix(agent-challenge): stop public log redaction from wedging the event loop _PUBLIC_SECRET_SEGMENT_RE nested (?:[A-Za-z0-9_.]+[-_.])* over a character class that also contained the separators _ and ., so any dotted or underscored run that never reached secret/token could be split in exponentially many ways. Measured on the live pattern: 37 characters took 23ms and 61 characters took 91s. Terminal-Bench task events carry pip output, which has exactly that shape, and get_submission_task_events replays them on an unauthenticated endpoint. One log line therefore pinned the validator event loop at 100% CPU, starved the evaluation worker, and left a job stuck running with no containers and no logs for hours. Redaction is now a single linear pass over delimited words plus two bounded marker checks, which keeps the previous redaction decisions unchanged: whole-segment token matches, tokens left alone, secret markers redacted. --- .../src/agent_challenge/api/routes.py | 40 ++++++++++++++----- .../tests/test_public_task_event_redaction.py | 37 +++++++++++++++++ 2 files changed, 68 insertions(+), 9 deletions(-) diff --git a/packages/challenges/agent-challenge/src/agent_challenge/api/routes.py b/packages/challenges/agent-challenge/src/agent_challenge/api/routes.py index 4db180662..300a2507b 100644 --- a/packages/challenges/agent-challenge/src/agent_challenge/api/routes.py +++ b/packages/challenges/agent-challenge/src/agent_challenge/api/routes.py @@ -5049,16 +5049,38 @@ def _is_sensitive_task_event_metadata_key(key: str) -> bool: # Whole-segment secret markers (not substrings of longer words like "tokens"). -_PUBLIC_SECRET_SEGMENT_RE = re.compile( - r"(? str: + """Redact whole words that carry a secret marker on a segment boundary.""" + + def _replace(match: re.Match[str]) -> str: + word = match.group(0) + if _PUBLIC_SECRET_MARKER_RE.search(word): + return "[REDACTED_SECRET]" + if _PUBLIC_TOKEN_SEGMENT_RE.search(word): + return "[REDACTED_SECRET]" + return word + + return _PUBLIC_SECRET_WORD_RE.sub(_replace, value) def _terminal_bench_task_id_shield_pattern() -> re.Pattern[str]: @@ -5105,7 +5127,7 @@ def _shield(match: re.Match[str]) -> str: # Segment-boundary redaction: match secret/token/raw-ref/broker-ref/pod- as # whole hyphen/underscore/dot-delimited segments, not as substrings of a # longer word (``tokens`` must not match ``token``). - sanitized = _PUBLIC_SECRET_SEGMENT_RE.sub("[REDACTED_SECRET]", sanitized) + sanitized = _redact_public_secret_segments(sanitized) for index, original in enumerate(shields): sanitized = sanitized.replace(f"\x00TBSAFE{index}\x00", original) return sanitized diff --git a/packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py b/packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py index e5d1545a5..c5ba07dca 100644 --- a/packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py +++ b/packages/challenges/agent-challenge/tests/test_public_task_event_redaction.py @@ -39,3 +39,40 @@ def test_public_task_event_text_still_redacts_genuine_secrets() -> None: for secret in _GENUINE_SECRET_SHAPES: assert secret not in out, f"secret still visible: {secret!r} in {out!r}" assert "[REDACTED_SECRET]" in out + + +def test_secret_redaction_is_linear_on_dotted_runs() -> None: + """A public log line must never wedge the event loop. + + ``_PUBLIC_SECRET_SEGMENT_RE`` nested ``(?:[A-Za-z0-9_.]+[-_.])*`` over a class + that also contains the separators, so a dotted/underscored run that never + reaches ``secret``/``token`` backtracked exponentially: 37 chars took 23ms and + 61 chars took 91s. pip output carries exactly that shape + ("Successfully installed agent-challenge-1.0.1 aiohappyeyeballs-2.7.1 ..."), + and the endpoint serving it is public, so one log line pinned the validator + at 100% CPU and starved every evaluation. + """ + import time + + from agent_challenge.api.routes import _public_task_event_text + + payload = "a.b_c." * 40 + "!" + started = time.perf_counter() + _public_task_event_text(payload) + elapsed = time.perf_counter() - started + assert elapsed < 1.0, f"redaction took {elapsed:.1f}s on {len(payload)} chars" + + +def test_realistic_pip_output_is_fast_and_unredacted() -> None: + from agent_challenge.api.routes import _public_task_event_text + + line = ( + "Successfully installed agent-challenge-1.0.1 aiohappyeyeballs-2.7.1 " + "aiohttp-3.14.3 aiosignal-1.4.0 async-substrate-interface-2.2.1 " + ) * 6 + import time + + started = time.perf_counter() + out = _public_task_event_text(line) + assert time.perf_counter() - started < 1.0 + assert "agent-challenge-1.0.1" in out