From f940641a5a881bd9538a42cd2a5761cd2290b3fa Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 29 Jun 2026 20:46:51 -0400 Subject: [PATCH] fix: never log the OTP code value (secret-in-logs); correct false test claim (v0.1.5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit M-1: retrieve.py logged the live single-use code at INFO ('found code %s', 'code %s skipped too old'), shipping the secret to any aggregation/retention sink the host wires (our /srv/logs -> loki/grafana path). drop the code value from both lines — log that a code was found/retrieved and where, never the value. also truncate the oauth token-endpoint error body to 200 chars so a token response can't be dumped whole. aiomail-F3: CLAUDE.md claimed an '8-case tested' suite that does not exist in the repo; corrected to describe the manual throwaway-venv exercise + the real flake8 check. verified by execution: code retrieved, value absent from logs; control confirms the old line carried it. Signed-off-by: disqualifier --- README.md | 10 +++++----- pyproject.toml | 2 +- src/aiomail/__init__.py | 2 +- src/aiomail/oauth.py | 4 +++- src/aiomail/retrieve.py | 6 ++++-- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1349b73..22722b8 100644 --- a/README.md +++ b/README.md @@ -11,22 +11,22 @@ This reads codes from email; it does not generate them (that is `pyotp`'s job). `requirements.txt`: ``` -aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.4 +aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.5 # OAuth token providers (Microsoft / Google) need the extra: -aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.4 +aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.5 ``` Direct: ```bash -pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.4" -pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.4" +pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.5" +pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.5" ``` Requires `aioimaplib` and `beautifulsoup4` (pulled transitively). The `oauth` extra adds `aiohttp` for the refresh-token providers. -Drop the `@v0.1.4` suffix from the line above to install the latest unpinned. +Drop the `@v0.1.5` suffix from the line above to install the latest unpinned. ## Password auth diff --git a/pyproject.toml b/pyproject.toml index c96174c..4c863f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "aiomail" -version = "0.1.4" +version = "0.1.5" description = "async IMAP one-time-code retrieval with password/OAuth2 auth and dynamic matching" requires-python = ">=3.10" dependencies = [ diff --git a/src/aiomail/__init__.py b/src/aiomail/__init__.py index 61216d9..d8278a4 100644 --- a/src/aiomail/__init__.py +++ b/src/aiomail/__init__.py @@ -29,4 +29,4 @@ __all__ = [ "DEFAULT_FOLDERS", ] -__version__ = "0.1.4" +__version__ = "0.1.5" diff --git a/src/aiomail/oauth.py b/src/aiomail/oauth.py index c10c9de..25fe58b 100644 --- a/src/aiomail/oauth.py +++ b/src/aiomail/oauth.py @@ -81,7 +81,9 @@ class _RefreshTokenProvider: self._failures = 0 return token else: - body = await resp.text() + # log a truncated error body only — a token-endpoint + # response can carry sensitive material; never dump it whole + body = (await resp.text())[:200] log.warning("token endpoint %s -> %s: %s", endpoint, resp.status, body) except Exception as exc: log.warning("token request to %s failed: %s", endpoint, exc) diff --git a/src/aiomail/retrieve.py b/src/aiomail/retrieve.py index 072c29c..fc9df78 100644 --- a/src/aiomail/retrieve.py +++ b/src/aiomail/retrieve.py @@ -113,12 +113,14 @@ async def retrieve_otp( if max_age is not None: age = _age_seconds(message) if age is not None and age > max_age: - log.info("code %s skipped, too old (%.0fs > %.0fs)", code, age, max_age) + log.info("code skipped, too old (%.0fs > %.0fs)", age, max_age) continue if mark_seen: await client.mark_seen(email_id) - log.info("found code %s in %s", code, folder) + # never log the code value — it's a live single-use secret that would + # reach any aggregation/retention sink the host wires up + log.info("found code in %s", folder) return code if attempt < retries: