T09 · Insecure Skill Coding Practices
- Location
src/index.py:180- Finding
Environment API Key Exfiltration Through a Caller-Controlled LLM Endpoint
- Content
View full analysis
LLMClient: return LLMClient( base_url=config.llm_base_url, api_key=config.llm_api_key, model=config.llm_model, ) ``` ```python # src/index.py:180-183 output_dir = input.get("output_dir") config_overrides = { k: v for k, v in input.items() if k not in ("mode", "code", "save", "output_dir") } config = SkillConfig(**config_overrides) if config_overrides else None ``` ```python # src/config.py:31-33 llm_base_url: str = field( default_factory=lambda: os.getenv( "LLM_BASE_URL", "https://api.deepseek.com/v1" ) ) llm_api_key: str = field( default_factory=lambda: os.getenv("LLM_API_KEY", "") ) llm_model: str = field( default_factory=lambda: os.getenv("LLM_MODEL", "deepseek-chat") ) ``` ```python # src/llm/client.py:15-17 def __init__(self, base_url: str, api_key: str, model: str): self.client = AsyncOpenAI(base_url=base_url, api_key=api_key) self.model = model ``` ### Technical Analysis The public handler converts every input field other than four reserved fields into a `SkillConfig` constructor argument. This allows a caller to override `llm_base_url`, even though that field is not declared in the handler input schema. If the caller supplies `llm_base_url` but omits `llm_api_key`, the dataclass default factory automatically loads `LLM_API_KEY` from the process environment. The resulting configuration therefore combines an attacker-selected destination with a host-provided secret. `AsyncOpenAI` subsequently uses that secret when authenticating to the selected endpoint. Configuration validation only checks whether the endpoin ...[truncated 1506 chars]- Remediation
View remediation
