can1357/oh-my-pi · error · HTTPException

invalid repo {repo!r}

Error message

invalid repo {repo!r}

What it means

`_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.

Source

Thrown at python/robomp/src/proxy/server.py:260

    "ROBOMP_GIT_HTTP_AUTH",
    "GITHUB_TOKEN",
    "GH_TOKEN",
    "GITHUB_WEBHOOK_SECRET",
    "ROBOMP_REPLAY_TOKEN",
    "ROBOMP_GH_PROXY_HMAC_KEY",
)


@dataclass(slots=True, frozen=True)
class _RemoteAuth:
    url: str
    token: str | None
    auth_url: str | None


def _validate_repo_name(repo: str) -> None:
    if not _GITHUB_REPO_RE.fullmatch(repo) or "/.." in repo or "../" in repo:
        raise HTTPException(400, f"invalid repo {repo!r}")


def _github_url_for_repo(repo: str) -> str:
    _validate_repo_name(repo)
    return f"https://github.com/{repo}.git"


def _git_probe_env(repo_dir: Path) -> dict[str, str]:
    env = {**os.environ, "GIT_TERMINAL_PROMPT": "0", "GIT_ASKPASS": "", "SSH_ASKPASS": ""}
    for key in _GIT_PROBE_SCRUBBED_ENV_KEYS:
        env.pop(key, None)
    env.update(_safe_directory_env(repo_dir))
    return env


def _read_remote_urls(repo_dir: Path, slot_uid: int | None = None, *, push: bool = False) -> list[str]:
    """Read every configured fetch URL or push URL for `origin` without contacting it."""
    env = _git_probe_env(repo_dir)

View on GitHub (pinned to 9690622007)

Solutions

  1. Pass the fully qualified 'owner/repo' identifier exactly as it appears on GitHub (e.g. 'octocat/hello-world').
  2. Strip any '.git' suffix or URL prefix before sending — the regex expects the bare owner/name form.
  3. Sanitize path fragments: reject/normalize any value containing '..' or leading dots client-side.
  4. 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.

Example fix

// before
await proxy.get("/workflows/runs", params={"repo": "hello-world"})
// after
await proxy.get("/workflows/runs", params={"repo": "octocat/hello-world"})
Defensive patterns

Strategy: validation

Validate before calling

import re
REPO_RE = re.compile(r"^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$")
def safe_repo(repo: str) -> str:
    repo = repo.removesuffix(".git")
    if not REPO_RE.fullmatch(repo) or "/.." in repo or "../" in repo:
        raise ValueError(f"invalid repo {repo!r}; expected 'owner/name'")
    return repo

Type guard

def is_repo_name(v: object) -> TypeGuard[str]:
    return (
        isinstance(v, str)
        and "/" in v
        and not v.startswith("/")
        and "/.." not in v
        and "../" not in v
        and all(p and not p.startswith(".") for p in v.split("/"))
    )

Try / catch

try:
    resp = http.get(f"{base}/workflows/runs", params={"repo": repo})
    resp.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 400 and "invalid repo" in e.response.text:
        raise ValueError(f"repo {repo!r} must be a clean 'owner/name' identifier") from e
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/0aa395d8d003dff4. Report an issue: GitHub.