Skip to content

Commit 183d0d4

Browse files
yeldarbyclaude
andcommitted
security(cli): sanitize API keys from error messages, fix video SDK noise
1. Add _sanitize_credentials() to _output.py that strips api_key values from URLs in error messages before displaying to the user. This prevents API keys from leaking in --json error output, logs, and terminal history. Applied to ALL error paths through output_error(). 2. Fix video infer handler to use suppress_sdk_output() — was leaking "loading Roboflow workspace..." to stdout in --json mode. Before: {"error": {"message": "...?api_key=tVO5PbdMtkaS5VP92xM7&..."}} After: {"error": {"message": "...?api_key=***&..."}} 405 tests pass, all linting clean. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent aeb4dc1 commit 183d0d4

2 files changed

Lines changed: 22 additions & 12 deletions

File tree

roboflow/cli/_output.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ def output(args: Any, data: Any, text: Optional[str] = None) -> None:
3636
print(json.dumps(data, indent=2, default=str))
3737

3838

39+
def _sanitize_credentials(text: str) -> str:
40+
"""Strip API keys from URLs and other sensitive patterns in error messages."""
41+
import re
42+
43+
return re.sub(r"api_key=[A-Za-z0-9_]+", "api_key=***", text)
44+
45+
3946
def _parse_error_message(raw: str) -> tuple[Optional[dict[str, Any]], str]:
4047
"""Try to parse a raw error string that may contain embedded JSON.
4148
@@ -44,7 +51,7 @@ def _parse_error_message(raw: str) -> tuple[Optional[dict[str, Any]], str]:
4451
otherwise ``None``. The *human_readable_message* drills into nested
4552
``error.message`` structures so the text-mode output is clean.
4653
"""
47-
text = raw.strip()
54+
text = _sanitize_credentials(raw.strip())
4855
# Strip status-code prefix like "404: {...}"
4956
colon_idx = text.find(": {")
5057
if 0 < colon_idx < 5:
@@ -60,7 +67,7 @@ def _parse_error_message(raw: str) -> tuple[Optional[dict[str, Any]], str]:
6067
return parsed, human
6168
except (json.JSONDecodeError, TypeError, ValueError):
6269
pass
63-
return None, raw
70+
return None, text # Return sanitized text, not the original raw
6471

6572

6673
def output_error(

roboflow/cli/handlers/video.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,19 @@ def _video_infer(args: argparse.Namespace) -> None:
4141
return
4242

4343
try:
44-
rf = roboflow.Roboflow(api_key)
45-
project = rf.workspace().project(args.project)
46-
version = project.version(args.version_number)
47-
model = version.model
48-
49-
job_id, _signed_url, _expire_time = model.predict_video(
50-
args.video_file,
51-
args.fps,
52-
prediction_type="batch-video",
53-
)
44+
from roboflow.cli._output import suppress_sdk_output
45+
46+
with suppress_sdk_output():
47+
rf = roboflow.Roboflow(api_key)
48+
project = rf.workspace().project(args.project)
49+
version = project.version(args.version_number)
50+
model = version.model
51+
52+
job_id, _signed_url, _expire_time = model.predict_video(
53+
args.video_file,
54+
args.fps,
55+
prediction_type="batch-video",
56+
)
5457
except Exception as exc:
5558
output_error(args, str(exc))
5659
return

0 commit comments

Comments
 (0)