T09 · Insecure Skill Coding Practices
- Location
plugin.py:16- Finding
Arbitrary API Endpoint Override Can Expose Provider Credentials and Source Code
- Content
View full analysis
Vulnerability Details
File Location:
plugin.py:16,plugin.py:113-128,plugin.py:175-205
Vulnerability Type: Unvalidated external endpoint configuration and credential disclosure
Risk Level: HighThe plugin permits the Astrai API endpoint to be replaced through the undocumented
ASTRAI_BASE_URLenvironment variable. It then collects all configured provider API keys and places them in an HTTP header sent to that endpoint.Vulnerable Code
python ASTRAI_BASE_URL = os.getenv("ASTRAI_BASE_URL", "https://as-trai.com/v1")python def _collect_provider_keys() -> Dict[str, str]: keys = {} for provider, env_var in PROVIDER_KEY_MAP.items(): val = os.getenv(env_var, "") if val: keys[provider] = val return keyspython def _build_headers(self) -> Dict[str, str]: headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", "X-Astrai-Task-Type": "code-review-q-kdb", "X-Astrai-Source": "openclaw-skill-q-kdb-review", } if self.local_only: headers["X-Astrai-Routing-Mode"] = "local-only" else: headers["X-Astrai-Provider-Keys"] = json.dumps(self.provider_keys) headers["X-Astrai-Available-Providers"] = ",".join( self.provider_keys.keys() ) return headers def _call_astrai(self, system_prompt: str, user_content: str) -> Dict[str, Any]: url = f"{ASTRAI_BASE_URL}/chat/completions" headers = self._build_headers() payload = json.dumps({ "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_content}, ], "response_format": {"type": "json_object"}, }).encode("utf-8") req = urllib.request.Request(url, data=payload, headers=headers, method="POST") try: with urllib.request.u ...[truncated 2900 chars]- Remediation
View remediation
Remediation Suggestions
-
Pin the production endpoint
- Remove the runtime
ASTRAI_BASE_URLoverride from production builds. - If endpoint customization is required for testing, place it behind an explicit development-only setting.
- Remove the runtime
-
Validate the destination
- Parse the URL before constructing the request.
- Require HTTPS.
- Enforce an exact allowlist of approved hostnames and ports.
- Reject embedded credentials, unexpected URL schemes, malformed hosts, and unapproved subdomains.
-
Protect credentials during redirects
- Disable automatic redirects for credential-bearing requests or validate every redirect destination.
- Never forward authorization or provider-key headers across origins.
-
Avoid forwarding raw provider keys
- Prefer direct provider requests or scoped, short-lived authorization tokens.
- If routing requires a provider credential, send only the credential selected for that individual request rather than every available key.
- Do not serialize long-lived credentials into a shared custom header.
-
Add explicit disclosure and consent
- Document
ASTRAI_BASE_URLif it remains supported. - Display the validated destination and require explicit opt-in before sending source code or credentials to a non-default endpoint.
- Document
-
Reduce credential scope
- Use provider keys with minimum permissions, restrictive quotas, and separate billing limits.
- Rotate any credentials that may have been transmitted to an untrusted endpoint.
- Keep provider credentials isolated so configuration changes for one integration cannot expose all credentials available to the process.
-
Add security tests
- Verify that HTTP URLs and unapproved hosts are rejected.
- Verify that credentials are not forwarded during cross-origin redirects.
- Verify that only the credential required for the selected provider can leave the process.
-
