{"record":{"id":"0aa395d8d003dff4","repo":"can1357/oh-my-pi","slug":"invalid-repo-repo-r","errorCode":null,"errorMessage":"invalid repo {repo!r}","messagePattern":"invalid repo (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":260,"sourceCode":"    \"ROBOMP_GIT_HTTP_AUTH\",\n    \"GITHUB_TOKEN\",\n    \"GH_TOKEN\",\n    \"GITHUB_WEBHOOK_SECRET\",\n    \"ROBOMP_REPLAY_TOKEN\",\n    \"ROBOMP_GH_PROXY_HMAC_KEY\",\n)\n\n\n@dataclass(slots=True, frozen=True)\nclass _RemoteAuth:\n    url: str\n    token: str | None\n    auth_url: str | None\n\n\ndef _validate_repo_name(repo: str) -> None:\n    if not _GITHUB_REPO_RE.fullmatch(repo) or \"/..\" in repo or \"../\" in repo:\n        raise HTTPException(400, f\"invalid repo {repo!r}\")\n\n\ndef _github_url_for_repo(repo: str) -> str:\n    _validate_repo_name(repo)\n    return f\"https://github.com/{repo}.git\"\n\n\ndef _git_probe_env(repo_dir: Path) -> dict[str, str]:\n    env = {**os.environ, \"GIT_TERMINAL_PROMPT\": \"0\", \"GIT_ASKPASS\": \"\", \"SSH_ASKPASS\": \"\"}\n    for key in _GIT_PROBE_SCRUBBED_ENV_KEYS:\n        env.pop(key, None)\n    env.update(_safe_directory_env(repo_dir))\n    return env\n\n\ndef _read_remote_urls(repo_dir: Path, slot_uid: int | None = None, *, push: bool = False) -> list[str]:\n    \"\"\"Read every configured fetch URL or push URL for `origin` without contacting it.\"\"\"\n    env = _git_probe_env(repo_dir)","sourceCodeStart":242,"sourceCodeEnd":278,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L242-L278","documentation":"`_validate_repo_name` rejects repo identifiers that fail _GITHUB_REPO_RE (owner/name shape) or contain traversal fragments ('/..' or '../'). It guards every URL and path derived from the repo name, raising this HTTP 400 before any GitHub request or filesystem access.","triggerScenarios":"Calling any repo-taking endpoint (_pool_dir users, workflow run/job listing, job log tail, git URL builders) with values like 'owner', '../etc', 'owner/../repo', empty string, or names with illegal characters.","commonSituations":"Clients passing a bare repo name without the owner prefix; shell variables expanding to relative paths; trailing-dot or double-dot path fragments from misconfigured remotes; injection probing against the proxy.","solutions":["Pass the fully qualified 'owner/repo' identifier exactly as it appears on GitHub (e.g. 'octocat/hello-world').","Strip any '.git' suffix or URL prefix before sending — the regex expects the bare owner/name form.","Sanitize path fragments: reject/normalize any value containing '..' or leading dots client-side.","If the repo legitimately fails the regex (unusual characters in the name), confirm the exact casing/characters against the GitHub URL and use the canonical form."],"exampleFix":"// before\nawait proxy.get(\"/workflows/runs\", params={\"repo\": \"hello-world\"})\n// after\nawait proxy.get(\"/workflows/runs\", params={\"repo\": \"octocat/hello-world\"})","handlingStrategy":"validation","validationCode":"import re\nREPO_RE = re.compile(r\"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$\")\ndef safe_repo(repo: str) -> str:\n    repo = repo.removesuffix(\".git\")\n    if not REPO_RE.fullmatch(repo) or \"/..\" in repo or \"../\" in repo:\n        raise ValueError(f\"invalid repo {repo!r}; expected 'owner/name'\")\n    return repo","typeGuard":"def is_repo_name(v: object) -> TypeGuard[str]:\n    return (\n        isinstance(v, str)\n        and \"/\" in v\n        and not v.startswith(\"/\")\n        and \"/..\" not in v\n        and \"../\" not in v\n        and all(p and not p.startswith(\".\") for p in v.split(\"/\"))\n    )","tryCatchPattern":"try:\n    resp = http.get(f\"{base}/workflows/runs\", params={\"repo\": repo})\n    resp.raise_for_status()\nexcept httpx.HTTPStatusError as e:\n    if e.response.status_code == 400 and \"invalid repo\" in e.response.text:\n        raise ValueError(f\"repo {repo!r} must be a clean 'owner/name' identifier\") from e\n    raise","preventionTips":["Always send the full 'owner/name'; never a bare repo name or a URL","Strip '.git' suffixes and URL prefixes before sending","Reject any repo input containing '..' or leading dots at the CLI boundary","Source repo names from validated config, not raw shell expansion"],"tags":["http-400","input-validation","path-traversal","github"],"backgroundTag":"path-traversal-rejected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}