{"record":{"id":"3f3418b61681eafd","repo":"can1357/oh-my-pi","slug":"remote-url-must-not-contain-embedded-credentials","errorCode":null,"errorMessage":"remote url must not contain embedded credentials","messagePattern":"remote url must not contain embedded credentials","errorType":"http","errorClass":"HTTPException","httpStatus":400,"severity":"error","filePath":"python/robomp/src/proxy/server.py","lineNumber":320,"sourceCode":"def _read_single_remote_url(repo_dir: Path, expected_repo: str, *, push: bool, slot_uid: int | None = None) -> str:\n    urls = list(dict.fromkeys(_read_remote_urls(repo_dir, slot_uid=slot_uid, push=push)))\n    if len(urls) != 1:\n        kind = \"push\" if push else \"fetch\"\n        log.warning(\n            \"gh-proxy: refusing git op — origin has ambiguous remote urls\",\n            extra={\"expected_repo\": expected_repo, \"kind\": kind, \"count\": len(urls)},\n        )\n        raise HTTPException(400, f\"origin must have exactly one {kind} url\")\n    return urls[0]\n\n\ndef _normalized_github_https_url(url: str, expected_repo: str) -> str:\n    _validate_repo_name(expected_repo)\n    parsed = urlparse(url)\n    if (parsed.scheme or \"\").lower() != \"https\":\n        raise HTTPException(400, f\"remote url must be https://github.com/{expected_repo}[.git]\")\n    if parsed.username or parsed.password:\n        raise HTTPException(400, \"remote url must not contain embedded credentials\")\n    try:\n        port = parsed.port\n    except ValueError as exc:\n        raise HTTPException(400, \"remote url has invalid port\") from exc\n    if port is not None:\n        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","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/python/robomp/src/proxy/server.py#L302-L338","documentation":"Raised when the origin HTTPS URL embeds credentials (username or password in the URL, e.g. https://user:token@github.com/...). The proxy forbids this because it injects its own token; embedded credentials would leak secrets into git's stored remote config and conflict with proxy-managed auth.","triggerScenarios":"Any proxied git op where urlparse(url) shows a username or password component in the origin URL — typically after someone baked a PAT into the remote during setup, or a credential-carrying URL was pasted from CI tooling.","commonSituations":"CI-generated remotes like https://x-access-token:ghp_...@github.com/... left in the worktree; copy-pasted URLs including basic-auth; secret managers rewriting URLs with tokens.","solutions":["Strip credentials: `git remote set-url origin https://github.com/<owner>/<repo>.git`","Check for insteadOf rewrites (`git config --get-regexp url\\..*\\.insteadof`) that inject credentials, and remove them","Rotate any token that was stored in the remote URL — it is persisted in .git/config in cleartext","Let the proxy supply auth (it injects its own token) rather than embedding credentials"],"exampleFix":"// before\n$ git remote set-url origin https://x-access-token:ghp_SECRET@github.com/acme/repo.git\n// after\n$ git remote set-url origin https://github.com/acme/repo.git","handlingStrategy":"validation","validationCode":"from urllib.parse import urlparse\n\ndef origin_has_no_embedded_credentials(url: str) -> bool:\n    p = urlparse(url)\n    return not (p.username or p.password)","typeGuard":null,"tryCatchPattern":"from fastapi import HTTPException\n\ntry:\n    result = client.post(\"/git/op\", json={\"repo\": \"acme/repo\"})\nexcept HTTPException as e:\n    if e.status_code == 400 and \"embedded credentials\" in e.detail:\n        rotate_token_from_url(worktree_dir)  # strip creds AND rotate the leaked token\n    else:\n        raise","preventionTips":["Never paste credential-bearing URLs as remotes; let the proxy inject auth","Scan .git/config for 'https://user:' patterns during worktree provisioning","Rotate any token found in a remote URL immediately — it persists in cleartext","Audit CI templates that write x-access-token URLs into remotes"],"tags":["security","credentials","git","remote-url"],"backgroundTag":"credentials-in-url","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}