ruvnet/ruflo · error · Error
refusing to open non-http(s) URL scheme: ${parsed.protocol}
Error message
refusing to open non-http(s) URL scheme: ${parsed.protocol} What it means
openBrowser() only spawns a platform opener for http/https URLs; assertSafeUrl throws for any other scheme before any child process is started. This is a command-injection guard: the URL flows into xdg-open/open/start, where crafted schemes (file:, ssh:, custom handlers) can invoke arbitrary helpers.
Source
Thrown at v3/@claude-flow/security/src/oauth/browser.ts:27
* authorize URL's query string (`?a=1&b=2&...`), so passing one through would
* false-positive-reject on every real invocation. That blocklist is the wrong
* tool for a URL argument: with `shell: false` (used here, same as
* `SafeExecutor`), a single argv element containing `&` is inert — there's no
* shell to interpret it. The actual safety property that matters is "the URL
* was constructed by us from validated components, never from raw external
* input" (this module's `authorizeUrl()` in `client.ts` is the only caller),
* which `assertSafeUrl` below checks directly instead.
*
* @module v3/security/oauth/browser
*/
import { execFile } from 'node:child_process';
/** Throws if `url` isn't a well-formed https/http URL — the one check that matters here. */
function assertSafeUrl(url: string): void {
const parsed = new URL(url); // throws on malformed input
if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {
throw new Error(`refusing to open non-http(s) URL scheme: ${parsed.protocol}`);
}
}
/**
* Attempts to open `url` in the system default browser. Resolves whether or
* not a browser window actually appeared — this cannot be confirmed in
* general, which is why the caller always also prints the URL as a fallback.
*/
export function openBrowser(url: string): Promise<void> {
assertSafeUrl(url);
const { cmd, args } = browserCommand(url);
return new Promise((resolve) => {
execFile(cmd, args, { shell: false, windowsHide: true }, () => resolve());
});
}
View on GitHub (pinned to fa13ee4ad6)
Solutions
- Only pass URLs produced by authorizeUrl() — it always builds from an http(s) base
- Check and normalize process.env.COGNITUM_AUTH_URL; it must be a full http(s) origin
- If you need a custom-scheme redirect for a native app, launch it with your own validated mechanism — never this function
Example fix
// before
await openBrowser(`myapp://sso/login?state=${state}`); // refuses: non-http(s)
// after
const url = authorizeUrl(redirectUri, state, codeChallenge); // https://auth.cognitum.one/...
await openBrowser(url); Defensive patterns
Strategy: validation
Validate before calling
function isSafeBrowserUrl(url: string): boolean {
try {
const { protocol } = new URL(url);
return protocol === 'https:' || protocol === 'http:';
} catch {
return false;
}
}
if (!isSafeBrowserUrl(url)) throw new Error(`refusing to open: ${url}`);
await openBrowser(url); Type guard
function isUnsafeSchemeError(e: unknown): boolean {
return e instanceof Error && e.message.startsWith('refusing to open non-http(s) URL scheme:');
} Try / catch
try {
await openBrowser(url);
} catch (e) {
if (isUnsafeSchemeError(e)) {
console.error(`Could not auto-open ${url} — open it manually in a browser`);
} else {
throw e;
}
} Prevention
- Always build OAuth URLs with authorizeUrl() instead of string concatenation
- Validate COGNITUM_AUTH_URL overrides to http(s) origins at startup
- Treat this throw as a security control — never catch-and-bypass it with a raw spawn
When it happens
Trigger: Passing a hand-built URL with a custom app scheme to openBrowser(); COGNITUM_AUTH_URL overridden to a file:// or non-http origin so authorizeUrl() output inherits the scheme; test fixtures using example:// URLs fed to the real opener.
Common situations: Misconfigured COGNITUM_AUTH_URL pointing at an internal non-http endpoint; code bypassing authorizeUrl() to construct deep links for a native SSO app; security scanners probing the browser-open path with javascript: payloads.
Related errors
- Invalid git ref: contains unsafe characters
- Invalid GCS object path: ${objectPath}
- Invalid argument: contains shell metacharacters
- Disallowed command: only npm/npx/pnpm/yarn commands are perm
- Invalid command: contains shell metacharacters
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/01c6f15e0a11b803.
Report an issue: GitHub.