{"record":{"id":"5777598e56db2110","repo":"abhigyanpatwari/GitNexus","slug":"only-https-and-http-git-urls-are-allowed","errorCode":null,"errorMessage":"Only https:// and http:// git URLs are allowed","messagePattern":"Only https:// and http:// git URLs are allowed","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/server/git-clone.ts","lineNumber":87,"sourceCode":"  'metadata.azure.com',\n  'metadata.internal',\n]);\n\n/**\n * Validate a git URL to prevent SSRF attacks.\n * Only allows https:// and http:// schemes. Blocks private/internal addresses,\n * IPv6 private ranges, cloud metadata hostnames, and numeric IP encodings.\n */\nexport function validateGitUrl(url: string): void {\n  let parsed: URL;\n  try {\n    parsed = new URL(url);\n  } catch {\n    throw new Error('Invalid URL');\n  }\n\n  if (!['https:', 'http:'].includes(parsed.protocol)) {\n    throw new Error('Only https:// and http:// git URLs are allowed');\n  }\n\n  const host = parsed.hostname.toLowerCase();\n\n  // Block known dangerous hostnames (cloud metadata services)\n  if (BLOCKED_HOSTNAMES.has(host)) {\n    throw new Error('Cloning from private/internal addresses is not allowed');\n  }\n\n  // Strip IPv6 brackets if present (URL parser behavior varies across Node versions)\n  let normalizedHost = host;\n  if (host.startsWith('[') && host.endsWith(']')) {\n    normalizedHost = host.slice(1, -1);\n  }\n\n  // Check if this is an IPv6 address\n  // Use manual colon detection as fallback since isIP may return 0 for some\n  // normalized IPv6 forms (e.g. ::ffff:7f00:1)","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/server/git-clone.ts#L69-L105","documentation":"After a successful URL parse, validateGitUrl allows only the https: and http: protocols; anything else — ssh:, git:, file:, ftp: — is rejected before any network activity. GitNexus's server-side clone path deliberately accepts only plain HTTP(S) so credentials and local filesystem access cannot be smuggled through other schemes.","triggerScenarios":"POST /api/analyze with url='ssh://git@github.com/user/repo.git', 'git://github.com/user/repo.git', or 'file:///srv/repos/repo' — all parse cleanly but fail the protocol allowlist. (scp-style 'git@github.com:repo' never gets here; it dies earlier as 'Invalid URL'.)","commonSituations":"Pasting the SSH remote copied from `git remote -v`; attempting file:// clones of locally mounted repos; legacy git:// protocol URLs; internal mirrors that only expose ssh.","solutions":["Convert the remote to https form: https://github.com/user/repo.git (embed a token if the repo is private, per the API's token support)","For a repo already on the server's disk, use the 'path' field (absolute path) instead of a url","Ask the mirror operator to expose HTTPS"],"exampleFix":"# before\nurl = 'ssh://git@github.com/user/repo.git'\n\n# after\nurl = 'https://github.com/user/repo.git'","handlingStrategy":"validation","validationCode":"function toHttpsGitUrl(raw) {\n  const s = raw.trim();\n  if (s.startsWith('git@')) return 'https://' + s.slice(4).replace(':', '/'); // git@host:owner/repo -> https://host/owner/repo\n  const u = new URL(s);\n  if (u.protocol !== 'https:' && u.protocol !== 'http:') throw new Error('only https/http supported');\n  return u.href;\n}","typeGuard":"function isHttpGitUrl(s) {\n  try { const u = new URL(s); return u.protocol === 'https:' || u.protocol === 'http:'; } catch { return false; }\n}","tryCatchPattern":"try { validateGitUrl(url); }\ncatch (e) {\n  if (/Only https:// and http:///.test(e.message)) { validateGitUrl(toHttpsGitUrl(url)); /* retry once with converted url */ }\n  else throw e;\n}","preventionTips":["Copy the HTTPS remote, not the SSH one, from `git remote -v`","Convert git@host:path and ssh:// forms to https://host/path client-side","For on-disk repos use the path option instead of a URL scheme"],"tags":["git-clone","url-scheme","validation","ssrf-guard"],"backgroundTag":"unsupported-url-scheme","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}