Nothing open on GitHub. Fix merged in #58501 on 4 Jul 2026. Check it covers your case.
The short answer
- Known?
- Yes. 3 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #58501. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and PRs address a gap in token redaction logic where Fireworks API key prefixes could bypass bare-token redaction. The work involves extending redaction coverage to handle Fireworks-specific token formats, with at least one fix also touching Telegram transport error redaction.
Open issues0
Open PRs0
Fixes on record
-
#58501 fix(redact): cover Fireworks token prefixes + redact Telegram transport errors (#58466, #58377 salvage)
files:
agent/redact.py,plugins/platforms/telegram/adapter.py,scripts/release.py,tests/agent/test_redact.py,tests/gateway/test_telegram_rich_messages.pyShow the diff
-
agent/redact.py@@ -107,7 +107,9 @@ r"brv_[A-Za-z0-9]{10,}", # ByteRover API key r"xai-[A-Za-z0-9]{30,}", # xAI (Grok) API key r"ntn_[A-Za-z0-9]{10,}", # Notion internal integration token + r"fw-[A-Za-z0-9]{30,}", # Fireworks AI API key r"fw_[A-Za-z0-9]{30,}", # Fireworks AI API key + r"fpk_[A-Za-z0-9]{30,}", # Fireworks AI project key ] # ENV assignment patterns: KEY=value where KEY contains a secret-like name. -
plugins/platforms/telegram/adapter.py@@ -22,6 +22,19 @@ logger = logging.getLogger(__name__) +def _redact_telegram_error_text(error: object) -> str: + """Redact secrets from Telegram transport errors before logging or returning them.""" + text = "" if error is None else str(error) + if not text: + return text + try: + from agent.redact import redact_sensitive_text + + return redact_sensitive_text(text, force=True) + except Exception: + return "<telegram error redacted>" + + def _consume_abandoned_task(task: asyncio.Task) -> None: """Observe a detached task's terminal exception to avoid noisy loop logs.""" try: @@ -1609,13 +1622,14 @@ async def _try_send_rich( _m = _re.search(r"retry\s+(?:in\s+)?(\d+)", err_str, _re.IGNORECASE) if _m: _retry_after = float(_m.group(1)) + safe_error = _redact_telegram_error_text(exc) logger.warning( "[%s] sendRichMessage transient failure (no legacy resend): %s", - self.name, exc, + self.name, safe_error, ) return SendResult( success=False, - error=str(exc), + error=safe_error, retryable=(is_connect_timeout or not is_timeout), retry_after=_retry_after, ) @@ -1704,13 +1718,14 @@ async def _try_edit_rich( _TimedOut = None is_timeout = (_TimedOut and isinstance(exc, _TimedOut)) or "timed out" in err_str is_connect_timeout = self._looks_like_connect_timeout(exc) + safe_error = _redact_telegram_error_text(exc) logger.warning( "[%s] rich editMessageText transient failure (no legacy resend): %s", - self.name, exc, + self.name, safe_error, ) return SendResult( success=False, - error=str(exc), + error=safe_error, retryable=(is_connect_timeout or not is_timeout), ) # Telegram won't echo rich content for messages that predate the bot's @@ -1978,15 +1993,16 @@ async def _handle_polling_network_error(self, error: Exception) -> None: "Telegram polling could not reconnect after %d network error retries. " "Restarting gateway." % MAX_NETWORK_RETRIES ) - logger.error("[%s] %s Last error: %s", self.name, message, error) + logger.error("[%s] %s Last error: %s", self.name, message, _redact_telegram_error_text(error)) self._set_fatal_error("telegram_network_error", message, retryable=True) await self._notify_fatal_error() return delay = min(BASE_DELAY * (2 ** (attempt - 1)), MAX_DELAY) + safe_error = _redact_telegram_error_text(error) logger.warning( "[%s] Telegram network error (attempt %d/%d), reconnecting in %ds. Error: %s", - self.name, attempt, MAX_NETWORK_RETRIES, delay, error, + self.name, attempt, MAX_NETWORK_RETRIES, delay, safe_error, ) await asyncio.sleep(delay) @@ -2058,7 +2074,8 @@ async def _handle_polling_network_error(self, error: Exception) -> None: self._background_tasks.add(probe) probe.add_done_callback(self._background_tasks.discard) except Exception as retry_err: - logger.warning("[%s] Telegram polling reconnect failed: %s", self.name, retry_err) + safe_retry_error = _redact_telegram_error_text(retry_err) + logger.warning("[%s] Telegram polling reconnect failed: %s", self.name, safe_retry_error) # start_polling failed — polling is dead and no further error # callbacks will fire, so schedule the next retry ourselves. if not self.has_fatal_error: @@ -3629,18 +3646,20 @@ async def send( err_lower = str(send_err).lower() if "message to be replied not found" in err_lower and reply_to_id is not None: if private_dm_topic_send: + safe_send_error = _redact_telegram_error_text(send_err) return SendResult( success=False, - error=str(send_err), + error=safe_send_error, retryable=False, ) # Original message was deleted before we # could reply. For private-topic fallback # sends, message_thread_id is only valid with # the reply anchor, so drop both together. + safe_send_error = _redact_telegram_error_text(send_err) logger.warning( "[%s] Reply target deleted, retrying without reply_to: %s", - self.name, send_err, + self.name, safe_send_error, ) reply_to_id = None if metadata and metadata.get("telegram_dm_topic_reply_fallback"): @@ -3677,8 +3696,9 @@ async def send( await self._drain_general_connections_after_pool_timeout() if _send_attempt < 2: wait = 2 ** _send_attempt + safe_send_error = _redact_telegram_error_text(send_err) logger.warning("[%s] Network error on send (attempt %d/3), retrying in %ds: %s", - self.name, _send_attempt + 1, wait, send_err) + self.name, _send_attempt + 1, wait, safe_send_error) await asyncio.sleep(wait) else: raise @@ -3687,12 +3707,13 @@ async def send( if retry_after is not None or "retry after" in str(send_err).lower(): if _send_attempt < 2: wait = float(retry_after) if retry_after is not None else 1.0 + safe_send_error = _redact_telegram_error_text(send_err) logger.warning( "[%s] Telegram flood control on send (attempt %d/3), retrying in %.1fs: %s", self.name, _send_attempt + 1, wait, - send_err, + safe_send_error, ) await asyncio.sleep(wait) continue @@ -3726,7 +3747,8 @@ async def send( ) except Exception as e: - logger.error("[%s] Failed to send Telegram message: %s", self.name, e, exc_info=True) + safe_error = _redact_telegram_error_text(e) + logger.error("[%s] Failed to send Telegram message: %s", self.name, safe_error) err_str = str(e).lower() error_kind = classify_send_error(e) # Message too long — content exceeded 4096 chars. Return failure so @@ -3748,7 +3770,7 @@ async def send( is_pool_timeout = self._looks_like_pool_timeout(e) return SendResult( success=False, - error=str(e), + error=safe_error, retryable=(is_connect_timeout or is_pool_timeout or not is_timeout), error_kind=error_kind, ) @@ -3862,10 +3884,11 @@ async def edit_message( if "not modified" in str(fmt_err).lower(): return SendResult(success=True, message_id=message_id) # Fallback: strip MarkdownV2 escapes and retry as clean plain text + safe_format_error = _redact_telegram_error_text(fmt_err) logger.warning( "[%s] MarkdownV2 edit failed, falling back to plain text: %s", self.name, - fmt_err, + safe_format_error, ) _plain = _strip_mdv2(content) if content else content await self._bot.edit_message_text( @@ -3920,11 +3943,12 @@ async def edit_message( ) return SendResult(success=True, message_id=message_id) except Exception as retry_err: + safe_retry_error = _redact_telegram_error_text(retry_err) logger.error( "[%s] Edit retry failed after flood wait: %s", - self.name, retry_err, + self.name, safe_retry_error, ) - return SendResult(success=False, error=str(retry_err)) + return SendResult(success=False, error=safe_retry_error) # Transient network errors (ConnectError, timeouts, server # disconnects) should not permanently disable progress-message # editing. Mark the result retryable so the caller knows it @@ -3945,21 +3969,22 @@ async def edit_message( ) _is_transient = any(m in err_str for m in _transient_markers) if _is_transient: + safe_error = _redact_telegram_error_text(e) logger.warning( "[%s] Transient network error editing message %s (will retry): %s", self.name, message_id, - e, + safe_error, ) - return SendResult(success=False, error=str(e), retryable=True) + return SendResult(success=False, error=safe_error, retryable=True) + safe_error = _redact_telegram_error_text(e) logger.error( "[%s] Failed to edit Telegram message %s: %s", self.name, message_id, - e, - exc_info=True, + safe_error, ) - return SendResult(success=False, error=str(e)) + return SendResult(success=False, error=safe_error) def _truncate_stream_overflow_preview(self, content: str) -> str: """Return a one-message preview for oversized streaming edits. -
scripts/release.py@@ -45,6 +45,7 @@ # Auto-extracted from noreply emails + manual overrides AUTHOR_MAP = { + "[redacted:email]": "ooiuuii", # PR #58466/#58377 salvage (redact: fireworks fw-/fpk_ prefixes; telegram: redact bot tokens out of transport error strings) "[redacted:email]": "msh01", # PR #58250 salvage (telegram: wall-clock init timeout via daemon-thread deadline + abandon the shielded initialize task on timeout so the retry ladder advances instead of hanging on attempt 1/8 under s6 supervision; #58236). Also covers PR #58276 salvage (compression: preserve a real user turn after compaction; #55677). "[email protected]": "huanshan5195", # PR #57601 salvage (custom-provider: emit reasoning_effort at the live CustomProfile path so GLM-5.2/ARK/vLLM/Ollama endpoints receive it; + "max" reasoning level) "[redacted:email]": "infinitycrew39", # PR #56431 salvage (honor live vLLM context limits on local endpoints) -
tests/agent/test_redact.py@@ -59,6 +59,22 @@ def test_fal_key(self): result = redact_sensitive_text("fal_abc123def456ghi789jkl") assert "abc123def456" not in result + def test_fireworks_keys(self): + samples = [ + "fw-" + "A" * 40, + "fw_" + "B" * 40, + "fpk_" + "C" * 40, + ] + + for token in samples: + result = redact_sensitive_text(f"provider error {token}") + assert token not in result + assert "..." in result + + def test_short_fireworks_like_words_unchanged(self): + text = "fw-tooshort fw_tooshort fpk_tooshort" + assert redact_sensitive_text(text) == text + def test_notion_internal_integration_token(self): result = redact_sensitive_text("ntn_abc123def456ghi789jkl") assert "abc123def456" not in result -
tests/gateway/test_telegram_rich_messages.py@@ -10,6 +10,7 @@ ``TelegramAdapter`` and wire a mock bot. """ +import logging from types import SimpleNamespace from unittest.mock import AsyncMock, MagicMock @@ -475,6 +476,53 @@ async def test_transient_timeout_is_not_retryable(): assert result.retryable is False [redacted:email] +async def test_rich_transport_error_redacts_bot_token_even_when_redaction_disabled(monkeypatch): + import agent.redact as redact + + monkeypatch.setattr(redact, "_REDACT_ENABLED", False) + token = "[redacted:secret-assignment]" + adapter = _make_adapter() + adapter._bot.do_api_request = AsyncMock( + side_effect=NetworkError( + f"Timed out requesting https://api.telegram.org/bot{token}/sendRichMessage" + ) + ) + + result = await adapter.send("12345", RICH_CONTENT) + + assert result.success is False + assert result.error is not None + assert token not in result.error + assert "bot123456789:***/sendRichMessage" in result.error + adapter._bot.send_message.assert_not_called() + + [redacted:email] +async def test_legacy_send_error_redacts_bot_token_without_traceback(monkeypatch, caplog): + import agent.redact as redact + + monkeypatch.setattr(redact, "_REDACT_ENABLED", False) + token = "[redacted:secret-assignment]" + adapter = _make_adapter({"rich_messages": False}) + adapter._bot.send_message = AsyncMock( + side_effect=BadRequest( + f"Bad Request: https://api.telegram.org/bot{token}/sendMessage" + ) + ) + + with caplog.at_level(logging.ERROR): + result = await adapter.send("12345", "Plain legacy content.") + + assert result.success is False + assert result.error is not None + assert token not in result.error + assert "bot123456789:***/sendMessage" in result.error + assert token not in caplog.text + assert "bot123456789:***/sendMessage" in caplog.text + adapter._bot.do_api_request.assert_not_called() + + @pytest.mark.asyncio async def test_routing_thread_id_maps_to_message_thread_id(): adapter = _make_adapter() @@ -823,6 +871,32 @@ async def test_finalize_edit_plain_content_stays_legacy(): adapter._bot.edit_message_text.assert_awaited() [redacted:email] +async def test_legacy_edit_error_logs_redacted_bot_token_without_traceback(monkeypatch, caplog): + import agent.redact as redact + + monkeypatch.setattr(redact, "_REDACT_ENABLED", False) + token = "[redacted:secret-assignment]" + adapter = _make_adapter() + adapter._bot.edit_message_text = AsyncMock( + side_effect=BadRequest( + f"Bad Request: https://api.telegram.org/bot{token}/editMessageText" + ) + ) + + with caplog.at_level(logging.WARNING): + result = await adapter.edit_message( + "12345", "555", "Just a normal answer.", finalize=True, + ) + + assert result.success is False + assert result.error is not None + assert token not in result.error + assert "bot123456789:***/editMessageText" in result.error + assert token not in caplog.text + assert "bot123456789:***/editMessageText" in caplog.text + + @pytest.mark.asyncio async def test_finalize_edit_cjk_rich_content_stays_legacy_to_avoid_tdesktop_garble(): adapter = _make_adapter()
-
1 PR closed without merging
From the close comments: merged via another change 1
-
#58466 fix(redact): cover Fireworks token prefixes · merged via another change →
6fcd470,#58501
Closed issues · 1
- Closed#58463Fireworks API key prefixes can bypass bare-token redaction
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.67: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Merged PRs
- #58501
- Closed issues
- 1
- Attempts
- 1 PR closed without merging
- Fix link checked
- 4 Jul 2026