JuliusBrussee/caveman · error
must be an absolute http(s) URL without credentials
Error message
${name} must be an absolute http(s) URL without credentials What it means
This is the second-layer URL check: the value parses as a URL but is rejected because it is not http/https, has no hostname, or embeds user/password credentials. The SDK refuses URLs with inline credentials both for security (leaking creds into requests/logs) and correctness of gateway routing.
Solutions
- Remove credentials from the URL and pass them separately (the SDK takes apiKey as its own option).
- Use an http: or https: scheme; switch ftp:// or custom schemes to https://.
- Ensure the URL includes a hostname (e.g. https://api.example.com, not https:///path).
Example fix
// before
new Cave({ apiKey, baseURL: "https://user:secret@api.example.com", agent: "x" });
// after
new Cave({ apiKey, baseURL: "https://api.example.com", agent: "x" }); Defensive patterns
Strategy: validation
Validate before calling
function assertCleanHttpURL(v: string, name: string): URL {
const u = new URL(v);
if ((u.protocol !== "http:" && u.protocol !== "https:") || !u.hostname || u.username || u.password)
throw new Error(`${name} must be an http(s) URL without credentials`);
return u;
} Type guard
const isCredentialFreeHttpURL = (v: string): boolean => {
try { const u = new URL(v); return (u.protocol === "http:" || u.protocol === "https:") && !!u.hostname && !u.username && !u.password; } catch { return false; }
}; Try / catch
try {
const cave = new Cave({ apiKey, baseURL, agent });
} catch (e) {
if (e instanceof Error && e.message.includes("without credentials")) {
const u = new URL(baseURL);
baseURL = u.origin + u.pathname;
// retry construction
} else throw e;
} Prevention
- Never embed user:password in URLs; pass secrets via the SDK's apiKey option.
- Audit config from curl examples or legacy SDKs that accepted auth-in-URL.
- Scan config files for the '://' credential pattern (user@ in host) in CI.
When it happens
Trigger: baseURL: "ftp://example.com" or "https://user:pass@api.example.com" or "https:///path" (empty hostname) passed to Cave options via normalized().
Common situations: Copy-pasting a URL with basic-auth credentials (common from curl examples or other SDKs that accept auth-in-URL); using a non-http scheme; misassembled URL where host part was lost.
Understand the failure class
Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.
Related errors
- : every source must be HTTPS
- must be an absolute http(s) URL without credentials
- awssig: incomplete AWS credentials
- backslash is not allowed in path
- cave_redirect_not_allowed
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/c5c4d7fac021ea66.
Report an issue: GitHub.
Appendix: source
Thrown at packages/sdk/typescript/src/index.ts:344
spanId: string;
parentSpanId: string;
kind: number;
startTimeNs: number;
endTimeNs: number;
statusCode: number; // 0=unset, 1=ok, 2=error
attributes: Record<string, string | number | boolean>;
};
function normalizedServiceURL(value: string, name: "baseURL" | "controlURL"): string {
if (value.trim() !== value) throw new Error(`${name} must not contain surrounding whitespace`);
let parsed: URL;
try {
parsed = new URL(value);
} catch {
throw new Error(`${name} must be an absolute http(s) URL`);
}
if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") || !parsed.hostname || parsed.username || parsed.password) {
throw new Error(`${name} must be an absolute http(s) URL without credentials`);
}
if (parsed.search || parsed.hash) throw new Error(`${name} must not contain a query or fragment`);
return value.replace(/\/+$/, "");
}
export class Cave {
readonly options: CaveOptions;
constructor(options: CaveOptions) {
if (!options.apiKey || !options.baseURL || !options.agent) throw new Error("apiKey, baseURL, and agent are required");
if (options.timeoutMs !== undefined && (!Number.isSafeInteger(options.timeoutMs) || options.timeoutMs <= 0)) {
throw new Error("timeoutMs must be a positive integer");
}
// CAVE_WORKFLOW lets a wrapper (`cave wrap --workflow x`) label every request
// from an SDK app without a code change. An explicit option always wins.
// globalThis lookup keeps the SDK browser-neutral (no node type dependency).
// The env value is normalized to the gateway's label rule (lowercase
// [a-z0-9_-], max 96) — an invalid ambient value is ignored rather thanView on GitHub (pinned to 3ee70a1026)