T09 · Insecure Skill Coding Practices
- Location
scripts/surf_report.py:93- Finding
Google Geocoding API Key Disclosure Through Error Logging
- Content
View full analysis
Vulnerability Details
File Location:
scripts/surf_report.py:93-104,scripts/surf_report.py:146-147, andexamples.md:71-74
Vulnerability Type: Sensitive credential exposure through unredacted URLs
Risk Level: MediumThe Google Geocoding API key is included in a URL query parameter. When an HTTP error or invalid JSON response occurs, the complete URL is incorporated into an exception message. The main program subsequently writes that message to stderr, and the documented cron configuration appends stderr to a persistent log file.
Vulnerable Code
scripts/surf_report.py:93-104:python try: with urllib.request.urlopen(req, timeout=timeout) as response: body = response.read().decode("utf-8") except urllib.error.HTTPError as exc: detail = exc.read().decode("utf-8", errors="replace") raise ApiError(f"HTTP {exc.code} for {url}: {detail}") from exc except urllib.error.URLError as exc: raise ApiError(f"Network error for {url}: {exc}") from exc try: parsed = json.loads(body) except json.JSONDecodeError as exc: raise ApiError(f"Invalid JSON from {url}: {exc}") from excscripts/surf_report.py:146-147:python query = urllib.parse.urlencode({"address": address, "key": api_key}) payload = http_get_json(f"{GEOCODE_ENDPOINT}?{query}", headers=None, timeout=timeout)The exception is ultimately written to stderr:
python except ApiError as exc: print(f"ERROR: {exc}", file=sys.stderr) return 4examples.md:71-74recommends persisting that stderr output:cron */30 * * * * cd /home/dgorissen/code/stormglass-skill && python scripts/surf_report.py --location "Highcliffe Beach" --horizon 72h --output json >> logs/surf_report.jsonl 2>> logs/surf_report.errTechnical Analysis
Credentials in URL query strings must be treated as sensitive. The
urlpassed tohttp_get_jsoncontains both ...[truncated 2185 chars]- Remediation
View remediation
Remediation Suggestions
-
Never include a complete request URL in an exception when it may contain credentials. Log only the endpoint scheme, host, and path.
-
Add centralized URL sanitization that removes sensitive parameters such as
key,token,api_key, andaccess_tokenbefore any diagnostic output:python def redact_url(url: str) -> str: parsed = urllib.parse.urlsplit(url) query = urllib.parse.parse_qsl(parsed.query, keep_blank_values=True) safe_query = [ (name, "[REDACTED]" if name.lower() in {"key", "token", "api_key", "access_token"} else value) for name, value in query ] return urllib.parse.urlunsplit( ( parsed.scheme, parsed.netloc, parsed.path, urllib.parse.urlencode(safe_query), parsed.fragment, ) )Use only the sanitized value in all error branches:
python safe_url = redact_url(url) raise ApiError(f"HTTP {exc.code} for {safe_url}") from exc -
Avoid including arbitrary upstream response bodies in routine error logs because they may contain request details or other sensitive information. Limit and sanitize any diagnostic body content.
-
Configure the Google key with API restrictions, application restrictions where practical, quota limits, and billing alerts.
-
Restrict log-file permissions, implement short retention and secure rotation, and prevent credential-bearing logs from entering broadly accessible aggregation systems.
-
Search existing logs and backups for exposed
key=parameters. Rotate the Google key if exposure may already have occurred. -
Add automated tests that simulate HTTP and malformed-JSON failures and assert that neither API keys nor unredacted credential parameters appear in stderr.
-
