T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/openproject.mjs:75
- Finding
- OpenProject API Token May Be Transmitted over Plaintext HTTP## Vulnerability Details **File Location**: `scripts/openproject.mjs`, lines 75–100 **Vulnerability Type**: Plaintext transmission of credentials **Risk Level**: High ### Vulnerable Code ```js function authHeader() { // OpenProject uses Basic auth with 'apikey' as username const token = Buffer.from(`apikey:${CFG.apiToken}`).toString('base64'); return `Basic ${token}`; } function baseUrl() { const host = CFG.host.replace(/\/+$/, ''); const prefix = host.startsWith('http') ? host : `https://${host}`; return `${prefix}/api/v3`; } async function opFetch(path, options = {}, retries = 3) { const url = path.startsWith('http') ? path : `${baseUrl()}${path}`; const headers = { 'Authorization': authHeader(), 'Accept': 'application/json', ...options.headers, }; if (!(options.body instanceof FormData)) { headers['Content-Type'] = headers['Content-Type'] || 'application/json'; } for (let attempt = 1; attempt <= retries; attempt++) { const resp = await fetch(url, { ...options, headers }); ``` ### Technical Analysis The `baseUrl()` function accepts an `OP_HOST` value beginning with either `http://` or `https://` because it preserves any value that starts with `http`. Every API request then includes the OpenProject API token in an HTTP Basic `Authorization` header. Base64 encoding does not provide confidentiality. If `OP_HOST` specifies a plaintext HTTP endpoint, the API token, request payloads, and API responses are transmitted without transport encryption. This is especially relevant because the project explicitly supports self-hosted instances, where users may configure an internal HTTP endpoint. The same transport issue also affects the direct attachment-upload requests because they use `baseUrl()` and attach the same authorization header. ### Attack Path 1. A user or agent configures a self-hosted instance using an address such as `OP_HOST=http://openproject.internal`. 2. The CLI preserves the plaintext HTTP scheme w ...[truncated 1598 chars]
- Remediation
- ## Remediation Suggestions 1. Parse `OP_HOST` using the standard URL parser and reject every protocol except HTTPS: ```js function baseUrl() { let url; try { url = new URL(CFG.host.includes('://') ? CFG.host : `https://${CFG.host}`); } catch { throw new Error('OP_HOST must be a valid OpenProject URL'); } if (url.protocol !== 'https:') { throw new Error('OP_HOST must use HTTPS to protect the API token'); } url.pathname = `${url.pathname.replace(/\/+$/, '')}/api/v3`; return url.toString().replace(/\/$/, ''); } ``` 2. If plaintext HTTP is required for local development, require an explicit option such as `OP_ALLOW_INSECURE_HTTP=true` and permit it only for loopback addresses such as `127.0.0.1`, `::1`, or `localhost`. Emit a prominent warning when this mode is active. 3. Do not treat arbitrary strings beginning with `http` as valid URLs. Validate the scheme, hostname, optional port, username, password, and path explicitly. 4. Ensure all request paths remain relative to the validated OpenProject origin. Avoid accepting absolute URLs in `opFetch()` unless they are separately validated as same-origin, preventing future callers from accidentally forwarding the authorization header to another host. 5. Apply the same validated HTTPS origin to all direct upload requests for work-package, meeting, and wiki attachments. 6. Update `README.md` and `SKILL.md` to state that remote and production OpenProject instances must use HTTPS with valid certificate verification. 7. After deployment of the fix, revoke and replace any API token that may previously have been used with an HTTP endpoint.
