T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/raven-transfer.mjs:9
- Finding
- Unrestricted API Base Override Can Exfiltrate Credentials and Financial Data<![CDATA[ ## Vulnerability Details **File Location**: `scripts/raven-transfer.mjs:9` and `scripts/raven-transfer.mjs:234-261` **Additional Location**: `tests/contract-live.test.mjs:6-25` **Vulnerability Type**: Arbitrary authenticated request destination / sensitive-data disclosure **Risk Level**: High ### Vulnerable Code From `scripts/raven-transfer.mjs:9`: ```js const API_BASE = process.env.RAVEN_API_BASE || "https://integrations.getravenbank.com/v1"; ``` From `scripts/raven-transfer.mjs:234-261`: ```js export async function ravenRequest(method, path, body, options = {}) { const { retries = 0, operation = "request", timeoutMs = TIMEOUT_MS, fetchImpl = fetch, } = options; let lastError; for (let attempt = 0; attempt <= retries; attempt += 1) { const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { const response = await fetchImpl(`${API_BASE}${path}`, { method, headers: { Authorization: `Bearer ${getApiKey()}`, "Content-Type": "application/json", }, ...(body ? { body: JSON.stringify(body) } : {}), signal: controller.signal, }); const data = await response.json().catch(() => ({})); ``` The live-contract tests repeat this trust model in `tests/contract-live.test.mjs:6-25`: ```js const apiBase = process.env.RAVEN_API_BASE || "https://integrations.getravenbank.com/v1"; const runLive = process.env.RAVEN_CONTRACT_TESTS === "1"; const apiKey = (() => { try { return resolveApiKey(process.env); } catch { return null; } })(); async function ravenLive(method, path, body) { const response = await fetch(`${apiBase}${path}`, { method, headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json", }, ...(body ? { body: JSON.stringify(body) } : {}), }); const data = await response.json().catch(() => ({})); return { response, d ...[truncated 3148 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Remove the endpoint override unless it is operationally required.** Use a fixed Raven API origin for production operation. 2. **Strictly validate any required override before sending credentials:** ```js const ALLOWED_API_ORIGINS = new Set([ "https://integrations.getravenbank.com", // Add an official sandbox origin only if Raven documents and supports it. ]); function resolveApiBase(rawValue) { const url = new URL( rawValue || "https://integrations.getravenbank.com/v1" ); if (url.protocol !== "https:") { throw new Error("RAVEN_API_BASE must use HTTPS."); } if (url.username || url.password) { throw new Error("RAVEN_API_BASE must not contain URL credentials."); } if (!ALLOWED_API_ORIGINS.has(url.origin)) { throw new Error("RAVEN_API_BASE is not an approved Raven endpoint."); } return url.toString().replace(/\/+$/, ""); } ``` 3. **Apply validation before credential resolution or request construction.** Do not load or attach the API key until the destination has passed validation. 4. **Do not rely on suffix matching alone.** Checks such as `hostname.endsWith("getravenbank.com")` can be implemented incorrectly and may accept lookalike domains. Prefer an exact allowlist of documented hosts. 5. **Separate development and production credentials.** If arbitrary endpoints are genuinely required for local testing, require an explicit unsafe-development flag and refuse to attach production credentials. Use a dedicated, low-privilege test credential. 6. **Apply the same endpoint validation to `tests/contract-live.test.mjs`.** Prefer importing a shared validated endpoint resolver rather than maintaining a second request implementation. 7. **Reduce credential privileges at the provider.** Use a Raven key restricted to only the API operations required by this Skill, if the provider supports scopes or account-level restrictions. ...[truncated 757 chars]
