agalwood/Motrix · error · HttpError
plugin.http.scheme_not_allowed
plugin.http.scheme_not_allowed
Error message
URL scheme '${parsed.protocol}' is not allowed; use http: or https: What it means
The HTTP capability only permits http: and https: URLs. parseUrl() accepts any syntactically valid URL (including file:, ftp:, data:), then checkScheme() rejects anything not in ALLOWED_SCHEMES. This is an SSRF / local-file-access guard around plugin-initiated network calls, and it is re-applied on every redirect hop so a Location response cannot escape the allowlist.
Source
Thrown at src/core/plugin/capabilities/http.ts:142
// Shared dispatcher for non-proxied requests; per-call ProxyAgent is built
// fresh when `opts.proxy` is provided.
const sharedAgent = new Agent()
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function parseUrl(raw: string): URL {
try {
return new URL(raw)
} catch {
throw new HttpError('plugin.http.invalid_url', `Invalid URL: ${raw}`)
}
}
function checkScheme(parsed: URL): void {
if (!ALLOWED_SCHEMES.has(parsed.protocol)) {
throw new HttpError(
'plugin.http.scheme_not_allowed',
`URL scheme '${parsed.protocol}' is not allowed; use http: or https:`
)
}
}
function clampTimeout(ms: number | undefined, defaultMs: number): number {
// Reject non-finite values (NaN/Infinity) — http.get/post reach this with
// unvalidated opts, and Math.min(MAX, NaN) is NaN, disabling the timeout.
if (ms === undefined || !Number.isFinite(ms)) return defaultMs
return Math.max(MIN_TIMEOUT_MS, Math.min(MAX_TIMEOUT_MS, ms))
}
function clampMaxBody(
requested: number | undefined,
defaultBytes: number
): number {
// Only accept a finite, positive request. A NaN (Math.min(NaN, HARD) = NaN)View on GitHub (pinned to 1a708ee577)
Solutions
- Normalize URLs to http:// or https:// before calling the http capability.
- For local files, use the fs/storage capability instead of file:// over http.
- Sanitize untrusted URL input (strip/replace non-http protocols) at the plugin boundary.
- If a redirect target is the cause, set redirect:'manual' and inspect each Location yourself.
Example fix
// before
await http.request({ url: 'file:///etc/passwd', responseType: 'text' })
// after
await http.request({ url: 'https://example.com/data', responseType: 'text' }) Defensive patterns
Strategy: validation
Validate before calling
import { ALLOWED_SCHEMES } from '...'
function assertHttpUrl(raw: string): URL {
const u = new URL(raw)
if (u.protocol !== 'http:' && u.protocol !== 'https:') {
throw new Error(`refusing non-http(s) URL: ${u.protocol}`)
}
return u
}
// at call site:
assertHttpUrl(url)
await http.request({ url, responseType: 'json' }) Type guard
function isHttpUrl(raw: string): raw is `${'http'|'https'}://${string}` {
try {
const u = new URL(raw)
return u.protocol === 'http:' || u.protocol === 'https:'
} catch { return false }
} Try / catch
try {
await http.request({ url, responseType: 'json' })
} catch (e) {
if (e instanceof HttpError && e.code === 'plugin.http.scheme_not_allowed') {
// log and reject the untrusted input; do not retry as-is
} else throw e
} Prevention
- Treat URLs from untrusted input as data, parse and validate the scheme before use.
- Never construct file:// or ftp:// URLs for the http capability; use fs for local I/O.
- When following redirects manually, re-run scheme validation on each Location.
When it happens
Trigger: Calling http.get/post/request with a URL whose protocol is not http:/https: (e.g. 'file:///etc/passwd', 'ftp://host', 'data:text/plain,x'). Also triggered when a 3xx response's Location header points to a non-allowed scheme, because checkScheme() runs on every hop at http.ts:412.
Common situations: Plugin builds a URL from user/config input without normalizing; plugin reads a local-resource path formatted as file://; CI/runtime upgrades tighten the allowlist; a server or short-link service redirects to a non-http scheme.
Related errors
- plugin.http.redirect_not_allowed
- manifest too large: ${text.length} > ${max}
- plugin.http.too_many_redirects
- manifest fetch failed: HTTP ${res.status}
- plugin.fs.path_outside_sandbox
AI-assisted analysis of agalwood/Motrix@1a708ee577 (2026-08-12).
Data as JSON: /api/errors/f2efba36178d3f32.
Report an issue: GitHub.