T09 · Insecure Skill Coding Practices
Warning
- Location
- api.js:171
- Finding
- Authenticated GitHub API Path Injection Through Unvalidated Repository Name## Vulnerability Details **File Location**: `api.js:171-198`; permissive action schema at `index.js:88-96` **Vulnerability Type**: Authenticated API path injection with unrestricted request-body properties **Risk Level**: Medium ### Vulnerable Code ```js async function createIssue(args, context) { const username = await getUsername(context); const { repo, title, body } = args; if (!title) { throw new Error('Issue title required'); } const url = `${GITHUB_API}/repos/${username}/${repo}/issues`; const response = await fetch(url, { method: 'POST', headers: getAuthHeaders(context), body: JSON.stringify({ title, body: body || '', ...(args.extra || {}) }) }); if (!response.ok) { const error = await response.json(); throw new Error(`Failed to create issue: ${error.message || response.status}`); } const issue = await response.json(); return { number: issue.number, title: issue.title, url: issue.html_url, state: issue.state }; } ``` The corresponding action schema permits an unrestricted `extra` object: ```js create_issue: { description: 'Create a new issue', parameters: { type: 'object', properties: { repo: { type: 'string' }, title: { type: 'string' }, body: { type: 'string' }, extra: { type: 'object' } }, required: ['repo', 'title'] }, handler: createIssueHandler }, ``` ### Technical Analysis The attacker-controlled `repo` value is directly interpolated into a URL path without validation or path-segment encoding: ```js `${GITHUB_API}/repos/${username}/${repo}/issues` ``` Values containing traversal components such as `../` can be normalized by the URL implementation before the request ...[truncated 2571 chars]
- Remediation
- ## Remediation Suggestions 1. Validate repository and owner names before constructing URLs. Reject path separators, traversal components, query delimiters, fragments, control characters, and percent-encoded equivalents. ```js const GITHUB_NAME_PATTERN = /^[A-Za-z0-9_.-]+$/; function validateGitHubName(value, field) { if ( typeof value !== 'string' || !value || !GITHUB_NAME_PATTERN.test(value) || value === '.' || value === '..' ) { throw new Error(`Invalid GitHub ${field}`); } return value; } ``` 2. Encode every dynamic URL path segment independently: ```js const safeUsername = encodeURIComponent( validateGitHubName(username, 'username') ); const safeRepo = encodeURIComponent( validateGitHubName(repo, 'repository name') ); const url = `${GITHUB_API}/repos/${safeUsername}/${safeRepo}/issues`; ``` 3. Remove the unrestricted `extra` object. Explicitly copy only supported issue fields: ```js const payload = { title, body: body || '', ...(Array.isArray(args.labels) ? { labels: args.labels } : {}), ...(Array.isArray(args.assignees) ? { assignees: args.assignees } : {}), ...(Number.isInteger(args.milestone) ? { milestone: args.milestone } : {}) }; ``` 4. Add `additionalProperties: false` to the action schema and define strict types, lengths, ranges, and array-item constraints for every accepted property. 5. Apply equivalent path validation and encoding to all functions that interpolate `owner`, `repo`, username, branch, or other caller-controlled values into GitHub API URLs. 6. Use fine-grained GitHub tokens restricted to the required repositories and operations. Avoid classic tokens with broad `repo` scope where possible. 7. Add regression tests covering `../`, encoded traversal, `/`, `?`, `#`, b ...[truncated 72 chars]
