T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/remove-password-from-pdf.py:57
- Finding
- Unrestricted Base URL Override Can Redirect Sensitive Documents and Credentials<![CDATA[ ## Vulnerability Details **File Location**: `scripts/remove-password-from-pdf.py`, lines 57-64 and 193-198 **Vulnerability Type**: Unvalidated external request destination **Risk Level**: High ### Vulnerable Code ```python def create_job( base_url: str, api_key: str, pdf_path: str, password: str, timeout_s: int = 120, ) -> Dict[str, Any]: url = base_url.rstrip("/") + CREATE_PATH headers = make_headers(api_key) with open(pdf_path, "rb") as f: files = {"file": (os.path.basename(pdf_path), f, "application/pdf")} data = {"password": password} resp = requests.post(url, headers=headers, files=files, data=data, timeout=timeout_s) ``` The destination can be overridden through either a command-line argument or an environment variable: ```python ap.add_argument( "--base-url", default=os.getenv("SOLUTIONS_BASE_URL", DEFAULT_BASE_URL), help="Base URL override", ) ``` Polling requests subsequently send the same Bearer credential to the selected destination: ```python def get_job( base_url: str, api_key: str, job_id: Any, timeout_s: int = 30, ) -> Dict[str, Any]: url = base_url.rstrip("/") + f"/api/{job_id}" headers = make_headers(api_key) resp = requests.get(url, headers=headers, timeout=timeout_s) ``` ### Technical Analysis The skill's declared functionality requires transmitting the protected PDF, its current password, and an API key to the documented Solutions API. Sending these values to the default provider endpoint is therefore expected and necessary for the declared cloud-based workflow. However, the implementation allows `base_url` to be replaced without validating: - The URL scheme - The destination hostname - Whether TLS is required - Whether the destination belongs to the declared provider - Whether credentials may be sent to the selected origin As a result, `SOLUTIONS_BASE_URL` or `--base-url` can redirect the upload to an arbitrary HTTP or HTTP ...[truncated 1863 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Remove the base URL override if alternate service deployments are not an essential requirement. 2. If configurability is required, parse the destination with `urllib.parse.urlparse` and enforce: - Scheme must be `https` - Hostname must exactly match an explicit allowlist - Port must be an approved TLS port - User information and URL fragments must be absent - The expected API path must not be replaceable 3. Reject plaintext HTTP destinations, including local and loopback destinations. 4. Construct endpoints from a validated origin rather than concatenating untrusted strings. 5. Disable redirects for credential-bearing requests or validate every redirect target before following it: ```python requests.post(..., allow_redirects=False) ``` 6. Avoid forwarding the `Authorization` header across origins under all circumstances. 7. Document any approved alternate provider endpoints and require explicit administrative configuration rather than accepting unrestricted per-run overrides. 8. Add tests confirming that HTTP URLs, unknown hosts, embedded credentials, malformed URLs, and cross-origin redirects are rejected before opening or uploading the PDF. ]]>
