DIYgod/RSSHub · warning

Invalid URL property: ${prop}

Error message

Invalid URL property: ${prop}

What it means

Thrown by validateTemplate in the anti-hotlink middleware when a user-supplied hotlink template contains a ${prop} or ${prop_ue} placeholder whose `prop` is not a standard URL object property. The allowed set is exactly: hash, host, hostname, href, origin, password, pathname, port, protocol, search, searchParams, username. The `_ue` suffix requests URL-encoding of the value.

Source

Thrown at lib/middleware/anti-hotlink.ts:97

        replaceUrls($, '*[data-rsshub-image="href"]', image_hotlink_template, 'href');
    }
    if (multimedia_hotlink_template) {
        replaceUrls($, 'video, video > source, audio, audio > source', multimedia_hotlink_template);
        if (!image_hotlink_template) {
            replaceUrls($, 'video[poster]', multimedia_hotlink_template, 'poster');
        }
    }
    return $.html();
};

const validateTemplate = (template?: string) => {
    if (!template) {
        return;
    }
    for (const match of template.matchAll(templateRegex)) {
        const prop = match[1].endsWith('_ue') ? match[1].slice(0, -3) : match[1];
        if (!allowedUrlProperties.has(prop)) {
            throw new Error(`Invalid URL property: ${prop}`);
        }
    }
};

const middleware: MiddlewareHandler = async (ctx, next) => {
    await next();

    let imageHotlinkTemplate: string | undefined;
    let multimediaHotlinkTemplate: string | undefined;

    // Read params if enabled
    if (config.feature.allow_user_hotlink_template) {
        // By default, the config turns these features off. Set corresponding config to
        // true to turn this feature on.
        // A risk is that the media URLs will be replaced by user-supplied templates,
        // so a user could literally take the control of "where are the media from",
        // but only in their personal-use feed URL.
        multimediaHotlinkTemplate = ctx.req.query('multimedia_hotlink_template');

View on GitHub (pinned to bed535e087)

Solutions

  1. Replace the invalid placeholder with one of: ${hash} ${host} ${hostname} ${href} ${origin} ${password} ${pathname} ${port} ${protocol} ${search} ${searchParams} ${username}.
  2. Append `_ue` (e.g. ${pathname_ue}) only for URL-encoding, never inside the property name otherwise.
  3. If you do not need a custom template, omit the query param so the config default is used.
  4. Disable allow_user_hotlink_template if templates are not required.

Example fix

// before
?image_hotlink_template=https://proxy.example.com/?url=${host_name}${pathname}
// after
?image_hotlink_template=https://proxy.example.com/?url=${host}${pathname_ue}
Defensive patterns

Strategy: validation

Validate before calling

const ALLOWED = new Set(['hash','host','hostname','href','origin','password','pathname','port','protocol','search','searchParams','username']);
const validateTemplate = (tpl: string) => {
  for (const m of tpl.matchAll(/\$\{([^{}]+)\}/g)) {
    const prop = m[1].endsWith('_ue') ? m[1].slice(0, -3) : m[1];
    if (!ALLOWED.has(prop)) throw new Error(`Invalid URL property: ${prop}`);
  }
};
validateTemplate(imageHotlinkTemplate);

Type guard

const isPlaceholderValid = (p: string): boolean =>
  ALLOWED.has(p.endsWith('_ue') ? p.slice(0, -3) : p);

Try / catch

try { validateTemplate(tpl); } catch (e) {
  // fall back to the default config template rather than failing the request
  tpl = undefined;
}

Prevention

When it happens

Trigger: config.feature.allow_user_hotlink_template is true and the request (or config) supplies an image_hotlink_template / multimedia_hotlink_template containing a placeholder like ${foo} or ${host_name} that is not in the allowed set.

Common situations: Typo in the template placeholder; assuming a custom field (e.g. ${domain}) exists on the URL object; copying a template from docs that used a property name that was never supported.

Related errors


AI-assisted analysis of DIYgod/RSSHub@bed535e087 (2026-08-12). Data as JSON: /api/errors/8468be7f34486b5d. Report an issue: GitHub.