T09 · Insecure Skill Coding Practices
Warning
- Location
- index.js:195
- Finding
- Unescaped User-Controlled API Path and Query Components<![CDATA[ ## Vulnerability Details **File Location**: `index.js`, lines 195–234 **Vulnerability Type**: Authenticated API path and query injection **Risk Level**: Medium ### Vulnerable Code ```javascript export async function anycrawl_crawl_status({ job_id }) { if (!job_id) { throw new Error("Job ID is required"); } return await anycrawlRequest(`/crawl/${job_id}/status`); } /** * Get crawl results (paginated) * * @param {Object} params * @param {string} params.job_id - Crawl job ID (required) * @param {number} params.skip - Number of results to skip (default: 0) * * @returns {Promise<Object>} Crawled pages with content */ export async function anycrawl_crawl_results({ job_id, skip = 0 }) { if (!job_id) { throw new Error("Job ID is required"); } return await anycrawlRequest(`/crawl/${job_id}?skip=${skip}`); } /** * Cancel a crawl job * * @param {Object} params * @param {string} params.job_id - Crawl job ID (required) * * @returns {Promise<Object>} Cancellation confirmation */ export async function anycrawl_crawl_cancel({ job_id }) { if (!job_id) { throw new Error("Job ID is required"); } return await anycrawlRequest(`/crawl/${job_id}`, { method: "DELETE" }); } ``` ### Technical Analysis The `job_id` value is inserted directly into URL paths without validation or percent-encoding. The `skip` value is likewise interpolated directly into a query string without enforcing its documented numeric type. An attacker who can supply tool arguments may include path separators, traversal components, query delimiters, or fragment delimiters. When the resulting string is processed as a URL, these characters can alter the intended API path or query parameters rather than remaining part of a single job identifier. The risk is particularly significant in `anycrawl_crawl_cancel`, because the manipulated endpoint receives an authenticated `DELETE` request. The shared `anycrawlRequest` function automatically at ...[truncated 2183 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Validate `job_id` against the exact identifier format issued by AnyCrawl. If job IDs are UUIDs, use a strict UUID allowlist: ```javascript function validateJobId(jobId) { if (typeof jobId !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(jobId)) { throw new Error("Invalid Job ID"); } return jobId; } ``` 2. Percent-encode every value inserted into a path segment: ```javascript const encodedJobId = encodeURIComponent(validateJobId(job_id)); return await anycrawlRequest(`/crawl/${encodedJobId}/status`); ``` 3. Require `skip` to be a non-negative safe integer: ```javascript if (!Number.isSafeInteger(skip) || skip < 0) { throw new Error("Skip must be a non-negative safe integer"); } ``` 4. Construct query strings with `URLSearchParams` instead of string interpolation: ```javascript const encodedJobId = encodeURIComponent(validateJobId(job_id)); const query = new URLSearchParams({ skip: String(skip) }); return await anycrawlRequest(`/crawl/${encodedJobId}?${query.toString()}`); ``` 5. Add defense-in-depth checks inside `anycrawlRequest`, such as allowing only explicitly supported endpoint patterns and rejecting endpoint strings containing traversal components or fragments. 6. Add tests using malicious values containing `../`, `/`, `?`, `&`, `#`, and percent-encoded equivalents to verify that they are rejected or safely encoded. ]]>
