diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0fcf01f..8af5e6bee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ Format adapted from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), al ## [Unreleased] +### Fixed +- if you connected a Gemini API key to solstone, that key was being written into your local `describe.log` and `transcribe.log` files on every request to Google's API. the logs are local to your install, but the key was on disk in the clear — anyone you'd shared a log file with for debugging could have read it. install this release if you've used the Google provider, and rotate your Gemini key in your Google AI Studio console. + ### Changed - Google cogitate agents now run through the Google GenAI SDK instead of a separate Gemini CLI process. - Google tool use is handled inside solstone, with the same read/write policy boundaries. diff --git a/pyproject.toml b/pyproject.toml index 73d0c6ff3..89bc39666 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "solstone" -version = "0.3.7" +version = "0.3.8" description = "Navigate Life Intelligently" readme = "README.md" requires-python = ">=3.11" diff --git a/solstone/__init__.py b/solstone/__init__.py index 0ca446aa4..dc88dc43c 100644 --- a/solstone/__init__.py +++ b/solstone/__init__.py @@ -2,3 +2,11 @@ # Copyright (c) 2026 sol pbc """solstone namespace package.""" + +import logging + +# httpx logs the full request URL at INFO; the Gemini API authenticates via +# `?key=AIzaSy...`, so INFO leaks live keys into describe.log / transcribe.log. +# Set the level on the named logger so it survives later basicConfig() calls +# from individual CLI entry points. +logging.getLogger("httpx").setLevel(logging.WARNING) diff --git a/tests/test_httpx_logger.py b/tests/test_httpx_logger.py new file mode 100644 index 000000000..b24042013 --- /dev/null +++ b/tests/test_httpx_logger.py @@ -0,0 +1,45 @@ +# SPDX-License-Identifier: AGPL-3.0-only +# Copyright (c) 2026 sol pbc + +"""Guard against regressing the httpx URL-leak silencing. + +httpx logs the full request URL at INFO. Gemini API keys ride in `?key=...` +on every call, so any path that lets httpx INFO records reach a file handler +leaks live credentials. The package import in `solstone/__init__.py` sets the +httpx logger to WARNING; these tests assert that contract and that it +survives a later `logging.basicConfig(level=INFO)` call. +""" + +import logging + +import solstone # noqa: F401 — import for its side effect (logger config) + + +def test_httpx_logger_silenced_at_import(): + assert logging.getLogger("httpx").level == logging.WARNING + + +def test_httpx_info_records_suppressed_after_basicconfig(): + logging.basicConfig(level=logging.INFO) + httpx_logger = logging.getLogger("httpx") + assert httpx_logger.getEffectiveLevel() >= logging.WARNING + + records: list[logging.LogRecord] = [] + + class _Capture(logging.Handler): + def emit(self, record: logging.LogRecord) -> None: + records.append(record) + + handler = _Capture(level=logging.DEBUG) + httpx_logger.addHandler(handler) + try: + httpx_logger.info( + "HTTP Request: GET https://generativelanguage.googleapis.com/v1beta/models?key=AIzaSyTEST" + ) + httpx_logger.warning("connection error") + finally: + httpx_logger.removeHandler(handler) + + levels = [r.levelno for r in records] + assert logging.INFO not in levels + assert logging.WARNING in levels diff --git a/uv.lock b/uv.lock index fa8310025..b645c5d55 100644 --- a/uv.lock +++ b/uv.lock @@ -3223,7 +3223,7 @@ wheels = [ [[package]] name = "solstone" -version = "0.3.7" +version = "0.3.8" source = { editable = "." } dependencies = [ { name = "anthropic" },