T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/search_aggregator.py:167
- Finding
- SerpAPI Credential Disclosure Through Error Reporting## Vulnerability Details **File Location**: `scripts/search_aggregator.py:47-48`, `scripts/search_aggregator.py:71-73`, `scripts/search_aggregator.py:167-176`, and `scripts/search_aggregator.py:325-344` **Vulnerability Type**: API credential exposure through unsanitized URL logging **Risk Level**: Medium ### Vulnerable 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 exc ``` ```python 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_KEY` as the `api_key` query 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
- ## Remediation Suggestions 1. 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) ) ``` 2. Use the sanitized value in errors: ```python msg = f"HTTP {exc.code} {exc.reason} @ {sanitized_url(url)}" ``` 3. 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. 4. Store only structured, non-sensitive diagnostics in `result["errors"]`, such as the provider name, HTTP status code, and a generic failure description. 5. 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. 6. Add regression tests that configure recognizable dummy secrets, exercise HTTP error paths, and assert that no secret appears in exception messages, stdout, `latest.json`, or `latest.md`. 7. 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.
