-
Notifications
You must be signed in to change notification settings - Fork 3
Description
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 falseThis 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:
- Use
'*'- Allows both http and https (security concern) - List every domain - Impractical for user-generated content
- 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/streamdownbehavior