{"record":{"id":"ccd9c4f71ee22acb","repo":"immich-app/immich","slug":"hostname-did-not-match-any-listed-in-methods-all","errorCode":null,"errorMessage":"Hostname did not match any listed in methods[].allowedHosts in the plugin manifest","messagePattern":"Hostname did not match any listed in methods\\[\\]\\.allowedHosts in the plugin manifest","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/src/services/workflow-execution.service.ts","lineNumber":107,"sourceCode":"      ]\n    >(async (authDto, context, args) => {\n      const hostname = new URL(args[0]).hostname;\n\n      for (const pattern of context.allowedHosts) {\n        const regex = new RegExp(pattern.replaceAll('.', String.raw`\\.`).replaceAll('*', '.*'));\n        if (regex.test(hostname)) {\n          // eslint-disable-next-line unicorn/no-invalid-argument-count\n          const res = await fetch(...args);\n\n          return {\n            ok: res.ok,\n            status: res.status,\n            body: await res.text(),\n          };\n        }\n      }\n\n      throw new Error('Hostname did not match any listed in methods[].allowedHosts in the plugin manifest');\n    });\n\n    const functions = {\n      searchAlbums,\n      createAlbum,\n      addAssetsToAlbum,\n      addAssetsToAlbums,\n      httpRequest,\n    };\n\n    const stubs: typeof functions = {\n      searchAlbums: dummy,\n      createAlbum: dummy,\n      addAssetsToAlbum: dummy,\n      addAssetsToAlbums: dummy,\n      httpRequest: dummy,\n    };\n","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/immich-app/immich/blob/199723261c6ffa897fec8ccdaea6359e39c37cc3/server/src/services/workflow-execution.service.ts#L89-L125","documentation":"A plain Error thrown by the httpRequest host function when the requested URL's hostname does not match any regex pattern derived from the step's methods[].allowedHosts list. Each allowedHosts entry is converted to a regex ('.' -> '\\.', '*' -> '.*') and tested against the URL hostname; if none match, the request is blocked as a security boundary.","triggerScenarios":"A plugin calls httpRequest('https://api.example.com/...') but the method's allowedHosts in the manifest omits 'example.com' or lists a different domain. Wildcard patterns that do not cover the subdomain also fail.","commonSituations":"Plugin author lists allowedHosts but forgets a subdomain; wildcard '*.example.com' is expected but the pattern is written as 'example.com' (no wildcard); plugin switched to a different API host without updating the manifest; typo in the host entry.","solutions":["Add the target hostname (or a matching wildcard like '*.example.com') to methods[].allowedHosts in the plugin manifest and re-import.","Verify the regex translation: '*' becomes '.*' and '.' becomes a literal dot, so write allowedHosts accordingly.","Prefer the narrowest matching pattern (e.g., 'api.example.com') over broad wildcards.","After editing the manifest, force plugin re-import by changing its hash or restarting microservices."],"exampleFix":"// before (manifest.json)\n{ \"methods\": [{ \"name\": \"fetchWeather\", \"allowedHosts\": [\"weather.io\"] }] }\n// plugin calls https://api.weather.io/... -> blocked\n\n// after\n{ \"methods\": [{ \"name\": \"fetchWeather\", \"allowedHosts\": [\"*.weather.io\"] }] }","handlingStrategy":"validation","validationCode":"function isHostAllowed(url, allowedHosts) {\n  const host = new URL(url).hostname;\n  return allowedHosts.some((pattern) => {\n    const re = new RegExp(pattern.replaceAll('.', '\\\\.').replaceAll('*', '.*'));\n    return re.test(host);\n  });\n}\nif (!isHostAllowed(targetUrl, manifest.methods[0].allowedHosts)) {\n  return { ok: false, reason: 'Host not in allowedHosts' };\n}","typeGuard":"const isAllowedHostsError = (e: unknown): boolean =>\n  typeof e === 'object' && e !== null && typeof (e as any).message === 'string' &&\n  (e as any).message.includes('allowedHosts');","tryCatchPattern":"// Surfaces inside httpRequest; validate host before the plugin calls it\nif (!isHostAllowed(url, ctx.allowedHosts)) {\n  return { ok: false, status: 0, body: 'blocked by allowedHosts' };\n}","preventionTips":["List every hostname (or '*.domain' wildcard) the plugin will call in allowedHosts.","Mirror the server's regex translation when designing patterns.","Re-import the plugin after editing allowedHosts."],"tags":["workflow","plugin","network","security","allowedhosts"],"backgroundTag":null,"analyzedSha":"199723261c6ffa897fec8ccdaea6359e39c37cc3","analyzedAt":"2026-08-12T04:54:27.085Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}