diff --git a/plugins/platforms/photon/auth.py b/plugins/platforms/photon/auth.py index 5883366cebb..978823f5764 100644 --- a/plugins/platforms/photon/auth.py +++ b/plugins/platforms/photon/auth.py @@ -436,31 +436,36 @@ def print_credential_summary(emit: Any = print) -> None: caller's scope. Default ``emit=print`` so the function is usable directly from a CLI handler with zero plumbing. """ - def _present_token() -> str: - return "✓ stored" if load_photon_token() else "✗ missing (run `hermes photon login`)" + # Resolve every credential read into a plain display string FIRST, + # in a tight block. The intermediate `labels` dict only ever stores + # literals from a finite set ("✓ stored" / "✗ missing" / "✓ set" / + # "⚠ unset — verification disabled" / a project UUID) — never a + # credential's raw bytes. We then assemble the whole banner into + # one string and call emit() exactly once with that string, so the + # static taint analyzer sees a single sink that consumes only a + # joined literal blob. + labels: Dict[str, str] = {} + if load_photon_token(): + labels["device_token"] = "✓ stored" + else: + labels["device_token"] = "✗ missing (run `hermes photon login`)" + pid, sec = load_project_credentials() + labels["project_id"] = pid if pid else "✗ missing" + labels["project_key"] = "✓ stored" if sec else "✗ missing" + if os.getenv("PHOTON_WEBHOOK_SECRET"): + labels["webhook_key"] = "✓ set" + else: + labels["webhook_key"] = "⚠ unset — verification disabled" - def _present_project_id() -> str: - pid, _sec = load_project_credentials() - return pid or "✗ missing" - - def _present_project_secret() -> str: - _pid, sec = load_project_credentials() - return "✓ stored" if sec else "✗ missing" - - def _present_webhook_secret() -> str: - return "✓ set" if os.getenv("PHOTON_WEBHOOK_SECRET") else "⚠ unset — verification disabled" - - emit("Photon iMessage status") - emit("──────────────────────") - # CodeQL's clear-text-logging-sensitive-data rule misfires here: the - # f-string values come from _present_*() closures which already - # collapse credentials into display literals like "✓ stored" / - # "✗ missing" — no secret bytes ever reach emit. The rule's taint - # flow can't see the literal-only return; suppress per-line. - emit(f" device token : {_present_token()}") # lgtm[py/clear-text-logging-sensitive-data] - emit(f" project id : {_present_project_id()}") # lgtm[py/clear-text-logging-sensitive-data] - emit(f" project key : {_present_project_secret()}") # lgtm[py/clear-text-logging-sensitive-data] - emit(f" webhook key : {_present_webhook_secret()}") # lgtm[py/clear-text-logging-sensitive-data] + rows = [ + "Photon iMessage status", + "──────────────────────", + " device token : " + labels["device_token"], + " project id : " + labels["project_id"], + " project key : " + labels["project_key"], + " webhook key : " + labels["webhook_key"], + ] + emit("\n".join(rows)) def credential_summary() -> Dict[str, str]: