-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
fix: validate server fn id during dev #6087
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| /// <reference types="vite/client" /> | ||
| import crypto from 'node:crypto' | ||
| import assert from 'node:assert' | ||
| import { TanStackDirectiveFunctionsPluginEnv } from '@tanstack/directive-functions-plugin' | ||
| import type { DevEnvironment, Plugin } from 'vite' | ||
| import type { Plugin } from 'vite' | ||
| import type { | ||
| DirectiveFn, | ||
| GenerateFunctionIdFn, | ||
|
|
@@ -39,16 +40,26 @@ const debug = | |
| process.env.TSR_VITE_DEBUG && | ||
| ['true', 'server-functions-plugin'].includes(process.env.TSR_VITE_DEBUG) | ||
|
|
||
| const validateServerFnIdVirtualModule = `virtual:tanstack-start-validate-server-fn-id` | ||
|
|
||
| function parseIdQuery(id: string): { | ||
| filename: string | ||
| query: { | ||
| [k: string]: string | ||
| } | ||
| } { | ||
| if (!id.includes('?')) return { filename: id, query: {} } | ||
| const [filename, rawQuery] = id.split(`?`, 2) as [string, string] | ||
| const query = Object.fromEntries(new URLSearchParams(rawQuery)) | ||
| return { filename, query } | ||
| } | ||
|
|
||
| export function TanStackServerFnPlugin( | ||
| opts: TanStackServerFnPluginOpts, | ||
| ): Array<Plugin> { | ||
| const directiveFnsById: Record<string, DirectiveFn> = {} | ||
| let serverDevEnv: DevEnvironment | undefined | ||
|
|
||
| const onDirectiveFnsById = (d: Record<string, DirectiveFn>) => { | ||
| if (serverDevEnv) { | ||
| return | ||
| } | ||
| if (debug) { | ||
| console.info(`onDirectiveFnsById received: `, d) | ||
| } | ||
|
|
@@ -152,6 +163,24 @@ export function TanStackServerFnPlugin( | |
| provider: opts.provider, | ||
| callers: opts.callers, | ||
| }), | ||
| { | ||
| name: 'tanstack-start-server-fn-vite-plugin-validate-serverfn-id', | ||
| apply: 'serve', | ||
| load: { | ||
| filter: { | ||
| id: new RegExp(resolveViteId(validateServerFnIdVirtualModule)), | ||
| }, | ||
| handler(id) { | ||
| const parsed = parseIdQuery(id) | ||
| assert(parsed) | ||
| assert(parsed.query.id) | ||
| if (directiveFnsById[parsed.query.id]) { | ||
| return `export {}` | ||
| } | ||
| this.error(`Invalid server function ID: ${parsed.query.id}`) | ||
| }, | ||
| }, | ||
| }, | ||
|
Comment on lines
166
to
183
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix the The current pattern Additionally, the assertions at lines 177–178 ( id: new RegExp(
- `^${resolveViteId(validateServerFnIdVirtualModule)}?.+`,
+ `^${escapeRegExp(resolveViteId(validateServerFnIdVirtualModule))}\\?id=.+$`,
),
handler(id) {
const parsed = parseIdQuery(id)
- assert(parsed)
- assert(parsed.query.id)
- if (directiveFnsById[parsed.query.id]) {
+ const serverFnId = parsed?.query?.id
+ if (!serverFnId) {
+ this.error(`Missing server function ID in: ${id}`)
+ }
+ if (directiveFnsById[serverFnId]) {
return `export {}`
}
- this.error(`Invalid server function ID: ${parsed.query.id}`)
+ this.error(`Invalid server function ID: ${serverFnId}`)
}Also add the escaping helper at the top of the file: +function escapeRegExp(s: string) {
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
+}Finally: verify that 🧰 Tools🪛 ast-grep (0.40.0)[warning] 170-172: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns. (regexp-from-variable) 🤖 Prompt for AI Agents |
||
| { | ||
| // On the server, we need to be able to read the server-function manifest from the client build. | ||
| // This is likely used in the handler for server functions, so we can find the server function | ||
|
|
@@ -191,6 +220,8 @@ export function TanStackServerFnPlugin( | |
| if (this.environment.mode !== 'build') { | ||
| const mod = ` | ||
| export async function getServerFnById(id) { | ||
| const validateIdImport = ${JSON.stringify(validateServerFnIdVirtualModule)} + '?id=' + id | ||
| await import(/* @vite-ignore */ '/@id/__x00__' + validateIdImport) | ||
| const decoded = Buffer.from(id, 'base64url').toString('utf8') | ||
| const devServerFn = JSON.parse(decoded) | ||
| const mod = await import(/* @vite-ignore */ devServerFn.file) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parseIdQueryis fine, but it makes “missing id” look like an assertion failure.parseIdQuerynever returns falsy, soassert(parsed)(Line 177) is redundant, andassert(parsed.query.id)will throw an AssertionError instead of a clear Vite error. Prefer an explicit check andthis.error('Missing ...')for consistency with the invalid-id path.🤖 Prompt for AI Agents