santifer/career-ops · error · Error

rippling: untrusted hostname

Error message

rippling: untrusted hostname "${parsed.hostname}" — must be ${API_HOST}

What it means

The rippling provider pins requests to a single trusted API host (API_HOST). assertRipplingApiUrl compares the parsed hostname against API_HOST and throws this error on mismatch, preventing SSRF and accidental calls to lookalike or mirror domains.

Solutions

  1. Use the exact API host expected (compare your URL's hostname to the API_HOST constant in providers/rippling.mjs).
  2. Fix typos or subdomain mistakes in the entry config.
  3. Route through proxies at the network layer, not by rewriting the URL hostname.

Example fix

// before
url = 'https://rippling.com/api/ats/jobs';
// after
url = `https://${API_HOST}/api/ats/jobs`; // API_HOST e.g. api.rippling.com
Defensive patterns

Strategy: validation

Validate before calling

const expectedHost = 'api.rippling.com'; // mirror of API_HOST
const hostOk = (u) => { try { return new URL(u).hostname === expectedHost; } catch { return false; } };
if (!hostOk(entry.url)) throw new Error(`skip: ${entry.url} is not the Rippling API host`);

Type guard

const isRipplingApi = (u) => { try { return new URL(u).hostname === 'api.rippling.com'; } catch { return false; } };

Try / catch

try { await provider.fetch(entry, ctx); } catch (e) { if (e.message.includes('untrusted hostname')) { console.error(`Entry ${entry.name} URL host mismatch`); return null; } throw e; }

Prevention

When it happens

Trigger: A rippling API URL whose parsed hostname !== API_HOST — e.g. https://rippling.com/... instead of the API subdomain, a typo'd host, or an attacker/config-supplied foreign hostname.

Common situations: Using the marketing domain instead of the API host, DNS/search-replaced URLs, proxies embedded in the URL, or config values copied from another provider.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/668c6254e64d14c7. Report an issue: GitHub.

Appendix: source

Thrown at providers/rippling.mjs:58

  return segment;
}

/** Build the board API URL for a validated slug. */
function apiUrlForSlug(slug) {
  return `${API_BASE}/${encodeURIComponent(slug)}/jobs`;
}

/** @param {string} url */
function assertRipplingApiUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`rippling: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`rippling: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== API_HOST) {
    throw new Error(`rippling: untrusted hostname "${parsed.hostname}" — must be ${API_HOST}`);
  }
  return url;
}

/** @type {Provider} */
export default {
  id: 'rippling',

  detect(entry) {
    const slug = resolveSlug(entry);
    return slug ? { url: apiUrlForSlug(slug) } : null;
  },

  async fetch(entry, ctx) {
    const slug = resolveSlug(entry);
    if (!slug) throw new Error(`rippling: cannot derive API URL for ${entry.name}`);
    const apiUrl = apiUrlForSlug(slug);
    assertRipplingApiUrl(apiUrl);

View on GitHub (pinned to aac998c7ed)