T09 · Insecure Skill Coding Practices
- Location
scripts/search_aggregator.py:167- Finding
SerpAPI Credential Disclosure Through Error Reporting
- Content
View full analysis
Vulnerability Details
File Location:
scripts/search_aggregator.py:47-48,scripts/search_aggregator.py:71-73,scripts/search_aggregator.py:167-176, andscripts/search_aggregator.py:325-344
Vulnerability Type: API credential exposure through unsanitized URL logging
Risk Level: MediumVulnerable Code
python if params: query = urllib.parse.urlencode({k: v for k, v in params.items() if v is not None}, doseq=True) url = f"{url}?{query}"python msg = f"HTTP {exc.code} {exc.reason} @ {url}" if detail: msg += f" | response={detail[:700]}" raise RuntimeError(msg) from excpython payload = request_json( method="GET", url=SERPAPI_BASE, params={ "engine": "google_scholar", "q": query, "num": per_source, "api_key": key, }, )python for source in selected: fn = source_map[source] try: merged.extend(fn(args.query, args.per_source)) except Exception as exc: errors[source] = str(exc) result = { "generated_at": now_iso(), "query": args.query, "sources": selected, "source_status": source_status(selected), "errors": errors, "summary": summarize(merged), "results": [asdict(i) for i in merged], } out_dir = Path(args.out_dir) out_dir.mkdir(parents=True, exist_ok=True) (out_dir / "latest.json").write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8") (out_dir / "latest.md").write_text(render_markdown(result), encoding="utf-8") print(json.dumps(result, ensure_ascii=False, indent=2))Technical Analysis
The Scholar search implementation passes
SERPAPI_API_KEYas theapi_keyquery parameter.request_json()appends all parameters to the request URL. If SerpAPI returns an HTTP error, the exception message incorporates that complete URL without redacting sensitive parameters.The c ...[truncated 2008 chars]
- Remediation
View remediation
Remediation Suggestions
-
Never include an unsanitized request URL in an exception when it may contain credentials. Redact sensitive query parameters before constructing diagnostic messages:
python def sanitized_url(url: str) -> str: parts = urllib.parse.urlsplit(url) params = urllib.parse.parse_qsl(parts.query, keep_blank_values=True) safe_params = [ (key, "[REDACTED]" if key.lower() in {"api_key", "key", "token", "access_token"} else value) for key, value in params ] return urllib.parse.urlunsplit( (parts.scheme, parts.netloc, parts.path, urllib.parse.urlencode(safe_params), parts.fragment) ) -
Use the sanitized value in errors:
python msg = f"HTTP {exc.code} {exc.reason} @ {sanitized_url(url)}" -
If SerpAPI supports an authentication header for this endpoint, prefer it over a query-string credential. URL credentials are more likely to appear in logs, proxies, telemetry, and exception messages.
-
Store only structured, non-sensitive diagnostics in
result["errors"], such as the provider name, HTTP status code, and a generic failure description. -
Treat provider response bodies as potentially sensitive and untrusted. Sanitize them before writing them to reports or logs, and avoid including them unless explicitly requested for debugging.
-
Add regression tests that configure recognizable dummy secrets, exercise HTTP error paths, and assert that no secret appears in exception messages, stdout,
latest.json, orlatest.md. -
Rotate any real SerpAPI key that may already have appeared in generated output or logs, and remove or restrict access to affected artifacts where feasible.
-
