Repository navigation
feat: relay the caller's own keyless signup link from the API #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
+516
−27
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
b99dcbe
feat: relay the caller's own keyless signup link from the API
rakshith48 b032a03
test: cover an untrusted signupUrl from the eligibility response
rakshith48 6f5f11e
fix: relay the API's regular signup link and fall back to it
rakshith48 97f8d79
feat: relay the API's encrypted /k/<token> keyless signup link
rakshith48 0838253
fix: validate relayed keyless signup links by URL parts, log drift
rakshith48 583a554
chore: release firecrawl-mcp 3.27.0
rakshith48 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // Keyless signup links the API may hand the MCP server to relay: the caller's | ||
| // own https://firecrawl.dev/k/<token> link, or the regular keyless signin link | ||
| // the API sends when it has no token. The URL is parsed and checked field by | ||
| // field, so parameter order and percent-encoding case don't matter, but only | ||
| // Firecrawl's own signup links are ever relayed. | ||
|
|
||
| const SIGNUP_HOSTS = new Set(['firecrawl.dev', 'www.firecrawl.dev']); | ||
| const TOKEN_PATH = /^\/k\/[0-9abcdefghjkmnpqrstvwxyz]{12}$/; | ||
| const SURFACES = new Set(['api', 'mcp', 'cli']); | ||
| const SIGNIN_PARAMS = new Set(['utm_source', 'utm_medium', 'redirect']); | ||
| const SIGNIN_REDIRECT = '/app/api-keys'; | ||
|
|
||
| /** Why a Firecrawl-hosted link was not relayed, for drift logging. */ | ||
| export type KeylessSignupUrlCheck = | ||
| | { ok: true; url: string } | ||
| | { ok: false; firecrawlHost: boolean }; | ||
|
|
||
| export function checkKeylessSignupUrl(value: unknown): KeylessSignupUrlCheck { | ||
| if (typeof value !== 'string') return { ok: false, firecrawlHost: false }; | ||
| let url: URL; | ||
| try { | ||
| url = new URL(value); | ||
| } catch { | ||
| return { ok: false, firecrawlHost: false }; | ||
| } | ||
| const firecrawlHost = SIGNUP_HOSTS.has(url.hostname); | ||
| if ( | ||
| url.protocol !== 'https:' || | ||
| !firecrawlHost || | ||
| url.port || | ||
| url.username || | ||
| url.password || | ||
| url.hash | ||
| ) { | ||
| return { ok: false, firecrawlHost }; | ||
| } | ||
| if (TOKEN_PATH.test(url.pathname) && !url.search) { | ||
| return { ok: true, url: value }; | ||
| } | ||
| if (url.pathname === '/signin') { | ||
| const params = url.searchParams; | ||
| const keys = [...params.keys()]; | ||
| const known = | ||
| keys.every((key) => SIGNIN_PARAMS.has(key)) && | ||
| new Set(keys).size === keys.length; | ||
| if ( | ||
| known && | ||
| params.get('utm_source') === 'keyless' && | ||
| SURFACES.has(params.get('utm_medium') ?? '') && | ||
| (!params.has('redirect') || params.get('redirect') === SIGNIN_REDIRECT) | ||
| ) { | ||
| return { ok: true, url: value }; | ||
| } | ||
| } | ||
| return { ok: false, firecrawlHost }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import assert from 'node:assert/strict'; | ||
| import test from 'node:test'; | ||
| import { checkKeylessSignupUrl } from '../dist/keyless-signup-link.js'; | ||
|
|
||
| const relayed = (value) => checkKeylessSignupUrl(value).ok; | ||
|
|
||
| test('relays the caller\'s own /k token link on either Firecrawl host', () => { | ||
| assert.equal(relayed('https://firecrawl.dev/k/hrxch5c20tcs'), true); | ||
| assert.equal(relayed('https://www.firecrawl.dev/k/hrxch5c20tcs'), true); | ||
| }); | ||
|
|
||
| test('relays the regular keyless signin link regardless of parameter order or encoding case', () => { | ||
| for (const url of [ | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=mcp', | ||
| 'https://firecrawl.dev/signin?utm_source=keyless&utm_medium=api', | ||
| 'https://www.firecrawl.dev/signin?utm_medium=cli&utm_source=keyless', | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=mcp&redirect=%2Fapp%2Fapi-keys', | ||
| 'https://www.firecrawl.dev/signin?redirect=%2fapp%2fapi-keys&utm_medium=mcp&utm_source=keyless', | ||
| ]) { | ||
| assert.equal(relayed(url), true, url); | ||
| } | ||
| }); | ||
|
|
||
| test('rejects anything that is not one of Firecrawl\'s own signup links', () => { | ||
| for (const url of [ | ||
| 'https://evil.example/k/hrxch5c20tcs', | ||
| 'https://firecrawl.dev.evil.example/k/hrxch5c20tcs', | ||
| 'http://firecrawl.dev/k/hrxch5c20tcs', | ||
| 'https://firecrawl.dev:8443/k/hrxch5c20tcs', | ||
| 'https://user@firecrawl.dev/k/hrxch5c20tcs', | ||
| 'https://firecrawl.dev/k/hrxch5c20tcs?x=1', | ||
| 'https://firecrawl.dev/k/hrxch5c20tcs#frag', | ||
| 'https://firecrawl.dev/k/short', | ||
| 'https://firecrawl.dev/k/HRXCH5C20TCS', | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=web', | ||
| 'https://www.firecrawl.dev/signin?utm_source=ads&utm_medium=mcp', | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=mcp&redirect=https%3A%2F%2Fevil.example', | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=mcp&next=%2Fx', | ||
| 'https://www.firecrawl.dev/signin?utm_source=keyless&utm_source=keyless&utm_medium=mcp', | ||
| 'https://www.firecrawl.dev/pricing?utm_source=keyless&utm_medium=mcp', | ||
| 'not a url', | ||
| 42, | ||
| ]) { | ||
| assert.equal(relayed(url), false, String(url)); | ||
| } | ||
| }); | ||
|
|
||
| test('flags rejected Firecrawl-hosted links so format drift can be logged', () => { | ||
| assert.deepEqual( | ||
| checkKeylessSignupUrl('https://www.firecrawl.dev/signin?utm_source=keyless&utm_medium=web'), | ||
| { ok: false, firecrawlHost: true } | ||
| ); | ||
| assert.deepEqual(checkKeylessSignupUrl('https://evil.example/k/hrxch5c20tcs'), { | ||
| ok: false, | ||
| firecrawlHost: false, | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.