From d542699c6cef88f167289c5f9a2d8470ad22cb2d Mon Sep 17 00:00:00 2001 From: arvarik <9952627+arvarik@users.noreply.github.com> Date: Tue, 23 Jun 2026 21:30:36 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=20fix:=20ensure=20API=20keys=20are?= =?UTF-8?q?=20always=20masked=20in=20config=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidated and improved masking logic for configuration values. - Centralized masking in `_mask_value` helper function. - Ensure any key containing 'api-key' is masked. - Short API keys (<= 8 chars) are now fully masked with '********'. - Long API keys (> 8 chars) retain the prefix/suffix masking. - Masking check is now case-insensitive. This fixes a vulnerability where short API keys were exposed in terminal output. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/gemstack/cli/config_cmd.py | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/gemstack/cli/config_cmd.py b/src/gemstack/cli/config_cmd.py index 397a01f..48d24bd 100644 --- a/src/gemstack/cli/config_cmd.py +++ b/src/gemstack/cli/config_cmd.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Any + import typer from rich.console import Console from rich.table import Table @@ -31,6 +33,16 @@ } +def _mask_value(key: str, value: Any) -> str: + """Mask configuration value if it appears to be an API key.""" + raw_value = str(value) + if "api-key" in key.lower(): + if len(raw_value) > 8: + return raw_value[:4] + "..." + raw_value[-4:] + return "********" + return raw_value + + @config_app.command("set") def config_set( key: str = typer.Argument(..., help="Configuration key"), @@ -63,10 +75,7 @@ def config_set( config.save() - # Mask API key in output - display_value = value - if "api-key" in key and len(value) > 8: - display_value = value[:4] + "..." + value[-4:] + display_value = _mask_value(key, value) console.print(f"[green]✅ Set {key} = {display_value}[/green]") console.print(f"[dim]Saved to {GemstackConfig.config_path()}[/dim]") @@ -98,10 +107,7 @@ def config_get( if raw_value is None: console.print(f"[dim]{key}: (not set)[/dim]") else: - # Mask API key - display_value = str(raw_value) - if "api-key" in key and len(display_value) > 8: - display_value = display_value[:4] + "..." + display_value[-4:] + display_value = _mask_value(key, raw_value) console.print(f"{key}: {display_value}") @@ -128,10 +134,8 @@ def config_list() -> None: if raw_value is None: display_value = "[dim](not set)[/dim]" - elif "api-key" in key and isinstance(raw_value, str) and len(raw_value) > 8: - display_value = raw_value[:4] + "..." + raw_value[-4:] else: - display_value = str(raw_value) + display_value = _mask_value(key, raw_value) table.add_row(key, display_value, description)