koala73/worldmonitor · error
UNTRUSTED_SOURCE_HOST
Error message
UNTRUSTED_SOURCE_HOST
What it means
fetchApprovedImdJson() validates every URL against isAllowedImdHost() before fetching: the URL must be https, exactly the allowed IMD hostname, on port 443 (or default), with no embedded credentials. Any URL failing this SSRF guard throws UNTRUSTED_SOURCE_HOST without making a network request. It is an intentional security control, not a network failure.
Solutions
- Use imdProductUrl(product) to build the URL so it always targets the approved IMD host over https.
- If you must fetch from another host, host your data or copy it to the approved host; do not bypass the check.
- Verify the URL parses as https with the exact allowed hostname, port 443/default, and no credentials: run isAllowedImdHost(url) in a REPL.
- Fix typos or stray query components/credentials in the configured URL.
Example fix
// before
await fetchApprovedImdJson('http://maaalaimaatham.gov.in/data.json');
// after
import { imdProductUrl } from './imd-cyclone-marine.mjs';
await fetchApprovedImdJson(imdProductUrl(product)); Defensive patterns
Strategy: validation
Validate before calling
import { isAllowedImdHost, imdProductUrl } from './imd-cyclone-marine.mjs';
const url = imdProductUrl(product);
if (!isAllowedImdHost(url)) {
throw new Error(`Refusing to fetch non-approved IMD URL: ${url}`);
}
await fetchApprovedImdJson(url); Type guard
function isSafeImdUrl(url) {
try {
const u = new URL(String(url));
return u.protocol === 'https:'
&& u.hostname.toLowerCase() === 'maaalaimaatham' // exact allowed IMD host
&& (u.port === '' || u.port === '443')
&& u.username === '' && u.password === '';
} catch {
return false;
}
} Try / catch
try {
const data = await fetchApprovedImdJson(url);
} catch (err) {
if (err.message === 'UNTRUSTED_SOURCE_HOST') {
console.error(`Blocked non-approved IMD host: ${url}`);
return fallbackSnapshot;
}
throw err;
} Prevention
- Always build URLs with imdProductUrl(product) instead of string concatenation.
- Never construct IMD URLs from untrusted/user-supplied input.
- Run isAllowedImdHost() in unit tests for every URL constant you introduce.
- Avoid http:// and credential-embedded URLs even in local experiments — they will always be rejected.
When it happens
Trigger: Calling fetchApprovedImdJson('http://maaalaimaatham/path') (non-https), a mirror/lookalike hostname, a URL with a custom port, a URL with user:pass@ credentials, or a non-URL string.
Common situations: Hard-coding an http:// URL during local testing; pointing the fetcher at a staging/mock host or a local proxy URL; data-driven URLs built from user or upstream input that are not on the approved host; typos in the hostname.
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
- serverUrl hostname is blocked
- callbackUrl is not a valid URL
- callbackUrl must use https
- callbackUrl hostname is a blocked metadata endpoint
- callbackUrl resolves to a private/reserved address: ${hostna
AI-assisted analysis of koala73/worldmonitor@7d06c8633d (2026-09-15).
Data as JSON: /api/errors/7f22f6df96d62fbc.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/lib/imd-cyclone-marine.mjs:755
const { done, value } = await reader.read();
if (done) break;
size += value.byteLength;
if (size > maxBytes) throw new Error(`IMD_RESPONSE_TOO_LARGE:${size}`);
chunks.push(value);
}
return JSON.parse(new TextDecoder().decode(Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)))));
}
export async function fetchApprovedImdJson(url, {
fetchFn = globalThis.fetch,
userAgent = CHROME_UA,
maxBytes = IMD_MAX_BYTES,
timeoutMs = IMD_TIMEOUT_MS,
apiKey = null,
apiKeyHeader = 'X-API-Key',
apiToken = null,
} = {}) {
if (!isAllowedImdHost(url)) throw new Error('UNTRUSTED_SOURCE_HOST');
const headers = { Accept: 'application/json', 'User-Agent': userAgent };
if (apiKey) headers[apiKeyHeader] = apiKey;
if (apiToken) headers.Authorization = `Bearer ${apiToken}`;
const response = await fetchFn(url, {
headers,
redirect: 'error',
signal: AbortSignal.timeout(timeoutMs),
});
if (!response.ok) {
const err = new Error(`HTTP ${response.status}`);
err.httpStatus = response.status;
throw err;
}
return readBoundedJsonResponse(response, maxBytes);
}
export function createImdProxyFetch(rawProxyUrl, { proxyFetchFn = proxyFetch } = {}) {
const proxyUrl = String(rawProxyUrl || '').trim();View on GitHub (pinned to 7d06c8633d)