-
Notifications
You must be signed in to change notification settings - Fork 2.3k
security: low-risk renderer decoupling prep #9988
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
base: develop
Are you sure you want to change the base?
Changes from all commits
6357df7
3af2acf
3f9e7bf
22201ec
62a9f1e
d1e806b
a066dbe
dd5055d
8586b07
b5e86d2
4a69f67
1c3aa3d
650c1e8
1f6736f
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 |
|---|---|---|
|
|
@@ -41,3 +41,4 @@ rootCA2.* | |
| final.cpp | ||
| insomnia.ico | ||
| final.rc | ||
| .tmp* | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| const extensionToMimeType: Record<string, string> = { | ||
| csv: 'text/csv', | ||
| gif: 'image/gif', | ||
| html: 'text/html', | ||
| jpeg: 'image/jpeg', | ||
| jpg: 'image/jpeg', | ||
| js: 'application/javascript', | ||
| json: 'application/json', | ||
| pdf: 'application/pdf', | ||
| png: 'image/png', | ||
| svg: 'image/svg+xml', | ||
| txt: 'text/plain', | ||
| xml: 'application/xml', | ||
| yaml: 'application/yaml', | ||
| yml: 'application/yaml', | ||
| }; | ||
|
|
||
| const mimeTypeToExtension: Record<string, string> = { | ||
| ...Object.fromEntries( | ||
| Object.entries(extensionToMimeType).map(([extension, mimeType]) => [mimeType, extension]), | ||
| ), | ||
| 'application/octet-stream': 'bin', | ||
| }; | ||
|
|
||
| export const lookupMimeType = (filePath: string) => { | ||
| const match = /\.([^.]+)$/.exec(filePath.trim().toLowerCase()); | ||
| if (!match) { | ||
| return false; | ||
| } | ||
|
|
||
| return extensionToMimeType[match[1]] || false; | ||
| }; | ||
|
|
||
| export const mimeTypeExtension = (contentType: string) => { | ||
| const normalizedType = contentType.split(';', 1)[0]?.trim().toLowerCase(); | ||
| if (!normalizedType) { | ||
| return false; | ||
| } | ||
|
|
||
| if (mimeTypeToExtension[normalizedType]) { | ||
| return mimeTypeToExtension[normalizedType]; | ||
| } | ||
|
|
||
| const subtype = normalizedType.split('/')[1]; | ||
| return subtype?.split('+').pop() || false; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,7 +54,6 @@ import { QUERY_PARAMS } from './api-key/constants'; | |
| import { getAuthObjectOrNull, isAuthEnabled } from './authentication'; | ||
| import { filterClientCertificates } from './certificate'; | ||
| import type { TransformedExecuteScriptContext } from './concurrency'; | ||
| import { addSetCookiesToToughCookieJar } from './set-cookie-util'; | ||
|
|
||
| const { isRequest } = models.request; | ||
| const { isRequestGroup } = models.requestGroup; | ||
|
|
@@ -1009,6 +1008,8 @@ const extractCookies = async ( | |
| const totalSetCookies = setCookieStrings.length; | ||
| if (totalSetCookies) { | ||
| const currentUrl = getCurrentUrl({ headerResults, finalUrl }); | ||
| // Lazy load cookie utilities only when needed to avoid upfront overhead | ||
| const { addSetCookiesToToughCookieJar } = await import('./set-cookie-util'); | ||
|
Member
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. Why do we use dynamic import here?
Contributor
Author
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. ✅ Addressed in latest commits |
||
| const { cookies, rejectedCookies } = await addSetCookiesToToughCookieJar({ | ||
| setCookieStrings, | ||
| currentUrl, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,3 @@ | ||
| import contentDisposition from 'content-disposition'; | ||
| import { extension as mimeExtension } from 'mime-types'; | ||
| import { href, redirect } from 'react-router'; | ||
| import { v4 as uuidv4 } from 'uuid'; | ||
|
|
||
|
|
@@ -69,6 +67,65 @@ export interface RunnerContextForRequest { | |
| responseId: string; | ||
| } | ||
|
|
||
| const stripQuotedValue = (value: string) => { | ||
| const trimmed = value.trim(); | ||
| if (trimmed.startsWith('"') && trimmed.endsWith('"')) { | ||
| return trimmed.slice(1, -1).replace(/\\(.)/g, '$1'); | ||
| } | ||
| return trimmed; | ||
| }; | ||
|
|
||
| const parseContentDispositionFilename = (headerValue: string) => { | ||
| const filenameStarMatch = headerValue.match(/filename\*\s*=\s*([^;]+)/i); | ||
| if (filenameStarMatch) { | ||
| const encodedValue = stripQuotedValue(filenameStarMatch[1]); | ||
| const parts = encodedValue.split("'"); | ||
| const value = parts.length >= 3 ? parts.slice(2).join("'") : encodedValue; | ||
|
|
||
| try { | ||
| return decodeURIComponent(value); | ||
| } catch { | ||
| return value; | ||
| } | ||
| } | ||
|
|
||
| const filenameMatch = headerValue.match(/filename\s*=\s*("(?:[^"\\]|\\.)*"|[^;]+)/i); | ||
| return filenameMatch ? stripQuotedValue(filenameMatch[1]) : null; | ||
| }; | ||
|
|
||
| const getDownloadFileExtension = (contentType?: string | null) => { | ||
| const normalizedType = contentType?.split(';', 1)[0]?.trim().toLowerCase(); | ||
| if (!normalizedType) { | ||
| return 'unknown'; | ||
| } | ||
|
|
||
| switch (normalizedType) { | ||
| case 'application/json': { | ||
| return 'json'; | ||
| } | ||
| case 'application/pdf': { | ||
| return 'pdf'; | ||
| } | ||
| case 'application/xml': | ||
| case 'text/xml': { | ||
| return 'xml'; | ||
| } | ||
| case 'text/csv': { | ||
| return 'csv'; | ||
| } | ||
| case 'text/html': { | ||
| return 'html'; | ||
| } | ||
| case 'text/plain': { | ||
| return 'txt'; | ||
| } | ||
| default: { | ||
| const subtype = normalizedType.split('/')[1]; | ||
| return subtype?.split('+').pop() || 'unknown'; | ||
| } | ||
|
Comment on lines
+122
to
+125
Contributor
Author
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. ✅ Addressed in latest commits |
||
| } | ||
| }; | ||
|
|
||
| const writeToDownloadPath = async ( | ||
| downloadPathAndName: string, | ||
| responsePatch: ResponsePatch, | ||
|
|
@@ -312,9 +369,8 @@ export const sendActionImplementation = async (options: { | |
|
|
||
| if (requestMeta.downloadPath) { | ||
| const header = getContentDispositionHeader(responsePatch.headers || []); | ||
| const name = header | ||
| ? contentDisposition.parse(header.value).parameters.filename | ||
| : `${requestData.request.name.replace(/\s/g, '-').toLowerCase()}.${(responsePatch.contentType && mimeExtension(responsePatch.contentType)) || 'unknown'}`; | ||
| const fallbackName = `${requestData.request.name.replace(/\s/g, '-').toLowerCase()}.${getDownloadFileExtension(responsePatch.contentType)}`; | ||
| const name = header ? parseContentDispositionFilename(header.value) || fallbackName : fallbackName; | ||
| await writeToDownloadPath( | ||
| window.path.join(requestMeta.downloadPath, name), | ||
| responsePatch, | ||
|
|
||
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.
✅ Addressed in latest commits