diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c850295c1..e374fca773 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,10 @@ on: push: branches: [main, feature/*, release/*] +# Least-privilege default for GITHUB_TOKEN; the `publish` job overrides with `contents: write`. +permissions: + contents: read + jobs: package: runs-on: ubuntu-latest diff --git a/packages/amazonq/test/unit/codewhisperer/service/referenceLogViewProvider.test.ts b/packages/amazonq/test/unit/codewhisperer/service/referenceLogViewProvider.test.ts index dcacf745a5..c2c96575f3 100644 --- a/packages/amazonq/test/unit/codewhisperer/service/referenceLogViewProvider.test.ts +++ b/packages/amazonq/test/unit/codewhisperer/service/referenceLogViewProvider.test.ts @@ -61,7 +61,7 @@ describe('referenceLogViewProvider', function () { assert.ok(actualTime === currentTimeString || actualTime === nextTimeString) assert.ok(actual.includes('MIT')) assert.ok(actual.includes('def two_su')) - assert.ok(actual.includes(mockUrl)) + assert.match(actual, /https:\/\/www\.amazon\.com/) assert.ok(!actual.includes(LicenseUtil.getLicenseHtml('MIT'))) }) }) @@ -97,7 +97,7 @@ describe('referenceLogViewProvider', function () { assert.ok(actual.includes('apache')) assert.ok(actual.includes('TEST_REPO')) assert.ok(actual.includes('test reference')) - assert.ok(actual.includes('flare.com')) - assert.ok(actual.includes('cw.com')) + assert.match(actual, /flare\.com/) + assert.match(actual, /cw\.com/) }) }) diff --git a/packages/core/src/auth/auth.ts b/packages/core/src/auth/auth.ts index d2f6f72812..e068d59391 100644 --- a/packages/core/src/auth/auth.ts +++ b/packages/core/src/auth/auth.ts @@ -156,7 +156,15 @@ export class Auth implements AuthService, ConnectionManager { * e.g. https://view.awsapps.com/start/# will become https://view.awsapps.com/start */ public normalizeStartUrl(startUrl: string | undefined) { - return !startUrl ? undefined : startUrl.replace(/[\/#]+$/g, '') + if (!startUrl) { + return undefined + } + // Strip trailing '/' and '#' without a backtracking-prone regex (avoids ReDoS). + let end = startUrl.length + while (end > 0 && (startUrl[end - 1] === '/' || startUrl[end - 1] === '#')) { + end-- + } + return startUrl.slice(0, end) } public isInternalAmazonUser(): boolean { diff --git a/packages/core/src/codewhisperer/util/authUtil.ts b/packages/core/src/codewhisperer/util/authUtil.ts index e5177e7b57..bb7a8b58da 100644 --- a/packages/core/src/codewhisperer/util/authUtil.ts +++ b/packages/core/src/codewhisperer/util/authUtil.ts @@ -164,7 +164,15 @@ export class AuthUtil { } public reformatStartUrl(startUrl: string | undefined) { - return !startUrl ? undefined : startUrl.replace(/[\/#]+$/g, '') + if (!startUrl) { + return undefined + } + // Strip trailing '/' and '#' without a backtracking-prone regex (avoids ReDoS). + let end = startUrl.length + while (end > 0 && (startUrl[end - 1] === '/' || startUrl[end - 1] === '#')) { + end-- + } + return startUrl.slice(0, end) } // current active cwspr connection diff --git a/packages/core/src/codewhisperer/util/importAdderUtil.ts b/packages/core/src/codewhisperer/util/importAdderUtil.ts index fa53944def..03024e069a 100644 --- a/packages/core/src/codewhisperer/util/importAdderUtil.ts +++ b/packages/core/src/codewhisperer/util/importAdderUtil.ts @@ -28,7 +28,7 @@ export function findLineOfFirstCode(editor: vscode.TextEditor, firstLineOfRecomm // skip //, /*, *, */, empty line if ( !text.match(/^\s*\/\//) && - !text.match(/\s*use\s+strict/) && + !text.match(/^\s*['"]?use\s+strict/) && !text.match(/^\s*$/) && !text.match(/^\s*\/\s*\*/) && !text.match(/^\s*\*/) && @@ -62,7 +62,7 @@ export function findLineOfLastImportStatement(editor: vscode.TextEditor, firstLi return i + 1 } } else if (lang === 'javascript' || lang === 'jsx') { - if (text.match(/^\s*import\s+\S+/) || text.match(/=\s*require\s*\(\s*\S+\s*\)\s*;/)) { + if (text.match(/^\s*import\s+\S+/) || text.match(/=\s*require\s*\(/)) { return i + 1 } } else if (lang === 'java') { diff --git a/packages/core/src/shared/utilities/proxyUtil.ts b/packages/core/src/shared/utilities/proxyUtil.ts index e617bcd85c..2b2d4496a2 100644 --- a/packages/core/src/shared/utilities/proxyUtil.ts +++ b/packages/core/src/shared/utilities/proxyUtil.ts @@ -89,10 +89,14 @@ export class ProxyUtil { } const strictSSL = config.proxyStrictSSL - // Handle SSL certificate verification + // Handle SSL certificate verification. if (!strictSSL) { + // Intentional, explicit user opt-in: this mirrors VS Code's own `http.proxyStrictSSL` + // setting. Only disabled when the user has explicitly turned off strict SSL, e.g. for + // corporate proxies with self-signed certificates. Logged as a warning so it is visible. + // codeql[js/disabling-certificate-validation] process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' - this.logger.info('SSL verification disabled via VS Code settings') + this.logger.warn('Proxy SSL certificate verification disabled via VS Code "http.proxyStrictSSL" setting') return // No need to set CA certs when SSL verification is disabled } diff --git a/packages/core/src/shared/utilities/textUtilities.ts b/packages/core/src/shared/utilities/textUtilities.ts index 6bc1a4eff2..7661e4138a 100644 --- a/packages/core/src/shared/utilities/textUtilities.ts +++ b/packages/core/src/shared/utilities/textUtilities.ts @@ -53,9 +53,9 @@ export function indent(s: string, size: number = 4, clear: boolean = false): str throw Error() // TODO: implement "dedent" for negative size. } if (clear) { - return s.replace(/^[ \t]*([^\n])/, `${spaces}$1`).replace(/(\n+)[ \t]*([^ \t\n])/g, `$1${spaces}$2`) + return s.replace(/^[ \t]*([^\n])/, `${spaces}$1`).replace(/(\n)[ \t]*([^ \t\n])/g, `$1${spaces}$2`) } - return spaces + s.replace(/(\n+)(.)/g, `$1${spaces}$2`) + return spaces + s.replace(/(\n)([^\n])/g, `$1${spaces}$2`) } /** @@ -134,7 +134,7 @@ export function addCodiconToString(codiconName: string, text: string): string { * @returns Final output without any new lines or comments */ export function stripNewLinesAndComments(text: string): string { - const blockCommentRegExp = /\/\*.*\*\// + const blockCommentRegExp = /\/\*(?:[^*]|\*(?!\/))*\*\//g let result: string = '' text.split(/\r|\n/).map((s) => { @@ -249,7 +249,13 @@ export function getRandomString(length = 32) { * @returns a base 64 url string */ export function toBase64URL(base64: string) { - return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') + const encoded = base64.replace(/\+/g, '-').replace(/\//g, '_') + // Strip trailing '=' padding without a backtracking-prone regex (avoids ReDoS). + let end = encoded.length + while (end > 0 && encoded[end - 1] === '=') { + end-- + } + return encoded.slice(0, end) } export function undefinedIfEmpty(str: string | undefined): string | undefined { diff --git a/packages/core/src/test/shared/extensions/ssh.test.ts b/packages/core/src/test/shared/extensions/ssh.test.ts index 4e9bda4121..cc83d063bf 100644 --- a/packages/core/src/test/shared/extensions/ssh.test.ts +++ b/packages/core/src/test/shared/extensions/ssh.test.ts @@ -49,7 +49,7 @@ function echoEnvVarsCmd(varNames: string[]) { * Trim noisy windows ChildProcess result to final line for easier testing. */ function assertOutputContains(rawOutput: string, expectedString: string): void | never { - const output = rawOutput.trim().split('\n').at(-1)?.replace('"', '') ?? '' + const output = rawOutput.trim().split('\n').at(-1)?.replace(/"/g, '') ?? '' assert.ok(output.includes(expectedString), `Expected output to contain "${expectedString}", but got "${output}"`) } diff --git a/scripts/createRelease.ts b/scripts/createRelease.ts index d6f39203fc..82726cd299 100644 --- a/scripts/createRelease.ts +++ b/scripts/createRelease.ts @@ -58,8 +58,9 @@ if (changelog.entries.length === 0) { append += '\n' + fileData.toString() nodefs.writeFileSync('CHANGELOG.md', append) -child_process.execSync(`git add ${changesDirectory}`) -child_process.execSync(`git rm -rf --ignore-unmatch ${nextReleaseDirectory}`) -child_process.execSync('git add CHANGELOG.md') +// Use execFileSync (no shell) so directory paths cannot be interpreted as shell syntax. +child_process.execFileSync('git', ['add', changesDirectory]) +child_process.execFileSync('git', ['rm', '-rf', '--ignore-unmatch', nextReleaseDirectory]) +child_process.execFileSync('git', ['add', 'CHANGELOG.md']) console.log(changesFile) diff --git a/scripts/newChange.ts b/scripts/newChange.ts index 7490e68631..08c207191b 100644 --- a/scripts/newChange.ts +++ b/scripts/newChange.ts @@ -53,5 +53,6 @@ const path = join(directory, fileName) nodefs.writeFileSync(path, JSON.stringify(contents, undefined, '\t') + '\n') console.log(`Change log written to ${path}`) -child_process.execSync(`git add ${directory}`) +// Use execFileSync (no shell) so the directory path cannot be interpreted as shell syntax. +child_process.execFileSync('git', ['add', directory]) console.log('Change log added to git working directory')