T09 · Insecure Skill Coding Practices
Note
- Location
- scripts/neis_cli.py:197
- Finding
- Optional API Key Exposed Through Curl Process Arguments## Vulnerability Details **File Location**: `scripts/neis_cli.py`, lines 197-212 and 226-240 **Vulnerability Type**: Sensitive information exposure through process command-line arguments **Risk Level**: Low ### Vulnerable Code ```python def optional_api_key() -> str | None: api_key = os.environ.get("NEIS_API_KEY", "").strip() return api_key or None class NeisClient: def fetch(self, endpoint: str, params: dict[str, Any]) -> dict[str, Any]: query = {"Type": "json", "pIndex": 1, "pSize": 100} query.update({key: value for key, value in params.items() if value is not None and value != ""}) if self.api_key: query["KEY"] = self.api_key url = f"{API_BASE_URL}/{endpoint}?{urllib.parse.urlencode(query)}" ``` ```python @staticmethod def _default_open(url: str) -> str: try: with urllib.request.urlopen(url, timeout=10) as response: return response.read().decode("utf-8") except urllib.error.URLError as exc: # Some local Python runtimes in macOS shells fail DNS resolution even though curl works. if not isinstance(exc.reason, OSError): raise try: result = subprocess.run( ["curl", "-fsSL", url], check=True, capture_output=True, text=True, ) ``` ### Technical Analysis The optional `NEIS_API_KEY` is inserted into the query string of the HTTPS request URL. If `urllib.request.urlopen()` raises a qualifying `URLError`, the implementation invokes curl and supplies the complete authenticated URL as a command-line argument. Command-line arguments can be observable through local process-inspection facilities, monitoring agents, audit logs, crash diagnostics, or process telemetry. Consequently, a local party with access to such facilities may capture the API key while the fallback curl process is running. The subprocess invocation uses an argument list rather than a shell co ...[truncated 1523 chars]
- Remediation
- ## Remediation Suggestions 1. Remove the curl fallback and return a controlled connection error when the standard Python HTTPS client fails. 2. If curl must remain, prevent the authenticated URL from appearing in its argument vector. Supply sensitive configuration through standard input or another mechanism that does not expose the key through process arguments, after verifying the selected mechanism is not logged. 3. Prefer authentication in an HTTP header if the NEIS API supports it. If query-string authentication is mandatory, ensure complete URLs are never logged, included in exceptions, or passed through observable command-line arguments. 4. Keep the API base URL fixed and continue using list-form subprocess invocation without `shell=True`. 5. Add a regression test that configures `NEIS_API_KEY`, triggers the fallback, intercepts `subprocess.run`, and verifies that no subprocess argument contains the key. 6. Document that school lookup parameters and the optional credential are transmitted to the official NEIS service over HTTPS.
