santifer/career-ops · error · Error
H1B_API_BASE must use https, or http on loopback: ${trimmed}
Error message
H1B_API_BASE must use https, or http on loopback: ${trimmed} What it means
resolveBase() enforces a transport-safety policy: H1B_API_BASE must be https, except plain http is allowed on loopback hosts (localhost, 127.x, ::1) so a self-hoster can develop against a local worker. Exempting every scheme on loopback previously let ftp://localhost and ws://localhost through, which later died as an opaque 'fetch failed'.
Source
Thrown at plugins/h1b-sponsor/lib/api.mjs:71
// 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.
return trimmed.replace(/\/+$/, '');
}
const USER_AGENT = 'career-ops-plugin-h1b-sponsor/1.0';
const DEFAULT_TIMEOUT_MS = 10_000;
const MAX_RETRY_WAIT_MS = 10_000;
const MAX_REDIRECTS = 3;
// The largest real employer profile this API serves is about 13 KB and a search
// page is about 4 KB, so a megabyte is ~80x headroom for future growth while
// still refusing to buffer a runaway or hostile body.
const MAX_READ_BYTES = 1024 * 1024;
function buildHeaders(token) {
const h = { 'Accept': 'application/json', 'User-Agent': USER_AGENT };
if (typeof token === 'string' && token.trim()) {
h['Authorization'] = `Bearer ${token.trim()}`;
}View on GitHub (pinned to 1696bec4d0)
Solutions
- Serve the API over https and set H1B_API_BASE to the https:// URL.
- For local development, use http://localhost:PORT or http://127.0.0.1:PORT — loopback http is explicitly allowed.
- Put TLS in front of a plain-http internal server (reverse proxy with a certificate) instead of relaxing the scheme.
- Do not use ws://, ftp://, or other schemes — fetch only speaks http(s).
Example fix
// before (.env) H1B_API_BASE=http://api.staging.example.com // after H1B_API_BASE=https://api.staging.example.com // or, for local dev: // H1B_API_BASE=http://localhost:8787
Defensive patterns
Strategy: validation
Validate before calling
const LOOPBACK = /^(localhost|127\.\d+\.\d+\.\d+|\[::1\]|::1)$/i;
function isAllowedScheme(v) {
try {
const u = new URL(v);
return u.protocol === 'https:' || (u.protocol === 'http:' && LOOPBACK.test(u.hostname));
} catch { return false; }
}
// if (!isAllowedScheme(process.env.H1B_API_BASE)) fix before running; Type guard
function isSecureOrLoopback(v) {
try {
const u = new URL(v);
return u.protocol === 'https:' ||
(u.protocol === 'http:' && /^(localhost|127\.|\[::1\]|::1)/i.test(u.hostname));
} catch { return false; }
} Try / catch
try {
await lookupEmployer(name);
} catch (e) {
if (e.message.startsWith('H1B_API_BASE must use https')) {
console.error('Use https:// in production; plain http only for localhost/127.x dev.');
return;
}
throw e;
} Prevention
- Default to https everywhere; only drop to http for loopback development.
- Keep separate .env.development (http://localhost:PORT) and .env.production (https://host) files.
- Never use non-http(s) schemes (ws:, ftp:) — fetch cannot speak them.
- Put TLS on internal servers via a reverse proxy instead of downgrading the scheme.
When it happens
Trigger: Setting H1B_API_BASE to an http:// URL with a non-loopback hostname (e.g. http://api.example.com), or to a non-http(s) scheme entirely (ftp:, ws:), then calling any plugin API function.
Common situations: Pointing at an internal staging server that lacks TLS; accidentally leaving 'http://' from a local dev config when switching to production; using ws:// or ftp:// because the docs snippet was copied from another service.
Related errors
- H1B_API_BASE is not a valid URL: ${trimmed}
- H1B_API_BASE must not embed credentials.
- H1B_API_BASE must not contain a query string or a fragment.
- bamboohr: untrusted hostname "${parsed.hostname}" — must mat
- bamboohr: cannot derive API URL for ${entry.name}
AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01).
Data as JSON: /api/errors/0d5883c88037c9bd.
Report an issue: GitHub.