From cb9b7c36f37177d2f189efb11fdaacee61bfbcc0 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Thu, 3 Sep 2026 09:36:07 -0700 Subject: [PATCH] review-fix(photon): restore credential_summary() (public on main) + test_credential_summary_no_secret_leak ethernet8023: the no-leak test was deleted alongside the function it pinned; the display-only credential status is live in print_credential_summary. credential_summary restored byte-identical to BASE so the leak contract is CI-pinned again. --- plugins/platforms/photon/auth.py | 33 +++++++++++++++++++++ tests/plugins/platforms/photon/test_auth.py | 25 ++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/plugins/platforms/photon/auth.py b/plugins/platforms/photon/auth.py index d402048e7a..390e7a9094 100644 --- a/plugins/platforms/photon/auth.py +++ b/plugins/platforms/photon/auth.py @@ -713,3 +713,36 @@ def print_credential_summary(emit: Any = print) -> None: " my number : " + (phone if phone else "✗ missing (run `hermes photon setup --phone ...`)"), " assigned number : " + (assigned if assigned else "✗ missing (run `hermes photon setup`)")] emit("\n".join(rows)) + + +def credential_summary() -> Dict[str, str]: + """Return a fully pre-formatted credential status dict (no raw secrets).""" + def _present_token() -> str: + return ( + "✓ stored" if load_photon_token() + else "✗ missing (run `hermes photon setup`)" + ) + + def _present_project_id() -> str: + sid, _sec = load_project_credentials() + return sid or "✗ missing" + + def _present_secret() -> str: + _sid, sec = load_project_credentials() + return "✓ stored" if sec else "✗ missing" + + def _present_phone() -> str: + phone, _assigned = load_user_numbers() + return phone or "✗ missing (run `hermes photon setup --phone ...`)" + + def _present_assigned_phone() -> str: + _phone, assigned = load_user_numbers() + return assigned or "✗ missing (run `hermes photon setup`)" + + return { + "device_token": _present_token(), + "project_id": _present_project_id(), + "project_key": _present_secret(), + "phone_number": _present_phone(), + "assigned_phone_number": _present_assigned_phone(), + } diff --git a/tests/plugins/platforms/photon/test_auth.py b/tests/plugins/platforms/photon/test_auth.py index f6284a7a4d..56e4c23d94 100644 --- a/tests/plugins/platforms/photon/test_auth.py +++ b/tests/plugins/platforms/photon/test_auth.py @@ -327,6 +327,31 @@ def test_get_imessage_line_returns_existing(monkeypatch: pytest.MonkeyPatch) -> assert line is not None and line["phoneNumber"] == "+15559999999" +# --------------------------------------------------------------------------- +# Credential summary (no secret leakage) + +def test_credential_summary_no_secret_leak( + tmp_hermes_home: Path, monkeypatch: pytest.MonkeyPatch, +) -> None: + monkeypatch.setattr(photon_auth, "_persist_runtime_env", lambda *a, **k: None) + photon_auth.store_photon_token("token-aaaaaaaaaaaaaaaa") + photon_auth.store_project_credentials( + spectrum_project_id="sp-uuid", + project_secret="secret-bbbbbbbbbbb", + dashboard_project_id="dash-uuid", + ) + summary = photon_auth.credential_summary() + blob = "\n".join(summary.values()) + assert "token-aaaa" not in blob + assert "secret-bbbb" not in blob + assert summary["device_token"].startswith("✓") + assert summary["project_key"].startswith("✓") + # Unified id: dashboard id == Spectrum id, surfaced as one project id. + assert summary["project_id"] == "sp-uuid" + assert summary["phone_number"].startswith("✗ missing") + assert summary["assigned_phone_number"].startswith("✗ missing") + + # --------------------------------------------------------------------------- # Device-token candidate extraction + dashboard validation.