{"record":{"id":"95e845c2f7c62957","repo":"vercel-labs/skills","slug":"unsupported-git-transport-ext","errorCode":null,"errorMessage":"Unsupported Git transport: ext","messagePattern":"Unsupported Git transport: ext","errorType":"exception","errorClass":"GitCloneError","httpStatus":null,"severity":"critical","filePath":"src/git.ts","lineNumber":237,"sourceCode":"    return (\n      `Authentication failed for ${url}.\\n` +\n      `  - For private repos, ensure you have access\\n` +\n      `  - Retry with SSH: npx skills add ${repo.sshUrl}\\n` +\n      `  - Check access with: gh auth status -h ${host} or ssh -T git@${host}`\n    );\n  }\n\n  return (\n    `Authentication failed for ${url}.\\n` +\n    `  - For private repos, ensure you have access\\n` +\n    `  - For SSH: Check your keys with 'ssh -T git@github.com'\\n` +\n    `  - For HTTPS: Run 'gh auth login' or configure git credentials`\n  );\n}\n\nexport async function cloneRepo(url: string, ref?: string): Promise<string> {\n  if (/^ext::/i.test(url)) {\n    throw new GitCloneError('Unsupported Git transport: ext', url);\n  }\n\n  const tempDir = await mkdtemp(join(tmpdir(), 'skills-'));\n  const cloneOptions = ref ? ['--depth', '1', '--branch', ref] : ['--depth', '1'];\n  const repo = parseGitHubRepoUrl(url);\n\n  try {\n    await createGitClient().clone(url, tempDir, cloneOptions);\n    return tempDir;\n  } catch (error) {\n    const errorMessage = error instanceof Error ? error.message : String(error);\n    const isTimeout = errorMessage.includes('block timeout') || errorMessage.includes('timed out');\n    const isAuthError = isAuthFailure(errorMessage);\n\n    if (isTimeout) {\n      await rm(tempDir, { recursive: true, force: true }).catch(() => {});\n      const seconds = Math.round(CLONE_TIMEOUT_MS / 1000);\n      throw new GitCloneError(","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/vercel-labs/skills/blob/435076e78988e1e6ec40d00b0b1d76bdbbc5419a/src/git.ts#L219-L255","documentation":"cloneRepo() rejects any Git URL using the ext:: transport before spawning git. The ext transport executes arbitrary shell commands via local helpers and is a well-known vector for command injection, so the skills CLI hard-blocks it.","triggerScenarios":"Passing a source like 'ext::ssh -iKey git@server repo' (or any URL matching /^ext::/i) to cloneRepo — via 'skills add <url>', 'skills use', or an update flow that clones.","commonSituations":"Users copying exotic Git transport URLs from internal docs or older automation scripts; CI configurations that historically used ext:: wrappers for SSH proxying.","solutions":["Use a plain SSH URL instead: git@host:owner/repo.git or ssh://git@host/owner/repo.git","For proxy needs, configure ~/.ssh/config (ProxyCommand) rather than the ext transport","Never pass ext:: URLs to this CLI — there is no flag to allow them by design"],"exampleFix":"# before\nskills add \"ext::ssh -i /key git@git.corp:team/skills.git\"\n# after\nskills add git@git.corp:team/skills.git   # with ProxyCommand in ~/.ssh/config","handlingStrategy":"validation","validationCode":"function isSafeGitUrl(url: string): boolean {\n  return !/^ext::/i.test(url) && /^(https?|git|ssh|file):|^[\\w.-]+@[\\w.-]+:/.test(url);\n}\nif (!isSafeGitUrl(source)) throw new Error(`Blocked unsafe git transport: ${source}`);","typeGuard":"function isUnsupportedTransport(e: unknown): e is Error & { url?: string } {\n  return e instanceof Error && /Unsupported Git transport/i.test(e.message);\n}","tryCatchPattern":"try { await cloneRepo(url); }\ncatch (e) {\n  if (isUnsupportedTransport(e)) {\n    throw new Error(`Convert ${url} to an ssh:// URL; ext:: is forbidden for security`);\n  }\n  throw e;\n}","preventionTips":["Normalize git URLs to https:// or git@host: forms in your tooling","Reject ext::, and generally transport-executing URLs, at input validation","Put proxy configuration in ~/.ssh/config instead of URL transports"],"tags":["security","git","command-injection","transport"],"backgroundTag":"git-unsupported-transport","analyzedSha":"435076e78988e1e6ec40d00b0b1d76bdbbc5419a","analyzedAt":"2026-08-28T17:47:53.369Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}