fix(redact,cli): mask credentials in every log line, silence httpx request urls
This commit is contained in:
@@ -24,7 +24,6 @@ import asyncio
|
||||
import contextlib
|
||||
import functools
|
||||
import logging
|
||||
import re
|
||||
import signal
|
||||
from contextlib import AsyncExitStack
|
||||
from typing import TYPE_CHECKING, Any
|
||||
@@ -51,6 +50,8 @@ from beaver_gateway.core.bus import EventBus
|
||||
from beaver_gateway.core.conversations import Conversations
|
||||
from beaver_gateway.core.envelope import Envelope
|
||||
from beaver_gateway.core.gateway_tools import build_tool_server
|
||||
from beaver_gateway.core.redact import install as install_redaction
|
||||
from beaver_gateway.core.redact import load_secrets as load_secrets_to_mask
|
||||
from beaver_gateway.core.registry import AgentRegistry, Gateway, McpRegistry
|
||||
from beaver_gateway.core.rotation import Rotation, RotationPolicy
|
||||
from beaver_gateway.core.scheduler import Scheduler
|
||||
@@ -89,6 +90,7 @@ def main() -> None:
|
||||
logging.basicConfig(
|
||||
level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s"
|
||||
)
|
||||
install_redaction()
|
||||
_install_sigterm_handler()
|
||||
asyncio.run(_async_main(), loop_factory=uvloop.new_event_loop)
|
||||
|
||||
@@ -124,6 +126,9 @@ async def _async_main() -> None:
|
||||
# populates Settings fields, not the process environment.
|
||||
# ``override=False``: real env vars (Docker, systemd) win over .env.
|
||||
load_dotenv(override=False)
|
||||
# Only now does the process environment hold the credentials the
|
||||
# redactor masks literally (in Docker they arrive via ``env_file``).
|
||||
load_secrets_to_mask()
|
||||
|
||||
settings = Settings() # ty: ignore[missing-argument]
|
||||
gateway = config_loader.load(settings.config_path)
|
||||
@@ -290,30 +295,8 @@ def _plain_postgres_url(url: str) -> str | None:
|
||||
return None
|
||||
|
||||
|
||||
_TOKEN_IN_QUERY = re.compile(r"(token=)[^&\s\"]+")
|
||||
|
||||
|
||||
class ScrubQueryTokens(logging.Filter):
|
||||
"""Mask ``?token=…`` in uvicorn access lines.
|
||||
|
||||
Webhook senders that cannot set headers put the secret in the URL, and
|
||||
``docker logs`` is not a vault.
|
||||
"""
|
||||
|
||||
def filter(self, record: logging.LogRecord) -> bool:
|
||||
if isinstance(record.args, tuple):
|
||||
record.args = tuple(
|
||||
_TOKEN_IN_QUERY.sub(r"\1<…>", a) if isinstance(a, str) else a
|
||||
for a in record.args
|
||||
)
|
||||
elif isinstance(record.msg, str):
|
||||
record.msg = _TOKEN_IN_QUERY.sub(r"\1<…>", record.msg)
|
||||
return True
|
||||
|
||||
|
||||
async def _serve_root(gateway: Gateway, *, extra: dict[str, ASGIApp]) -> None:
|
||||
app = build_root_app(gateway.frontends, extra=extra)
|
||||
logging.getLogger("uvicorn.access").addFilter(ScrubQueryTokens())
|
||||
config = uvicorn.Config(app, host=gateway.host, port=gateway.port, log_level="info")
|
||||
_log.info(
|
||||
"gateway on http://%s:%d - %s",
|
||||
|
||||
Reference in New Issue
Block a user