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
- Construct workspace_key via the sandbox.workspace_key helper — repo with '/' replaced by underscores plus '__<number>' (e.g. owner_repo__1).
- Strip or replace '/' and leading dots from the repo component before deriving the key client-side.
- Use a workspace_key previously returned by the sandbox/session API rather than hand-building one.
- 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
- Always derive workspace_key from the sandbox helper, never from raw owner/repo strings
- Replace '/' with '_' in the repo component before composing the key
- Treat workspace_key as opaque — store and echo it, don't reconstruct it
- Add a client-side regex check mirroring the server's shape before each push
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
- Destination paths cannot contain parent traversal or NUL byt
- Shared-folder destination escapes its configured root
- Absolute paths are not allowed in ${scheme}:// URLs: ${rawPa
- Path traversal (..) is not allowed in ${scheme}:// URLs: ${r
- Unsafe #{scheme}:// path (absolute or traversal): #{path}
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/ecdfd43257ac4bb6.
Report an issue: GitHub.