{"record":{"id":"4ad0cb454476c049","repo":"garrytan/gstack","slug":"proxy-creds-set-in-both-env-browse-proxy-user-an","errorCode":null,"errorMessage":"proxy creds set in both env (BROWSE_PROXY_USER) and URL — pick one source","messagePattern":"proxy creds set in both env \\(BROWSE_PROXY_USER\\) and URL — pick one source","errorType":"validation","errorClass":"ProxyConfigError","httpStatus":null,"severity":"error","filePath":"browse/src/proxy-config.ts","lineNumber":91,"sourceCode":"  if (!Number.isInteger(port) || port <= 0 || port > 65535) {\n    throw new ProxyConfigError(\n      'expected scheme://[user:pass@]host:port',\n      `invalid proxy URL — bad port`,\n    );\n  }\n\n  const urlHasUser = !!url.username;\n  const urlHasPass = !!url.password;\n  const envHasUser = !!opts.envUser;\n  const envHasPass = !!opts.envPass;\n  const urlHasCreds = urlHasUser || urlHasPass;\n  const envHasCreds = envHasUser || envHasPass;\n\n  // D9 (codex correction): refuse on mixed sources. Silent override is a\n  // debugging trap — when a stale BROWSE_PROXY_USER from a prior session\n  // wins over a fresh --proxy URL, the user can't tell why.\n  if (urlHasCreds && envHasCreds) {\n    throw new ProxyConfigError(\n      'unset BROWSE_PROXY_USER/PASS or remove user:pass@ from --proxy',\n      `proxy creds set in both env (BROWSE_PROXY_USER) and URL — pick one source`,\n    );\n  }\n\n  let userId: string | undefined;\n  let password: string | undefined;\n  if (urlHasCreds) {\n    userId = decodeURIComponent(url.username);\n    password = url.password ? decodeURIComponent(url.password) : undefined;\n  } else if (envHasCreds) {\n    userId = opts.envUser;\n    password = opts.envPass;\n  }\n\n  return {\n    scheme: scheme as 'socks5' | 'http' | 'https',\n    host: url.hostname,","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/garrytan/gstack/blob/94993f74012782fd94416dd44b8314f6363a13a4/browse/src/proxy-config.ts#L73-L109","documentation":"Thrown by parseProxyConfig when credentials appear in BOTH the URL (user:pass@host) AND the environment (BROWSE_PROXY_USER / BROWSE_PROXY_PASS). This is the D9 'codex correction': the parser refuses to guess which source wins, because a silent override is a debugging trap — a stale env var from a prior session could win over a fresh --proxy URL and the user would have no idea why auth failed.","triggerScenarios":"Setting --proxy user:pass@host AND exporting BROWSE_PROXY_USER (or BROWSE_PROXY_PASS) in the same shell, then running the CLI. Both urlHasCreds and envHasCreds are true, so the parser throws.","commonSituations":"Stale env vars from a prior session combined with a fresh --proxy flag; CI that sets BROWSE_PROXY_USER globally and also embeds creds in the URL per-job; wrapper script that injects creds from both sources; switching auth strategies without clearing the old one.","solutions":["Unset the env vars: `unset BROWSE_PROXY_USER BROWSE_PROXY_PASS`","OR remove user:pass@ from the --proxy URL and rely on env only","Pick one credential source and document it in your shell rc / CI config","Audit wrappers and dotfiles for stray BROWSE_PROXY_* exports"],"exampleFix":"# before: creds in both places\nexport BROWSE_PROXY_USER=alice\nbrowse --proxy bob:pw@host:1080 ...  # throws\n\n# after: pick one source\nunset BROWSE_PROXY_USER\nbrowse --proxy bob:pw@host:1080 ...","handlingStrategy":"validation","validationCode":"function credsNotInBothPlaces(proxyUrl: string, envUser?: string, envPass?: string): boolean {\n  let urlHasCreds = false;\n  try {\n    const u = new URL(proxyUrl);\n    urlHasCreds = !!u.username || !!u.password;\n  } catch { /* invalid URL handled elsewhere */ }\n  const envHasCreds = !!envUser || !!envPass;\n  return !(urlHasCreds && envHasCreds);\n}\n\nif (!credsNotInBothPlaces(proxyUrl, envUser, envPass)) {\n  throw new Error('Set proxy creds in EITHER the URL OR the env, not both');\n}","typeGuard":"function hasUrlCreds(s: string): boolean {\n  try { const u = new URL(s); return !!u.username || !!u.password; } catch { return false; }\n}","tryCatchPattern":"try {\n  parseProxyConfig(opts);\n} catch (e) {\n  if (e instanceof ProxyConfigError && /both env/.test(e.message)) {\n    console.error(`${e.message}. ${e.hint}`);\n    // unset env and retry with URL creds\n    delete process.env.BROWSE_PROXY_USER;\n    delete process.env.BROWSE_PROXY_PASS;\n    parseProxyConfig({ ...opts, envUser: undefined, envPass: undefined });\n  } else throw e;\n}","preventionTips":["Pick ONE credential source (URL or env) and document it in your setup","Audit shell rc files and CI secrets for stray BROWSE_PROXY_USER/PASS exports","When switching auth strategies, unset the old source explicitly","Validate for dual-source conflict in your config loader before calling parseProxyConfig"],"tags":["proxy","config","credentials","env-conflict","security","proxy-config-error"],"backgroundTag":null,"analyzedSha":"94993f74012782fd94416dd44b8314f6363a13a4","analyzedAt":"2026-08-12T04:06:23.140Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}