T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/issues.mjs:32
- Finding
- Redmine credentials can be transmitted over unencrypted HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/issues.mjs:32-68` **Related Documentation**: `SKILL.md:9-14, 39-43` **Vulnerability Type**: Missing transport security enforcement for sensitive credentials **Risk Level**: High ### Vulnerable Code ```js const REDMINE_URL = process.env.REDMINE_URL?.replace(/\/$/, ""); const REDMINE_API_KEY = process.env.REDMINE_API_KEY; const REDMINE_USERNAME = process.env.REDMINE_USERNAME; const REDMINE_PASSWORD = process.env.REDMINE_PASSWORD; if (!REDMINE_URL) { console.error("Missing REDMINE_URL"); process.exit(2); } const headers = { "Content-Type": "application/json", "Accept": "application/json", }; if (REDMINE_API_KEY) { headers["X-Redmine-API-Key"] = REDMINE_API_KEY; } const auth = (!REDMINE_API_KEY && REDMINE_USERNAME && REDMINE_PASSWORD) ? `Basic ${Buffer.from(`${REDMINE_USERNAME}:${REDMINE_PASSWORD}`).toString("base64")}` : null; if (auth) headers["Authorization"] = auth; if (!headers["X-Redmine-API-Key"] && !headers["Authorization"]) { console.error("Missing auth: set REDMINE_API_KEY or REDMINE_USERNAME+REDMINE_PASSWORD"); process.exit(3); } async function requestJson(path, { method = "GET", params = {}, body = undefined } = {}) { const url = new URL(`${REDMINE_URL}${path}`); for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null && `${v}`.length > 0) url.searchParams.set(k, `${v}`); } const res = await fetch(url, { headers, method, body: body !== undefined ? JSON.stringify(body) : undefined, }); ``` ### Technical Analysis The script obtains an API key or username and password from environment variables and attaches them to every Redmine request. This authentication behavior is necessary for accessing protected Redmine resources, but the configured `REDMINE_URL` is not validated to require HTTPS. If `REDMINE_URL` uses `http:`, the API key or HTTP Basic Authorization header is transmitted without transport encryption. Bas ...[truncated 1957 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Parse and validate `REDMINE_URL` before creating authentication headers or making requests: ```js let redmineBaseUrl; try { redmineBaseUrl = new URL(process.env.REDMINE_URL); } catch { console.error("REDMINE_URL must be a valid URL"); process.exit(2); } if (redmineBaseUrl.protocol !== "https:") { console.error("REDMINE_URL must use HTTPS"); process.exit(2); } if (redmineBaseUrl.username || redmineBaseUrl.password) { console.error("REDMINE_URL must not contain embedded credentials"); process.exit(2); } redmineBaseUrl.pathname = redmineBaseUrl.pathname.replace(/\/$/, ""); ``` 2. Construct request URLs relative to the validated base URL rather than concatenating unvalidated strings. 3. If HTTP support is essential for isolated local development, require a separate explicit opt-in such as `REDMINE_ALLOW_INSECURE_HTTP=true`, limit it to loopback addresses where possible, and emit a prominent warning. 4. Review redirect behavior and reject redirects to a different origin or a non-HTTPS destination before credentials can be forwarded. 5. Recommend narrowly scoped API keys rather than account passwords, and use read-only credentials for `get` and `list` workflows. 6. Document that users must trust the configured Redmine host and verify its TLS certificate. ]]>
