{"record":{"id":"b3399b9819049b7a","repo":"can1357/oh-my-pi","slug":"remote-url-contains-forbidden-control-bytes","errorCode":null,"errorMessage":"remote url contains forbidden control bytes","messagePattern":"remote url contains forbidden control bytes","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":344,"sourceCode":"        raise HTTPException(400, \"remote url must not specify a port\")\n    if (parsed.hostname or \"\").lower() != \"github.com\":\n        raise HTTPException(400, f\"remote url host must be github.com for repo {expected_repo!r}\")\n    if parsed.params or parsed.query or parsed.fragment:\n        raise HTTPException(400, \"remote url must not contain params, query, or fragment\")\n    path = parsed.path.strip(\"/\")\n    if path.endswith(\".git\"):\n        path = path[:-4]\n    if path.lower() != expected_repo.lower():\n        raise HTTPException(400, f\"remote url does not match repo {expected_repo!r}\")\n    return _github_url_for_repo(expected_repo)\n\n\ndef _remote_auth_for_url(url: str, expected_repo: str, token: str) -> _RemoteAuth:\n    raw = url.strip()\n    if not raw or raw != url:\n        raise HTTPException(400, \"remote url must not be empty or padded\")\n    if _FORBIDDEN_URL_BYTES_RE.search(raw):\n        raise HTTPException(400, \"remote url contains forbidden control bytes\")\n    if raw.startswith(\"-\"):\n        raise HTTPException(400, \"remote url must not start with '-'\")\n    if _REMOTE_HELPER_RE.match(raw):\n        raise HTTPException(400, \"git remote helper transports are disabled\")\n    scheme = (urlparse(raw).scheme or \"\").lower()\n    if scheme in (\"http\", \"https\"):\n        normalized = _normalized_github_https_url(raw, expected_repo)\n        return _RemoteAuth(url=normalized, token=token, auth_url=normalized)\n    return _RemoteAuth(url=raw, token=None, auth_url=None)\n\n\ndef _clone_remote_auth(clone_url: str, expected_repo: str, token: str) -> _RemoteAuth:\n    try:\n        return _remote_auth_for_url(clone_url, expected_repo, token)\n    except HTTPException:\n        log.warning(\n            \"gh-proxy: refusing clone — clone_url is not permitted\",\n            extra={\"expected_repo\": expected_repo},","sourceCodeStart":326,"sourceCodeEnd":362,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L326-L362","documentation":"The proxy rejects remote URLs containing control bytes (\\x00-\\x1f, \\x7f) or their percent-encoded forms (%00, %0a, %0d) with HTTP 400. Control characters in URLs enable argument injection and header/response splitting when passed to git or HTTP layers, so any URL matched by _FORBIDDEN_URL_BYTES_RE is refused before use.","triggerScenarios":"Supplying a URL containing raw newlines, carriage returns, tabs, NUL bytes, or percent-encoded %0a/%0d/%00 sequences to an endpoint that resolves to _clone_remote_auth or _origin_remote_auth.","commonSituations":"Multi-line YAML strings folding newlines into a URL value; log-file scraped values with embedded \\r\\n; crafted URLs from untrusted input testing CRLF injection; copying URLs from terminals that wrapped lines.","solutions":["Remove all control characters from the URL; keep it on a single printable line","If a value may contain them, sanitize: url = re.sub(r'[\\x00-\\x1f\\x7f]', '', url) and re-encode intentionally","Quote YAML scalar values and use single-line strings for URLs"],"exampleFix":"// before\nconst url = `https://github.com/o/r.git\\n`;\n// after\nconst url = \"https://github.com/o/r.git\".replace(/[\\x00-\\x1f\\x7f]/g, \"\");","handlingStrategy":"validation","validationCode":"if /[\\x00-\\x1f\\x7f]|%(?:00|0a|0d)/i.test(url) throw new Error(\"control bytes in remote url\");","typeGuard":"function hasNoControlBytes(u: unknown): u is string {\n  return typeof u === \"string\" && !/[\\x00-\\x1f\\x7f]/.test(u) && !/%(?:00|0a|0d)/i.test(u);\n}","tryCatchPattern":null,"preventionTips":["Keep URL values on a single line in config files; quote YAML scalars","Sanitize any value scraped from logs or terminals before use","Never build URLs by concatenating untrusted raw input"],"tags":["http-400","security","input-validation","crlf-injection"],"backgroundTag":"url-validation-rejected","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}