From 99baf16b1ba4ff071248e44e1231985c6b38b6d9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Mon, 16 Feb 2026 16:47:51 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20Fix=20sensitive=20API=20token=20?= =?UTF-8?q?exposure=20in=20stdout?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removed the `print` statement in `src/seedpass/api.py` that leaked the API token to stdout. Enhanced the API server and CLI to support secure token management: - Added `SEEDPASS_API_TOKEN` environment variable support in both `api.py` and `cli.py`. - Added a `--token` option to the `api start` CLI command. - Redirected the auto-generated token output to stderr instead of stdout. - Updated `api stop` to respect the `SEEDPASS_API_TOKEN` environment variable. These changes prevent the API token from being easily logged in insecure environments where stdout is captured. Co-authored-by: PR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com> --- src/seedpass/api.py | 9 ++++++--- src/seedpass/cli.py | 16 ++++++++++++---- 2 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/seedpass/api.py b/src/seedpass/api.py index df00ff62..31de348a 100644 --- a/src/seedpass/api.py +++ b/src/seedpass/api.py @@ -43,21 +43,24 @@ def _reload_relays(relays: list[str]) -> None: pass -def start_server(fingerprint: str | None = None) -> str: +def start_server(fingerprint: str | None = None, token: str | None = None) -> str: """Initialize global state and return the API token. Parameters ---------- fingerprint: Optional seed profile fingerprint to select before starting the server. + token: + Optional API token to use. If omitted, it checks the + ``SEEDPASS_API_TOKEN`` environment variable, or generates a new one. """ global _pm, _token if fingerprint is None: _pm = PasswordManager() else: _pm = PasswordManager(fingerprint=fingerprint) - _token = secrets.token_urlsafe(16) - print(f"API token: {_token}") + + _token = token or os.getenv("SEEDPASS_API_TOKEN") or secrets.token_urlsafe(16) origins = [ o.strip() for o in os.getenv("SEEDPASS_CORS_ORIGINS", "").split(",") diff --git a/src/seedpass/cli.py b/src/seedpass/cli.py index 9de63a45..a3f03d97 100644 --- a/src/seedpass/cli.py +++ b/src/seedpass/cli.py @@ -1,6 +1,7 @@ from pathlib import Path from typing import Optional, List import json +import os import typer import sys @@ -811,10 +812,16 @@ def update_checksum(ctx: typer.Context) -> None: @api_app.command("start") -def api_start(ctx: typer.Context, host: str = "127.0.0.1", port: int = 8000) -> None: +def api_start( + ctx: typer.Context, + host: str = "127.0.0.1", + port: int = 8000, + token: Optional[str] = typer.Option(None, "--token", help="API token"), +) -> None: """Start the SeedPass API server.""" - token = api_module.start_server(ctx.obj.get("fingerprint")) - typer.echo(f"API token: {token}") + actual_token = api_module.start_server(ctx.obj.get("fingerprint"), token=token) + if not os.getenv("SEEDPASS_API_TOKEN") and token is None: + typer.echo(f"API token: {actual_token}", err=True) uvicorn.run(api_module.app, host=host, port=port) @@ -823,10 +830,11 @@ def api_stop(ctx: typer.Context, host: str = "127.0.0.1", port: int = 8000) -> N """Stop the SeedPass API server.""" import requests + token = os.getenv("SEEDPASS_API_TOKEN", api_module._token) try: requests.post( f"http://{host}:{port}/api/v1/shutdown", - headers={"Authorization": f"Bearer {api_module._token}"}, + headers={"Authorization": f"Bearer {token}"}, timeout=2, ) except Exception as exc: # pragma: no cover - best effort