{"record":{"id":"788454e764e55a7e","repo":"nexu-io/open-design","slug":"public-github-repository-metric-only-supports-http","errorCode":null,"errorMessage":"public_github_repository_metric only supports https://api.github.com repository URLs","messagePattern":"public_github_repository_metric only supports https://api\\.github\\.com repository URLs","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"apps/daemon/src/live-artifacts/refresh.ts","lineNumber":661,"sourceCode":"    status: compactExecOutput(status),\n    recentCommits: compactExecOutput(recentCommits),\n    diffStat: compactExecOutput(diffStat),\n  });\n}\n\nfunction selectGithubRepositoryApiUrl(input: PublicGithubRepositoryMetricInput): URL {\n  const rawUrl = optionalString(input.url, 'input.url');\n  if (rawUrl === undefined) throw new Error('public_github_repository_metric requires input.url');\n\n  let url: URL;\n  try {\n    url = new URL(rawUrl);\n  } catch {\n    throw new Error('public_github_repository_metric input.url must be a valid URL');\n  }\n\n  if (url.protocol !== 'https:' || url.hostname !== 'api.github.com') {\n    throw new Error('public_github_repository_metric only supports https://api.github.com repository URLs');\n  }\n  if (!/^\\/repos\\/[A-Za-z0-9_.-]+\\/[A-Za-z0-9_.-]+$/.test(url.pathname)) {\n    throw new Error('public_github_repository_metric only supports /repos/{owner}/{repo} URLs');\n  }\n  url.search = '';\n  url.hash = '';\n  url.username = '';\n  url.password = '';\n  return url;\n}\n\nfunction selectGithubFields(input: PublicGithubRepositoryMetricInput): string[] {\n  if (input.fields === undefined) return ['stargazers_count', 'full_name', 'html_url', 'updated_at'];\n  if (!Array.isArray(input.fields)) throw new Error('input.fields must be an array of strings');\n  const fields = input.fields.filter((field): field is string => typeof field === 'string');\n  if (fields.length !== input.fields.length) throw new Error('input.fields must be an array of strings');\n  return fields.slice(0, 20);\n}","sourceCodeStart":643,"sourceCodeEnd":679,"githubUrl":"https://github.com/nexu-io/open-design/blob/5be4028344c2eb4c667c5a97bda8f750c5597ef7/apps/daemon/src/live-artifacts/refresh.ts#L643-L679","documentation":"Thrown by selectGithubRepositoryApiUrl after the URL parses successfully but its protocol is not 'https:' or its hostname is not exactly 'api.github.com'. This is a defense-in-depth allow-list: the daemon will only ever issue requests to the GitHub REST API origin, never to arbitrary hosts or the github.com web frontend. It prevents SSRF and credential leakage via attacker-supplied URLs.","triggerScenarios":"input.url is a valid URL but points at 'https://github.com/...', 'http://api.github.com/...' (wrong scheme), a lookalike host like 'https://api.github.com.evil.com/...', or a non-GitHub host entirely.","commonSituations":"Model supplies the repository's HTML URL (github.com) instead of the API URL (api.github.com); developer pastes from browser address bar; proxy or staging environment replaces the host; confusion between REST API and GraphQL endpoints.","solutions":["Convert the web URL to its API form: replace host 'github.com' with 'api.github.com' and prefix path with '/repos' if missing.","Ensure the scheme is exactly 'https:' (not 'http:').","Do not attempt to point this tool at a GitHub Enterprise host; the allow-list intentionally excludes enterprise origins."],"exampleFix":"// before\ninput: { url: 'https://github.com/octocat/Hello-World' }\n// after\ninput: { url: 'https://api.github.com/repos/octocat/Hello-World' }","handlingStrategy":"validation","validationCode":"function toGithubApiUrl(repoUrl: string): URL {\n  const u = new URL(repoUrl);\n  if (u.hostname === 'github.com' && /^\\/[\\w.-]+\\/[\\w.-]+$/.test(u.pathname)) {\n    return new URL(`https://api.github.com/repos${u.pathname}`);\n  }\n  if (u.protocol === 'https:' && u.hostname === 'api.github.com') return u;\n  throw new Error('not a GitHub API URL');\n}","typeGuard":"function isGithubApiUrl(v: unknown): v is string {\n  if (typeof v !== 'string') return false;\n  try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'api.github.com'; } catch { return false; }\n}","tryCatchPattern":null,"preventionTips":["Always use the api.github.com host, not github.com.","Pin the scheme to https in the authoring helper.","Do not extend the allow-list to GitHub Enterprise without a deliberate security review."],"tags":["validation","ssrf","allow-list","github-api","live-artifacts"],"backgroundTag":null,"analyzedSha":"5be4028344c2eb4c667c5a97bda8f750c5597ef7","analyzedAt":"2026-08-12T12:03:58.812Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}