T05 · Unauthorized Access and Privilege Escalation
Error
- Location
- src/commands/job.ts:544
- Finding
- Attacker-Controlled Schema URI Enables Server-Side Request Forgery<![CDATA[ ## Vulnerability Details **File Location**: `src/commands/job.ts:49-53`, `src/commands/job.ts:544-566` **Vulnerability Type**: Server-Side Request Forgery through an untrusted marketplace schema URI **Risk Level**: High ### Vulnerable Code ```ts const schema = await resolveRequirementsSchema( Number(offering.serviceType), offering.requirementsSchemaURI, ); ``` ```ts async function loadOfferingSchemaFromUri(schemaUri: string): Promise<OfferingSchema | null> { let schemaPayload: unknown; if (schemaUri.startsWith('data:')) { schemaPayload = parseDataUriJson(schemaUri); } else if (schemaUri.startsWith('http://') || schemaUri.startsWith('https://')) { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30_000); try { const response = await fetch(schemaUri, { signal: controller.signal }); if (!response.ok) { throw new Error(`Failed to fetch requirements schema URI (${response.status} ${response.statusText})`); } const buf = await response.arrayBuffer(); if (buf.byteLength > 1_048_576) { throw new Error(`Schema too large (${buf.byteLength} bytes). Limit is 1MB`); } schemaPayload = JSON.parse(new TextDecoder().decode(buf)); } finally { clearTimeout(timeoutId); } } else if (schemaUri.trim().startsWith('{')) { schemaPayload = JSON.parse(schemaUri); } else { return null; } if (!isOfferingSchema(schemaPayload)) { throw new Error('requirementsSchemaURI did not resolve to a valid OfferingSchema document'); } return schemaPayload; } ``` ### Technical Analysis The `requirementsSchemaURI` value comes from marketplace offering data and is therefore controlled by the offering publisher. When a buyer creates a job with requirements, the CLI passes that value directly to `fetch()`. The implementation only verifies that the URI starts with `http://` or `https://`. It does not: - Reject loopback, privat ...[truncated 2874 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Permit only `https:` schema URLs unless plaintext HTTP is explicitly required by a narrowly controlled development mode. 2. Prefer an allowlist of trusted schema-hosting domains. 3. Before connecting, resolve the hostname and reject every address in loopback, private, link-local, multicast, carrier-grade NAT, documentation, reserved, and cloud metadata ranges for both IPv4 and IPv6. 4. Disable automatic redirects with `redirect: 'manual'`, or validate the protocol, hostname, and resolved address before following every redirect. 5. Defend against DNS rebinding by ensuring that the address used for the actual connection is the validated address. 6. Explicitly block common metadata destinations, including `169.254.169.254` and their IPv6 equivalents. 7. Stream response data and abort as soon as the one-megabyte limit is exceeded rather than calling `arrayBuffer()` first. 8. Apply separate connection, header, and body timeouts. 9. Consider retrieving schemas through a hardened backend proxy with network egress restrictions instead of fetching them directly from the user's machine. 10. Treat remote schemas as untrusted data and retain strict structural and semantic validation after retrieval. ]]>
