T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/wp-cli.js:55
- Finding
- WordPress credentials may be transmitted over plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/wp-cli.js:42-61, 87-96` **Vulnerability Type**: Missing HTTPS enforcement for authenticated network requests **Risk Level**: Medium ### Vulnerable Code Credential-bearing authorization headers are generated from environment variables: ```javascript function buildAuthHeader() { const basicToken = process.env.WP_BASIC_TOKEN; if (basicToken) { return { Authorization: `Basic ${basicToken}` }; } const user = process.env.WP_USER; const appPassword = process.env.WP_APP_PASSWORD; if (user && appPassword) { const token = Buffer.from(`${user}:${appPassword}`).toString('base64'); return { Authorization: `Basic ${token}` }; } const jwt = process.env.WP_JWT_TOKEN; if (jwt) { return { Authorization: `Bearer ${jwt}` }; } return {}; } function resolveBaseUrl() { const base = process.env.WP_BASE_URL; if (!base) { console.error('Missing WP_BASE_URL. Example: https://example.com'); process.exit(1); } return base.replace(/\/$/, ''); } ``` These headers are subsequently attached to requests without checking the URL protocol: ```javascript async function requestJson({ method, path, query, body }) { const headers = { 'Accept': 'application/json', ...buildAuthHeader(), }; const options = { method, headers }; if (body !== undefined) { headers['Content-Type'] = 'application/json'; options.body = JSON.stringify(body); } const response = await fetch(buildApiUrl(path, query), options); ``` ### Technical Analysis `resolveBaseUrl()` accepts any value supported by the `URL` and `fetch` implementations, including an `http://` URL. No runtime control requires HTTPS before `requestJson()` attaches a Basic or Bearer authorization header. Base64 encoding in HTTP Basic authentication does not provide encryption. If `WP_BASE_URL` uses HTTP, a network-positioned attacker can observe the authorization header, request body, query parameters, and ...[truncated 2307 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate `WP_BASE_URL` before any request is made, and reject protocols other than HTTPS: ```javascript function resolveBaseUrl() { const rawBase = process.env.WP_BASE_URL; if (!rawBase) { throw new Error('Missing WP_BASE_URL. Example: https://example.com'); } let baseUrl; try { baseUrl = new URL(rawBase); } catch { throw new Error('WP_BASE_URL must be a valid absolute URL.'); } if (baseUrl.protocol !== 'https:') { throw new Error('WP_BASE_URL must use HTTPS.'); } baseUrl.hash = ''; baseUrl.search = ''; baseUrl.pathname = baseUrl.pathname.replace(/\/+$/, ''); return baseUrl.toString().replace(/\/$/, ''); } ``` 2. If local plaintext testing is required, permit it only through an explicit opt-in such as `WP_ALLOW_INSECURE_HTTP=true`, restrict the exception to loopback hosts, and print a prominent warning. Production deployments should never enable this option. 3. Validate that the final request URL remains on the configured origin before attaching credentials. Build unauthenticated and authenticated requests separately so authorization headers cannot accidentally be sent to an unintended destination. 4. Use a dedicated WordPress application password for a least-privileged service account. Avoid Administrator credentials unless the workflow strictly requires them. 5. Rotate the application password, Basic token, or JWT immediately if it may previously have been used with an HTTP endpoint. 6. Add automated tests asserting that: - `http://example.com` is rejected. - Malformed and non-HTTP(S) URLs are rejected. - HTTPS URLs are accepted. - Authorization headers are never attached when transport validation fails. ]]>
