can1357/oh-my-pi · error · HTTPException

invalid workspace_key {workspace_key!r}

Error message

invalid workspace_key {workspace_key!r}

What it means

`_workspace_repo_dir` treats workspace_key as untrusted path input and enforces the shape `<repo_with_underscores>__<number>`. Keys containing '/', leading '.', or '..' are rejected with this HTTP 400 to prevent path traversal outside cfg.workspace_root.

Source

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

        if start_side is not None:
            start_side_str = _require_str(start_side, f"comments[{idx}].start_side")
            if start_side_str not in ("RIGHT", "LEFT"):
                raise HTTPException(400, f"comments[{idx}].start_side must be RIGHT or LEFT")
            comment["start_side"] = start_side_str
        comments.append(comment)
    return comments


def _pool_dir(cfg: Settings, repo: str) -> Path:
    _validate_repo_name(repo)
    return Path(cfg.workspace_root) / "_pool" / repo.replace("/", "__")


def _workspace_repo_dir(cfg: Settings, workspace_key: str) -> Path:
    # Defense-in-depth: workspace_key is constructed by `sandbox.workspace_key`
    # as `<repo_with_underscores>__<number>`. Reject anything outside that shape.
    if "/" in workspace_key or workspace_key.startswith(".") or ".." in workspace_key:
        raise HTTPException(400, f"invalid workspace_key {workspace_key!r}")
    return Path(cfg.workspace_root) / workspace_key / "repo"


def _resolve_token(cfg: Settings) -> str:
    if cfg.github_token is None:
        # Will already have been caught at startup, but stay defensive.
        raise HTTPException(500, "gh-proxy: GITHUB_TOKEN not configured")
    return cfg.github_token.get_secret_value()


def _resolve_hmac_key(cfg: Settings) -> bytes:
    if cfg.gh_proxy_hmac_key is None:
        raise HTTPException(500, "gh-proxy: ROBOMP_GH_PROXY_HMAC_KEY not configured")
    return cfg.gh_proxy_hmac_key.get_secret_value().encode("utf-8")


_ORIGIN_READ_TIMEOUT_SECONDS = 5.0

View on GitHub (pinned to 9690622007)

Solutions

  1. Construct workspace_key via the sandbox.workspace_key helper — repo with '/' replaced by underscores plus '__<number>' (e.g. owner_repo__1).
  2. Strip or replace '/' and leading dots from the repo component before deriving the key client-side.
  3. Use a workspace_key previously returned by the sandbox/session API rather than hand-building one.
  4. If a legitimate key is being rejected, check for hidden whitespace or encoding artifacts adding '..' segments.

Example fix

// before
key = f"{owner}/{repo}__1"
// after
key = f"{repo.replace('/', '_')}__1"  # e.g. sandbox.workspace_key(repo, 1)
Defensive patterns

Strategy: validation

Validate before calling

import re
WORKSPACE_KEY_RE = re.compile(r"^[^/.]+__\d+$")
if not WORKSPACE_KEY_RE.fullmatch(workspace_key):
    raise ValueError(f"workspace_key {workspace_key!r} must be '<repo>__<n>' with no slashes/dots")

Type guard

def is_workspace_key(v: object) -> TypeGuard[str]:
    return (
        isinstance(v, str)
        and "/" not in v
        and not v.startswith(".")
        and ".." not in v
        and "__" in v
        and v.rsplit("__", 1)[1].isdigit()
    )

Try / catch

try:
    resp = http.post(f"{base}/git/push", json=payload)
    resp.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.status_code == 400 and "invalid workspace_key" in e.response.text:
        raise ValueError("workspace_key must come from sandbox.workspace_key(repo, n)") from e
    raise

Prevention

When it happens

Trigger: Calling git_push_endpoint or git_push_release_endpoint with a workspace_key containing slashes (e.g. "owner/repo__1"), starting with '.', or containing '..' segments.

Common situations: Clients building workspace_key from the raw 'owner/repo' name instead of the underscore-flattened repo id; relative-path fragments leaking in from shell variables; tampering attempts during auth-bypass probing.

Related errors


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