test(e2e): report safe OAuth failure locations

This commit is contained in:
Joshua Valluru 2026-09-19 12:52:28 -07:00
parent 4e8a4d4b61
commit b7bab56d4d
3 changed files with 44 additions and 0 deletions

View file

@ -1,4 +1,5 @@
import os
import re
import sys
import xml.etree.ElementTree as ET
from pathlib import Path
@ -45,6 +46,13 @@ def main() -> int:
if case.get("file") != path or all(case.find(tag) is None for tag in ("failure", "error")):
continue
_ = sys.stdout.write(f" failed: {case.get('classname', '')}::{case.get('name', '')}\n")
for prop in case.findall("./properties/property"):
name = prop.get("name", "")
value = prop.get("value", "")
if name in ("oauth_failure_phase", "oauth_exception_type", "oauth_frame") and re.fullmatch(
r"[A-Za-z0-9_.:<>-]{1,240}", value
):
_ = sys.stdout.write(f" {name}: {value}\n")
if (
selected
and not missing

View file

@ -226,3 +226,31 @@ def test_an_unusable_secret_is_named_without_printing_its_value(
assert unprintable not in result.stderr
assert result.stdout == ""
assert not env_path.exists()
@pytest.mark.parametrize("phase", ("setup", "call", "teardown"))
def test_oauth_failure_diagnostics_do_not_publish_private_payloads(tmp_path: Path, phase: str) -> None:
suite: Final = ET.Element("testsuite")
case: Final = ET.SubElement(suite, "testcase", file=SELECTED[0])
private: Final = "private-token-in-exception-message"
failure: Final = ET.SubElement(case, "failure", message=private)
failure.text = private
properties: Final = ET.SubElement(case, "properties")
for name, value in (
("oauth_failure_phase", phase),
("oauth_exception_type", "AssertionError"),
("oauth_frame", "oauth_gateway.py:120:start"),
("oauth_frame", f"injected\\n{private}"),
("unrelated_property", private),
):
_ = ET.SubElement(properties, "property", name=name, value=value)
report: Final = tmp_path / "report.xml"
ET.ElementTree(suite).write(report)
result: Final = subprocess.run(
[sys.executable, str(GATE), str(report), SELECTED[0]], capture_output=True, text=True
)
assert result.returncode == 1
assert f"oauth_failure_phase: {phase}" in result.stdout
assert "oauth_exception_type: AssertionError" in result.stdout
assert "oauth_frame: oauth_gateway.py:120:start" in result.stdout
assert private not in result.stdout + result.stderr

View file

@ -17,6 +17,7 @@ import functools
import os
from collections.abc import Generator, Iterator
from datetime import datetime, timezone
from pathlib import Path
from types import MappingProxyType
from typing import Final
@ -245,6 +246,13 @@ def pytest_runtest_makereport(
"""Stash the call-phase outcome so teardown can tell a passed test from a
failed one without re-deriving it."""
report = yield
if item.get_closest_marker("mcp_oauth_live") is not None and call.excinfo is not None:
# Publish code locations only, never exception messages, source text or locals.
item.user_properties.append(("oauth_failure_phase", report.when))
item.user_properties.append(("oauth_exception_type", call.excinfo.type.__name__))
for entry in call.excinfo.traceback:
item.user_properties.append(("oauth_frame", f"{Path(entry.path).name}:{entry.lineno + 1}:{entry.name}"))
report.user_properties = list(item.user_properties)
if report.when == "call":
item.stash[_CALL_PASSED] = report.passed
return report