santifer/career-ops · error · Error
plugin egress must use HTTPS: ${u.href}
Error message
plugin egress must use HTTPS: ${u.href} What it means
Thrown by the guardedFetch host check in plugins/_engine.mjs when a plugin egress URL's protocol is not https:, unless it is an explicitly opted-in loopback HTTP host (allowsLocalhost + http: + a loopback hostname, to support local-AI providers like Ollama/LM Studio on http://localhost:11434). This enforces TLS for all plugin network egress by default.
Source
Thrown at plugins/_engine.mjs:374
* Posture note: core providers use redirect:'error' (reject ANY redirect). This
* is the deliberately looser plugin posture — allowlist-pinned FOLLOW — because
* keyed APIs (Notion/Google/Apify) legitimately 30x within their own host set;
* the allowlist + per-hop re-validation + cross-host credential strip bound it.
*
* ADVISORY only: this binds a plugin that routes through ctx.fetch*, not one
* that calls global fetch directly (see the trust note in README.md).
*
* @param {string[]} allowedHosts
*/
function makeGuardedFetch(allowedHosts, { allowsLocalhost = false } = {}) {
const allow = new Set(allowedHosts);
const isLoopbackHost = (h) => /^(localhost|127\.\d+\.\d+\.\d+|\[?::1\]?)$/i.test(h);
const hostOk = (u) => {
if (u.protocol !== 'https:') {
// Plain HTTP is allowed ONLY for an opted-in loopback host (local-AI
// providers like Ollama/LM Studio serve http://localhost:11434).
if (!(allowsLocalhost && u.protocol === 'http:' && isLoopbackHost(u.hostname))) {
throw new Error(`plugin egress must use HTTPS: ${u.href}`);
}
}
if (allow.size > 0 && !allow.has(u.hostname)) throw new Error(`plugin egress to "${u.hostname}" is not in allowedHosts [${[...allow].join(', ')}]`);
};
return async function guardedFetch(url, opts = {}) {
const { timeoutMs = 10_000, headers = {}, method = 'GET', body = null } = opts;
let current = new URL(url);
hostOk(current);
// SSRF: reject a host that resolves to a private/loopback/metadata address
// (re-checked on every redirect hop). Loopback allowed only when opted in.
await resolveAndValidate(current.hostname, { allowsLocalhost });
let reqHeaders = { ...headers };
for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
let res;
try {
res = await fetch(current.href, {View on GitHub (pinned to 9b17a8ac97)
Solutions
- Change the plugin's request URL to its https:// equivalent.
- For a legitimate local-AI provider, declare allowsLocalhost in the plugin's network permission so http:// to loopback is permitted.
- Ensure no redirect downgrades https to http (the guard re-checks each hop).
- Review the plugin manifest's allowedHosts/network config.
Example fix
// before
await ctx.fetch('http://api.example.com/data');
// after
await ctx.fetch('https://api.example.com/data'); Defensive patterns
Strategy: validation
Validate before calling
function isHttpsOrAllowedLoopback(s, { allowsLocalhost } = {}) {
const u = new URL(s);
if (u.protocol === 'https:') return true;
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[?::1\]?)$/i.test(u.hostname);
return allowsLocalhost && u.protocol === 'http:' && loopback;
} Type guard
/** Narrows a URL string to one the plugin egress guard will accept. */
function isAllowedEgressUrl(s, { allowsLocalhost } = {}) {
if (typeof s !== 'string') return false;
let u;
try { u = new URL(s); } catch { return false; }
if (u.protocol === 'https:') return true;
const loopback = /^(localhost|127\.\d+\.\d+\.\d+|\[?::1\]?)$/i.test(u.hostname);
return !!(allowsLocalhost && u.protocol === 'http:' && loopback);
} Prevention
- Default all plugin fetches to https://.
- Only opt in allowsLocalhost for genuine local-AI providers (Ollama/LM Studio).
- Audit redirect targets — the guard re-checks each hop and will throw on a downgrade.
When it happens
Trigger: A plugin calls ctx.fetch('http://api.example.com/...') without TLS; a redirect lands on an http:// target; a plugin targets a local-AI server but allowsLocalhost was not enabled for it.
Common situations: Plugin author used http:// during dev and forgot to switch to https://; a third-party endpoint redirects from https to http; local Ollama/LM Studio integration without opting in allowsLocalhost in the manifest/config.
Related errors
- plugin egress to "${u.hostname}" is not in allowedHosts [${[
- plugin egress to ${hostname} is blocked (private/loopback/me
- plugin egress: ${hostname} resolves to a blocked address (${
- Access denied: Egress guard blocked private target IP ${ip}
- Refusing non-HTTP(S) URL: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/0b0b64534a6941a4.
Report an issue: GitHub.