T09 · Insecure Skill Coding Practices
- Location
scripts/gitlab_api.js:44- Finding
Unvalidated CLI Input Enables GitLab API Route and Query Manipulation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/gitlab_api.js, lines 44–57
Vulnerability Type: Unvalidated API path and query interpolation
Risk Level: MediumVulnerable Code
javascript async function listMergeRequests(projectId, state = 'opened') { console.log(`Listing ${state} MRs for project ${projectId}...`); // If projectId is 'jorgermp/repo', we need to URL encode it or use ID const id = encodeURIComponent(projectId); const data = await request(`/projects/${id}/merge_requests?state=${state}`); console.log(JSON.stringify(data, null, 2)); } async function commentOnMergeRequest(projectId, mrIid, body) { console.log(`Commenting on MR #${mrIid} in ${projectId}...`); const id = encodeURIComponent(projectId); const data = await request(`/projects/${id}/merge_requests/${mrIid}/notes`, 'POST', { body }); console.log(`Comment added: ${data.web_url}`); }Technical Analysis
The
mrIidcommand-line argument is inserted directly into the URL path without verifying that it is a positive integer. A value containing path traversal segments can therefore alter the intended GitLab API route. Whenfetchparses the resulting URL, dot segments such as..are normalized before the request is sent.For example, an
mrIidof../issues/1produces the following URL path before normalization:text /projects/<project>/merge_requests/../issues/1/notesURL normalization changes it to:
text /projects/<project>/issues/1/notesConsequently, a command presented as adding a merge-request comment can instead add a note to an issue.
The
stateargument is also concatenated directly into a query string without encoding or allowlist validation. An attacker can supply query delimiters and inject additional GitLab API query parameters, potentially changing the scope or semantics of the merge-request listing operation.Attack Path
...[truncated 1300 chars]
- Remediation
View remediation
Remediation Suggestions
-
Require merge-request IIDs to be positive integers before constructing the request:
javascript function requirePositiveInteger(value, fieldName) { if (!/^[1-9]\d*$/.test(String(value))) { throw new Error(`${fieldName} must be a positive integer.`); } return String(value); } async function commentOnMergeRequest(projectId, mrIid, body) { const id = encodeURIComponent(projectId); const validatedIid = requirePositiveInteger(mrIid, 'mr_iid'); const data = await request( `/projects/${id}/merge_requests/${validatedIid}/notes`, 'POST', { body } ); console.log(`Comment added: ${data.web_url}`); } -
Restrict merge-request state to GitLab's documented values:
javascript const ALLOWED_MR_STATES = new Set(['opened', 'closed', 'merged', 'all']); function validateMrState(state) { if (!ALLOWED_MR_STATES.has(state)) { throw new Error('Invalid merge-request state.'); } return state; } -
Construct query strings with
URLSearchParamsinstead of direct interpolation:javascript const params = new URLSearchParams({ state: validateMrState(state) }); const data = await request( `/projects/${id}/merge_requests?${params.toString()}` ); -
Validate that every required command argument is present before making a network request.
-
Apply strict validation to every value used as a URL path segment, even where encoding is also performed.
-
Use a token with the minimum GitLab permissions necessary and avoid broadly privileged personal access tokens where a narrower project or group access token is sufficient.
-
