nexu-io/open-design · error · Error
public_github_repository_metric only supports https://api.gi
Error message
public_github_repository_metric only supports https://api.github.com repository URLs
What it means
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.
Source
Thrown at apps/daemon/src/live-artifacts/refresh.ts:661
status: compactExecOutput(status),
recentCommits: compactExecOutput(recentCommits),
diffStat: compactExecOutput(diffStat),
});
}
function selectGithubRepositoryApiUrl(input: PublicGithubRepositoryMetricInput): URL {
const rawUrl = optionalString(input.url, 'input.url');
if (rawUrl === undefined) throw new Error('public_github_repository_metric requires input.url');
let url: URL;
try {
url = new URL(rawUrl);
} catch {
throw new Error('public_github_repository_metric input.url must be a valid URL');
}
if (url.protocol !== 'https:' || url.hostname !== 'api.github.com') {
throw new Error('public_github_repository_metric only supports https://api.github.com repository URLs');
}
if (!/^\/repos\/[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(url.pathname)) {
throw new Error('public_github_repository_metric only supports /repos/{owner}/{repo} URLs');
}
url.search = '';
url.hash = '';
url.username = '';
url.password = '';
return url;
}
function selectGithubFields(input: PublicGithubRepositoryMetricInput): string[] {
if (input.fields === undefined) return ['stargazers_count', 'full_name', 'html_url', 'updated_at'];
if (!Array.isArray(input.fields)) throw new Error('input.fields must be an array of strings');
const fields = input.fields.filter((field): field is string => typeof field === 'string');
if (fields.length !== input.fields.length) throw new Error('input.fields must be an array of strings');
return fields.slice(0, 20);
}View on GitHub (pinned to 5be4028344)
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.
Example fix
// before
input: { url: 'https://github.com/octocat/Hello-World' }
// after
input: { url: 'https://api.github.com/repos/octocat/Hello-World' } Defensive patterns
Strategy: validation
Validate before calling
function toGithubApiUrl(repoUrl: string): URL {
const u = new URL(repoUrl);
if (u.hostname === 'github.com' && /^\/[\w.-]+\/[\w.-]+$/.test(u.pathname)) {
return new URL(`https://api.github.com/repos${u.pathname}`);
}
if (u.protocol === 'https:' && u.hostname === 'api.github.com') return u;
throw new Error('not a GitHub API URL');
} Type guard
function isGithubApiUrl(v: unknown): v is string {
if (typeof v !== 'string') return false;
try { const u = new URL(v); return u.protocol === 'https:' && u.hostname === 'api.github.com'; } catch { return false; }
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- public_github_repository_metric input.url must be a valid UR
- public_github_repository_metric only supports /repos/{owner}
- input.fields must be an array of strings
- connector refresh source requires connector metadata
- ${field} must be a dot-separated JSON path
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/788454e764e55a7e.
Report an issue: GitHub.