-
-
Notifications
You must be signed in to change notification settings - Fork 0
fix: improve error handling in datasource URL parsing #18
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 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -71,15 +71,9 @@ function loadPrismaConfig(schemaDir: string): string | null { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Use Function constructor to safely evaluate the object literal | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // This is safer than eval as it doesn't have access to the local scope | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const configFn = new Function('env', `return ${configObjectStr}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const config = configFn(env) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return config?.datasource?.url || null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (evalError) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.warn(`Warning: Could not evaluate config object: ${evalError}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return null | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const configFn = new Function('env', `return ${configObjectStr}`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const config = configFn(env) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return config?.datasource?.url | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+74
to
+76
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const configFn = new Function('env', `return ${configObjectStr}`) | |
| const config = configFn(env) | |
| return config?.datasource?.url | |
| try { | |
| const configFn = new Function('env', `return ${configObjectStr}`) | |
| const config = configFn(env) | |
| return config?.datasource?.url | |
| } catch (error) { | |
| if (error instanceof Error && error.message.includes('Environment variable')) { | |
| // Propagate environment variable errors so they can be handled upstream | |
| throw error | |
| } | |
| console.warn(`Warning: Failed to parse prisma.config.ts: ${error}`) | |
| return null | |
| } |
Copilot
AI
Jan 10, 2026
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.
The regex pattern ([^\n]+) will capture everything up to the newline, including trailing whitespace, semicolons, and potentially inline comments. For example, with url = "postgres://..." // comment, this will capture "postgres://..." // comment. While the .trim() on line 119 removes leading/trailing whitespace, it won't handle inline comments or trailing semicolons that might be part of urlValueStr before evaluation. This could cause the Function constructor evaluation to fail unexpectedly. Consider using a more specific pattern like ([^\n;/]+) to stop at semicolons or comment markers, or add additional sanitization of the captured value.
Copilot
AI
Jan 10, 2026
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.
Using the Function constructor with user-controlled input (schema file content) poses a security risk. While the code attempts to sandbox by only passing the env function, the evaluated code can still execute arbitrary JavaScript. An attacker with control over the schema file could potentially inject malicious code. Consider adding input validation to ensure urlValueStr only contains expected patterns (quotes, env() calls, etc.) before evaluation, or use a safer alternative like a purpose-built parser for Prisma schema syntax.
| try { | |
| // Use Function constructor to evaluate the url value | |
| const urlFn = new Function('env', `return ${urlValueStr}`) | |
| url = urlFn(env) | |
| // Safely evaluate limited URL expressions without using the Function constructor. | |
| // Supported forms: | |
| // - String literal: "postgres://..." or 'postgres://...' | |
| // - env("VAR") or env('VAR') | |
| // - Concatenations using + of the above, e.g. env("URL") + "/shadow" | |
| const evaluateUrlExpression = (expr: string): string => { | |
| const parts = expr.split('+').map((p) => p.trim()).filter((p) => p.length > 0) | |
| if (parts.length === 0) { | |
| throw new CliError( | |
| 'Could not evaluate datasource url from schema, you could provide it via -d option.' | |
| ) | |
| } | |
| let result = '' | |
| for (const part of parts) { | |
| // Match simple string literal | |
| const stringMatch = part.match(/^(['"])(.*)\1$/) | |
| if (stringMatch) { | |
| result += stringMatch[2] | |
| continue | |
| } | |
| // Match env("VAR") or env('VAR') | |
| const envMatch = part.match(/^env\(\s*(['"])([A-Za-z_][A-Za-z0-9_]*)\1\s*\)$/) | |
| if (envMatch) { | |
| result += env(envMatch[2]) | |
| continue | |
| } | |
| // Any other pattern is considered unsupported for security reasons | |
| throw new CliError( | |
| 'Unsupported datasource URL expression in schema; only string literals, env() calls, and "+" concatenations are allowed.' | |
| ) | |
| } | |
| return result | |
| } | |
| try { | |
| url = evaluateUrlExpression(urlValueStr) |
Copilot
AI
Jan 10, 2026
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.
Inconsistent error message format and option naming. Line 136 uses "-d option" while line 144 uses "-d option" but previously the code mentioned "--datasource-url option" (visible in the diff context). The error messages should consistently reference the same option format. Additionally, line 136 says "Could not evaluate datasource url" while line 144 says "No datasource URL found" - these should follow a consistent capitalization style for "datasource URL" vs "datasource url".
Copilot
AI
Jan 10, 2026
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.
There is a logic issue with the dual error checking. When urlMatch succeeds (line 118), url is assigned from the evaluation (line 133), then the check at line 149 validates if it's truthy. However, when urlMatch fails (line 139), the code loads from loadPrismaConfig, checks if it's null at line 142, but then checks again at line 149. This means when coming from the else branch, if url is null, it throws at line 142, making the check at line 149 unreachable for that path. If coming from the if branch, the evaluation could theoretically return an empty string or other falsy value that's not caught by the try-catch. The logic should be restructured to have a single validation point after both paths complete, or the check at line 149 should be removed if line 142 is sufficient for all cases.
| // If still no URL found, throw error | |
| if (url == null) { | |
| throw new CliError( | |
| 'No datasource URL found. For Prisma 7, ensure prisma.config.ts exists with datasource configuration, or provide the URL via -d option.' | |
| ) | |
| } | |
| } | |
| } | |
| if (url == null) { | |
| throw new CliError( | |
| 'No datasource URL found. For Prisma 7, ensure prisma.config.ts exists with datasource configuration, or provide the URL via -d option.' | |
| ) | |
| } |
Copilot
AI
Jan 10, 2026
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.
The error message uses inconsistent capitalization and grammar. It should be "Datasource URL has no value, you could provide it via -d option." with "Datasource" capitalized to match the style of other error messages, or follow a consistent lowercase style throughout. Additionally, consider adding a period after "option" for consistency with other error messages in the codebase.
| throw new CliError('datasource url has no value, you could provide it via -d option.') | |
| throw new CliError('Datasource URL has no value, you could provide it via -d option.') |
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.
The return type signature of
loadPrismaConfigisstring | null, but line 76 can potentially returnundefinedifconfig?.datasource?.urlis undefined. While functionally similar, this creates a type inconsistency. Consider explicitly handling the undefined case by changing toreturn config?.datasource?.url || nullorreturn config?.datasource?.url ?? nullto ensure the return value strictly matches the declared type signature.