Skip to content

Feature Request: Support protocol-only prefixes in allowedLinkPrefixes #27

@chanmathew

Description

@chanmathew

Summary

Allow protocol-only prefixes like 'https://' in allowedLinkPrefixes to enable "allow all HTTPS URLs" without also allowing HTTP.

Current Behavior

The allowedLinkPrefixes prop parses each prefix using new URL(), which fails for protocol-only strings:

// In utils/url.js transformUrl()
const parsedPrefix = parseUrl(prefix);  // new URL('https://') throws!
if (!parsedPrefix) return false;        // So this always returns false

This means:

  • 'https://example.com' ✅ Works (allows only example.com)
  • '*' ✅ Works (allows ALL http and https)
  • 'https://' ❌ Fails (not a valid URL, silently ignored)

Problem

There's no way to say "allow all HTTPS URLs but block HTTP". The only options are:

  1. Use '*' - Allows both http and https (security concern)
  2. List every domain - Impractical for user-generated content
  3. Use custom link snippet - Workaround, but adds complexity

Expected Behavior

Protocol-only prefixes should work:

<Streamdown allowedLinkPrefixes={['https://', 'mailto:']} />

This should allow any https:// URL while blocking http:// URLs.

Workaround

Currently we use '*' and filter in a custom link snippet:

<Streamdown allowedLinkPrefixes={['*', 'mailto:']} {...props}>
  {#snippet link({ token, children })}
    {@const href = token.href ?? ''}
    {@const isInsecureHttp = href.startsWith('http://')}

    {#if isInsecureHttp}
      <span class="text-muted-foreground">{@render children()}</span>
    {:else}
      <a {href} target="_blank" rel="noopener noreferrer">
        {@render children()}
      </a>
    {/if}
  {/snippet}
</Streamdown>

This works but requires every consumer to implement the same filtering logic.

Proposed Solution

In transformUrl(), add special handling for protocol-only prefixes before the URL parsing:

export const transformUrl = (url, allowedPrefixes, defaultOrigin) => {
  if (!url) return null;

  const parsedUrl = parseUrl(url, defaultOrigin);
  if (!parsedUrl) return null;

  // NEW: Check for protocol-only prefixes (e.g., 'https://', 'mailto:')
  const protocolOnlyMatch = allowedPrefixes.some((prefix) => {
    // Match patterns like 'https://' or 'mailto:'
    if (prefix.endsWith('://') || prefix.endsWith(':')) {
      return url.startsWith(prefix);
    }
    return false;
  });

  if (protocolOnlyMatch) {
    return parsedUrl.href;
  }

  // ... rest of existing logic
};

Use Case

We're building a chat application where users paste URLs. We want to:

  • ✅ Allow all HTTPS links (user-generated, any domain)
  • ✅ Allow mailto: and tel: links
  • ❌ Block insecure HTTP links
  • ❌ Block javascript: and other dangerous protocols

The '*' wildcard is too permissive, and listing domains is impractical.

Environment

  • svelte-streamdown: 3.0.1
  • Related to upstream vercel/streamdown behavior

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions