{"record":{"id":"b17838436a76eb3f","repo":"mastra-ai/mastra","slug":"github-owner-and-repo-may-only-contain-letters-nu","errorCode":null,"errorMessage":"GitHub owner and repo may only contain letters, numbers, dots, underscores, and dashes","messagePattern":"GitHub owner and repo may only contain letters, numbers, dots, underscores, and dashes","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"mastracode/sdk/src/plugins/install.ts","lineNumber":248,"sourceCode":"  let url: URL;\n  try {\n    url = new URL(urlPart);\n  } catch {\n    throw new Error(`Invalid GitHub URL: ${specifier}`);\n  }\n\n  if (url.hostname !== 'github.com') {\n    throw new Error('Only github.com plugin URLs are supported');\n  }\n\n  const [owner, rawRepo, ...rest] = url.pathname.split('/').filter(Boolean);\n  if (!owner || !rawRepo || rest.length > 0) {\n    throw new Error('GitHub plugin URL must be in the form https://github.com/owner/repo');\n  }\n\n  const repo = rawRepo.replace(/\\.git$/, '');\n  if (!/^[A-Za-z0-9_.-]+$/.test(owner) || !/^[A-Za-z0-9_.-]+$/.test(repo)) {\n    throw new Error('GitHub owner and repo may only contain letters, numbers, dots, underscores, and dashes');\n  }\n\n  return {\n    owner,\n    repo,\n    repoSpec: `${owner}/${repo}`,\n    ...(ref ? { ref } : {}),\n  };\n}\n","sourceCodeStart":230,"sourceCodeEnd":258,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/sdk/src/plugins/install.ts#L230-L258","documentation":"After structural validation, parseGithubUrl checks the owner and repo (with a trailing '.git' stripped) against /^[A-Za-z0-9_.-]+$/. Characters outside that set — e.g. URL-encoded, Unicode, or shell metacharacters — are rejected to prevent path/shell injection when the SDK clones the repo and writes into the plugin directory.","triggerScenarios":"Specifiers with percent-encoded or special characters in owner/repo, like 'https://github.com/own%65r/repo' resolving to odd names, 'https://github.com/owner/repo~branch', or names containing '+', '@', or spaces.","commonSituations":"Hand-editing the URL and introducing characters that are not valid in GitHub owner/repo names; pasting a URL from a system that URL-encoded the path; repo names with unusual suffixes.","solutions":["Use the exact owner/repo names as they appear on github.com (letters, numbers, dots, underscores, dashes only).","Remove URL encoding — write the literal ASCII name, not percent-encoded forms.","Drop non-standard suffixes like '~branch' from the repo segment; use the '#ref' fragment for refs instead."],"exampleFix":"// before\nawait installPlugin('https://github.com/acme/widgets%20pro');\n// after\nawait installPlugin('https://github.com/acme/widgets-pro');","handlingStrategy":"validation","validationCode":"const NAME_RE = /^[A-Za-z0-9_.-]+$/;\nfunction hasValidOwnerRepo(specifier: string): boolean {\n  try {\n    const [owner, repo = ''] = new URL(specifier.split('#')[0]).pathname.split('/').filter(Boolean);\n    return NAME_RE.test(owner) && NAME_RE.test(repo.replace(/\\.git$/, ''));\n  } catch {\n    return false;\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  await installPlugin(specifier);\n} catch (error) {\n  if (error instanceof Error && error.message.startsWith('GitHub owner and repo may only contain')) {\n    throw new Error(`Owner/repo in \"${specifier}\" contain unsupported characters; use the literal GitHub name`);\n  }\n  throw error;\n}","preventionTips":["Never percent-encode owner/repo names in plugin specifiers.","Test owner/repo against /^[A-Za-z0-9_.-]+$/ in your own URL builder.","Move refs/suffixes into the '#ref' fragment instead of the repo segment."],"tags":["plugin-install","validation","github","sanitization"],"backgroundTag":"invalid-identifier-characters","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}