T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/lib/redmine.js:1
- Finding
- Redmine API credentials can be transmitted over plaintext HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/lib/redmine.js:1-40` **Vulnerability Type**: Insufficient transport and destination validation for sensitive credentials **Risk Level**: Medium ### Vulnerable Code ```js const REDMINE_URL = process.env.REDMINE_URL || process.env.REDMINE_API_KEY?.startsWith('http') || ''; const API_KEY = process.env.REDMINE_API_KEY || process.env.REDMINE_BASE_URL || ''; function getBaseUrl() { if (REDMINE_URL?.startsWith('http')) { return REDMINE_URL; } if (API_KEY?.startsWith('http')) { return API_KEY; } if (!REDMINE_URL || !API_KEY) { throw new Error('REDMINE_URL and REDMINE_API_KEY environment variables are required. Configure them with: openclaw skills config epragma-redmine-issue set REDMINE_URL <your-redmine-url> and openclaw skills config epragma-redmine-issue set REDMINE_API_KEY <your-api-key>'); } return REDMINE_URL; } function getApiKey() { if (REDMINE_URL?.startsWith('http')) { return API_KEY; } if (API_KEY?.startsWith('http')) { return process.env.REDMINE_BASE_URL || ''; } return API_KEY; } export { getBaseUrl, getApiKey }; async function request(endpoint, options = {}) { const baseUrl = getBaseUrl(); const apiKey = getApiKey(); const url = `${baseUrl}${endpoint}`; const headers = { 'Content-Type': 'application/json', 'X-Redmine-API-Key': apiKey, ...options.headers, }; const response = await fetch(url, { ...options, headers, }); ``` ### Technical Analysis The destination check accepts any value beginning with `http`, which includes unencrypted `http://` endpoints. Every API request then attaches the Redmine credential in the `X-Redmine-API-Key` header. Issue descriptions, comments, time-entry information, and mutation request bodies may also be transmitted over the same unencrypted connection. The configuration logic additionally assigns multiple meanings to credential-related environment variables. `REDMINE_API_KEY` m ...[truncated 2526 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse the configured endpoint using `new URL()` rather than relying on a prefix check. 2. Require the `https:` protocol in normal operation: ```js function getBaseUrl() { const rawUrl = process.env.REDMINE_URL; if (!rawUrl) { throw new Error('REDMINE_URL is required'); } const parsed = new URL(rawUrl); if (parsed.protocol !== 'https:') { throw new Error('REDMINE_URL must use HTTPS'); } parsed.username = ''; parsed.password = ''; return parsed.toString().replace(/\/$/, ''); } ``` 3. Give each environment variable exactly one purpose. Use only `REDMINE_URL` for the endpoint and `REDMINE_API_KEY` for the credential. 4. Remove the swapped-variable fallback involving `REDMINE_BASE_URL`. 5. Reject API-key values that resemble URLs and reject endpoint URLs containing embedded credentials. 6. If plaintext HTTP is required for local development, place it behind an explicit opt-in setting and restrict it to loopback hosts such as `127.0.0.1` or `localhost`. 7. Recommend a dedicated, least-privileged Redmine API account whose permissions are limited to the operations the user needs. 8. Rotate any API key that may previously have been transmitted over HTTP. 9. Update `SKILL.md` and `_meta.json` so the top-level description discloses that the Skill supports issue and time-entry mutation, rather than describing it only as a read operation. ]]>
