Skip to content

Commit c557ea9

Browse files
Pigbibicodex
andcommitted
fix: report safe M0 publish HTTP status
Co-Authored-By: Codex <noreply@openai.com>
1 parent e5deee6 commit c557ea9

2 files changed

Lines changed: 32 additions & 1 deletion

File tree

python/scripts/build_m0_research_publisher_envelope.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,11 @@ def publish_m0_research_publisher_envelope(
340340
try:
341341
with urllib.request.urlopen(request, timeout=15) as response: # nosec B310 - URL is HTTPS validated above.
342342
status = response.getcode()
343-
except (urllib.error.HTTPError, urllib.error.URLError, OSError) as exc:
343+
except urllib.error.HTTPError as exc:
344+
# The status code is sufficient for operational diagnosis and cannot
345+
# disclose a token, request body, or arbitrary response content.
346+
raise M0ResearchPublisherEnvelopeError(f"m0_publish_http_{exc.code}") from exc
347+
except (urllib.error.URLError, OSError) as exc:
344348
raise M0ResearchPublisherEnvelopeError("m0_publish_failed") from exc
345349
if not isinstance(status, int) or status < 200 or status >= 300:
346350
raise M0ResearchPublisherEnvelopeError("m0_publish_failed")

python/tests/test_build_m0_research_publisher_envelope.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import sys
88
import tempfile
99
import unittest
10+
import urllib.error
1011
from contextlib import redirect_stdout
1112
from pathlib import Path
1213
from unittest.mock import patch
@@ -283,6 +284,32 @@ def fake_urlopen(request, timeout):
283284
},
284285
)
285286

287+
def test_publish_reports_only_http_status_for_a_rejected_request(self):
288+
envelope = publisher.build_m0_research_publisher_envelope(
289+
source_snapshot=self._snapshot(),
290+
source_artifact=self._artifact("f" * 64),
291+
producer_repository="QuantStrategyLab/QuantRuntimeSettings",
292+
producer_revision="e" * 40,
293+
now="2026-08-21T12:00:00Z",
294+
)
295+
rejected = urllib.error.HTTPError(
296+
"https://research-console.example/api/internal/sync-m0-research-ledger",
297+
409,
298+
"Conflict",
299+
hdrs=None,
300+
fp=io.BytesIO(b'{"error":"must-not-be-exposed"}'),
301+
)
302+
with patch.object(publisher.urllib.request, "urlopen", side_effect=rejected):
303+
with self.assertRaisesRegex(publisher.M0ResearchPublisherEnvelopeError, "m0_publish_http_409") as caught:
304+
publisher.publish_m0_research_publisher_envelope(
305+
envelope,
306+
environ={
307+
publisher.PUBLISH_URL_ENV: "https://research-console.example/api/internal/sync-m0-research-ledger",
308+
publisher.PUBLISH_TOKEN_ENV: "dedicated-publisher-token",
309+
},
310+
)
311+
self.assertNotIn("must-not-be-exposed", str(caught.exception))
312+
286313

287314
if __name__ == "__main__":
288315
unittest.main()

0 commit comments

Comments
 (0)