santifer/career-ops · error · Error
H1B_API_BASE must not embed credentials.
Error message
H1B_API_BASE must not embed credentials.
What it means
resolveBase() rejects an H1B_API_BASE that embeds userinfo credentials (user:password@ in the URL). Undici refuses credentialed Requests anyway, and the URL is echoed to stdout via the source field, so credentials would be printed. The library throws a named error instead of leaking a secret.
Source
Thrown at plugins/h1b-sponsor/lib/api.mjs:55
// (an unset shell variable, an empty .env line, a CI secret that did not
// populate), and silently falling back would send someone's shortlist and
// their token to a host they believed they had replaced.
if (raw === undefined) return DEFAULT_BASE;
const trimmed = String(raw).trim();
if (!trimmed) {
throw new Error('H1B_API_BASE is set but empty. Unset it to use the default endpoint.');
}
let parsed;
try {
parsed = new URL(trimmed);
} catch {
throw new Error(`H1B_API_BASE is not a valid URL: ${trimmed}`);
}
if (parsed.username || parsed.password) {
// Undici refuses a credentialed Request anyway, and the value reaches
// stdout through the source field, so this would print a password.
throw new Error('H1B_API_BASE must not embed credentials.');
}
if (parsed.search || parsed.hash) {
// Paths are appended, so a query or fragment swallows them: the request
// would go to the base itself and answer about a company never asked for.
throw new Error('H1B_API_BASE must not contain a query string or a fragment.');
}
// Plain http would put an Authorization header on the wire in the clear.
// Loopback is exempt so a self-hoster can develop against a local worker,
// but only for http. Exempting every scheme on a loopback host let
// ftp://localhost and ws://localhost past validation, and those die later
// inside fetch as a bare "fetch failed", which is the opaque failure this
// check exists to replace with a named configuration error.
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\]|::1)$/i.test(parsed.hostname);
const allowedScheme = parsed.protocol === 'https:' || (parsed.protocol === 'http:' && loopback);
if (!allowedScheme) {
throw new Error(`H1B_API_BASE must use https, or http on loopback: ${trimmed}`);
}
// Appended as `${base}/employers/...`, so a trailing slash would double up.View on GitHub (pinned to 1696bec4d0)
Solutions
- Remove the user:password@ portion from H1B_API_BASE and keep only scheme+host+path.
- Pass credentials out-of-band if the backend requires them — e.g. an Authorization header or a token env var consumed by your proxy — not in the URL.
- If the API is behind basic auth, front it with a local reverse proxy that injects the header, and point H1B_API_BASE at the proxy.
Example fix
// before (.env) H1B_API_BASE=https://admin:s3cret@h1b.example.com // after H1B_API_BASE=https://h1b.example.com
Defensive patterns
Strategy: validation
Validate before calling
function hasEmbeddedCredentials(v) {
try { const u = new URL(v); return Boolean(u.username || u.password); }
catch { return false; }
}
// assert before running: if (hasEmbeddedCredentials(process.env.H1B_API_BASE)) throw ... Type guard
function isCredentialFreeUrl(v) {
try { const u = new URL(v); return !u.username && !u.password; } catch { return false; }
} Try / catch
try {
await lookupEmployer(name);
} catch (e) {
if (e.message === 'H1B_API_BASE must not embed credentials.') {
console.error('Remove user:pass@ from H1B_API_BASE; pass secrets via headers/proxy instead.');
return;
}
throw e;
} Prevention
- Never put tokens in URLs — cURL -u syntax does not translate to a base-URL config.
- Grep .env for '@' inside H1B_API_BASE as part of pre-commit checks.
- Log the redacted origin+path of the base at startup so credentials would be visible immediately.
- If the upstream needs auth, terminate it at a local proxy that injects headers.
When it happens
Trigger: Setting H1B_API_BASE to something like https://user:token@h1b.example.com and calling any plugin API function that builds a request from apiBase().
Common situations: Putting an API token into the URL as basic-auth credentials because a cURL example used -u or user:pass@host syntax; copying an authenticated URL from a browser or proxy tool.
Related errors
- flowxtra: untrusted hostname "${parsed.hostname}" — must be
- yourator: URL must use HTTPS: ${url}
- refusing to archive restricted destination: ${preGuard.reaso
- Invalid or blocked URL: ${rejected.reason}
- Access denied: Localhost or internal domain target detected.
AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01).
Data as JSON: /api/errors/b042478848b2da55.
Report an issue: GitHub.