diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts index 94a7bc142ca291232c29d289a9e451b955c901ce..0d7485f93ecd32681650eb24c9b40aed0a108185 100644 --- a/lib/platform/azure/index.ts +++ b/lib/platform/azure/index.ts @@ -5,6 +5,7 @@ import { appSlug } from '../../config/app-strings'; import GitStorage from '../git/storage'; import { logger } from '../../logger'; import { PlatformConfig, RepoParams, RepoConfig } from '../common'; +import { sanitize } from '../../util/sanitize'; interface Config { storage: GitStorage; @@ -353,7 +354,7 @@ export async function createPr( const targetRefName = azureHelper.getNewBranchName( useDefaultBranch ? config.defaultBranch : config.baseBranch ); - const description = azureHelper.max4000Chars(hostRules.sanitize(body)); + const description = azureHelper.max4000Chars(sanitize(body)); const azureApiGit = await azureApi.gitApi(); const workItemRefs = [ { @@ -405,9 +406,7 @@ export async function updatePr(prNo: number, title: string, body?: string) { title, }; if (body) { - objToUpdate.description = azureHelper.max4000Chars( - hostRules.sanitize(body) - ); + objToUpdate.description = azureHelper.max4000Chars(sanitize(body)); } await azureApiGit.updatePullRequest(objToUpdate, config.repoId, prNo); } @@ -418,7 +417,7 @@ export async function ensureComment( content: string ) { logger.debug(`ensureComment(${issueNo}, ${topic}, content)`); - const body = `### ${topic}\n\n${hostRules.sanitize(content)}`; + const body = `### ${topic}\n\n${sanitize(content)}`; const azureApiGit = await azureApi.gitApi(); await azureApiGit.createThread( { diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index b0b5aa73cc3e9c6015e4af18f24bcc91511e3bbc..02db3b56f71c699099d23122ff068b77f8189753 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -7,6 +7,7 @@ import * as hostRules from '../../util/host-rules'; import GitStorage from '../git/storage'; import { logger } from '../../logger'; import { PlatformConfig, RepoParams, RepoConfig } from '../common'; +import { sanitize } from '../../util/sanitize'; /* * Version: 5.3 (EOL Date: 15 Aug 2019) @@ -592,7 +593,7 @@ export async function ensureComment( topic: string | null, rawContent: string ) { - const content = hostRules.sanitize(rawContent); + const content = sanitize(rawContent); try { const comments = await getComments(prNo); let body: string; @@ -728,7 +729,7 @@ export async function createPr( _labels?: string[] | null, useDefaultBranch?: boolean ) { - const description = hostRules.sanitize(rawDescription); + const description = sanitize(rawDescription); logger.debug(`createPr(${branchName}, title=${title})`); const base = useDefaultBranch ? config.defaultBranch : config.baseBranch; let reviewers = []; @@ -883,7 +884,7 @@ export async function updatePr( title: string, rawDescription: string ) { - const description = hostRules.sanitize(rawDescription); + const description = sanitize(rawDescription); logger.debug(`updatePr(${prNo}, title=${title})`); try { diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index 7835ad4ed374d6c0016d3f5d2bfa3952073c511e..063d80bc70ac6ab73531e6f03fdcd7441e3b0c91 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -9,6 +9,7 @@ import { readOnlyIssueBody } from '../utils/read-only-issue-body'; import { appSlug } from '../../config/app-strings'; import * as comments from './comments'; import { PlatformConfig, RepoParams, RepoConfig } from '../common'; +import { sanitize } from '../../util/sanitize'; let config: utils.Config = {} as any; @@ -353,7 +354,7 @@ async function closeIssue(issueNumber: number) { export async function ensureIssue(title: string, body: string) { logger.debug(`ensureIssue()`); - const description = getPrBody(hostRules.sanitize(body)); + const description = getPrBody(sanitize(body)); /* istanbul ignore if */ if (!config.has_issues) { @@ -476,12 +477,7 @@ export function ensureComment( content: string ) { // https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment - return comments.ensureComment( - config, - prNo, - topic, - hostRules.sanitize(content) - ); + return comments.ensureComment(config, prNo, topic, sanitize(content)); } export function ensureCommentRemoval(prNo: number, topic: string) { @@ -536,7 +532,7 @@ export async function createPr( const body = { title, - description: hostRules.sanitize(description), + description: sanitize(description), source: { branch: { name: branchName, @@ -652,7 +648,7 @@ export async function updatePr( ) { logger.debug(`updatePr(${prNo}, ${title}, body)`); await api.put(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { - body: { title, description: hostRules.sanitize(description) }, + body: { title, description: sanitize(description) }, }); } diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index db2df6a0cf9ebd529b5d9dd86255b7b2c062bacc..3b9a7580f3d5b89694a1914a83bc612d0c25c45e 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -15,6 +15,7 @@ import { configFileNames, urls, } from '../../config/app-strings'; +import { sanitize } from '../../util/sanitize'; const defaultConfigFile = configFileNames[0]; @@ -835,7 +836,7 @@ export async function ensureIssue( reopen = true ) { logger.debug(`ensureIssue(${title})`); - const body = hostRules.sanitize(rawbody); + const body = sanitize(rawbody); try { const issueList = await getIssueList(); const issues = issueList.filter(i => i.title === title); @@ -1035,7 +1036,7 @@ export async function ensureComment( topic: string | null, rawContent: string ) { - const content = hostRules.sanitize(rawContent); + const content = sanitize(rawContent); try { const comments = await getComments(issueNo); let body: string; @@ -1187,7 +1188,7 @@ export async function createPr( useDefaultBranch: boolean, platformOptions: { statusCheckVerify?: boolean } = {} ) { - const body = hostRules.sanitize(rawBody); + const body = sanitize(rawBody); const base = useDefaultBranch ? config.defaultBranch : config.baseBranch; // Include the repository owner to handle forkMode and regular mode const head = `${config.repository!.split('/')[0]}:${branchName}`; @@ -1588,7 +1589,7 @@ export async function getPrFiles(prNo: number) { export async function updatePr(prNo: number, title: string, rawBody?: string) { logger.debug(`updatePr(${prNo}, ${title}, body)`); - const body = hostRules.sanitize(rawBody); + const body = sanitize(rawBody); const patchBody: any = { title }; if (body) { patchBody.body = body; diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts index e34ea3b7c02d14a23dfba382a13a9cd7e9460818..3e47de1557934467b94efc357e60bd4f047d3b91 100644 --- a/lib/platform/gitlab/index.ts +++ b/lib/platform/gitlab/index.ts @@ -7,6 +7,7 @@ import GitStorage from '../git/storage'; import { PlatformConfig, RepoParams, RepoConfig } from '../common'; import { configFileNames } from '../../config/app-strings'; import { logger } from '../../logger'; +import { sanitize } from '../../util/sanitize'; const defaultConfigFile = configFileNames[0]; let config: { @@ -453,7 +454,7 @@ export async function findIssue(title: string) { export async function ensureIssue(title: string, body: string) { logger.debug(`ensureIssue()`); - const description = getPrBody(hostRules.sanitize(body)); + const description = getPrBody(sanitize(body)); try { const issueList = await getIssueList(); const issue = issueList.find((i: { title: string }) => i.title === title); @@ -574,7 +575,7 @@ export async function ensureComment( topic: string | null | undefined, rawContent: string ) { - const content = hostRules.sanitize(rawContent); + const content = sanitize(rawContent); const massagedTopic = topic ? topic.replace(/Pull Request/g, 'Merge Request').replace(/PR/g, 'MR') : topic; @@ -692,7 +693,7 @@ export async function createPr( labels?: string[] | null, useDefaultBranch?: boolean ) { - const description = hostRules.sanitize(rawDescription); + const description = sanitize(rawDescription); const targetBranch = useDefaultBranch ? config.defaultBranch : config.baseBranch; @@ -800,7 +801,7 @@ export async function updatePr( await api.put(`projects/${config.repository}/merge_requests/${iid}`, { body: { title, - description: hostRules.sanitize(description), + description: sanitize(description), }, }); } diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts index 1f045a24e1efacf5a438b4f99db6023c8572cf6e..0e4704a448ae369f5e47a595d14454b2ec679df9 100644 --- a/lib/util/host-rules.ts +++ b/lib/util/host-rules.ts @@ -1,6 +1,7 @@ import URL from 'url'; import merge from 'deepmerge'; import { logger } from '../logger'; +import * as sanitize from './sanitize'; export interface HostRule { hostType?: string; @@ -15,8 +16,6 @@ export interface HostRule { timeout?: number; } -let secrets: string[] = []; - let hostRules: HostRule[] = []; export function add(params: HostRule) { @@ -33,14 +32,13 @@ export function add(params: HostRule) { const confidentialFields = ['password', 'token']; confidentialFields.forEach(field => { const secret = params[field]; - if (secret && secret.length > 3 && !secrets.includes(secret)) - secrets.push(secret); + if (secret && secret.length > 3) sanitize.add(secret); }); if (params.username && params.password) { const secret = Buffer.from( `${params.username}:${params.password}` ).toString('base64'); - if (!secrets.includes(secret)) secrets.push(secret); + sanitize.add(secret); } } @@ -166,18 +164,7 @@ export function hosts({ hostType }: { hostType: string }) { .filter(Boolean); } -export function sanitize(input: string) { - if (!input) return input; - let output: string = input; - secrets.forEach(secret => { - while (output.includes(secret)) { - output = output.replace(secret, '**redacted**'); - } - }); - return output; -} - export function clear() { hostRules = []; - secrets = []; + sanitize.clear(); } diff --git a/lib/util/sanitize.ts b/lib/util/sanitize.ts new file mode 100644 index 0000000000000000000000000000000000000000..98d6b737c1ea9185e1e986414d055f4a36c0aec9 --- /dev/null +++ b/lib/util/sanitize.ts @@ -0,0 +1,20 @@ +const secrets = new Set<string>(); + +export function sanitize(input: string) { + if (!input) return input; + let output: string = input; + secrets.forEach(secret => { + while (output.includes(secret)) { + output = output.replace(secret, '**redacted**'); + } + }); + return output; +} + +export function add(secret: string) { + secrets.add(secret); +} + +export function clear() { + secrets.clear(); +} diff --git a/test/platform/azure/index.spec.ts b/test/platform/azure/index.spec.ts index ce772c70ac66307237fede17d08f4ee7992ce75f..e93d28124d0a7fcfd519355a186925459d47a51f 100644 --- a/test/platform/azure/index.spec.ts +++ b/test/platform/azure/index.spec.ts @@ -20,7 +20,7 @@ describe('platform/azure', () => { jest.mock('../../../lib/platform/git/storage'); jest.mock('../../../lib/util/host-rules'); hostRules = require('../../../lib/util/host-rules'); - hostRules.sanitize = jest.fn(input => input); + require('../../../lib/util/sanitize').sanitize = jest.fn(input => input); azure = require('../../../lib/platform/azure'); azureApi = require('../../../lib/platform/azure/azure-got-wrapper'); azureHelper = require('../../../lib/platform/azure/azure-helper'); diff --git a/test/platform/bitbucket-server/index.spec.ts b/test/platform/bitbucket-server/index.spec.ts index abdeb193421d3cfe5b8f027347fc1dc455f9ce41..063944356a0707eab4516514cdc1e1a144fe955a 100644 --- a/test/platform/bitbucket-server/index.spec.ts +++ b/test/platform/bitbucket-server/index.spec.ts @@ -34,7 +34,6 @@ describe('platform/bitbucket-server', () => { jest.mock('../../../lib/platform/git/storage'); jest.mock('../../../lib/util/host-rules'); hostRules = require('../../../lib/util/host-rules'); - hostRules.sanitize = jest.fn(input => input); api = require('../../../lib/platform/bitbucket-server/bb-got-wrapper') .api; jest.spyOn(api, 'get'); diff --git a/test/platform/bitbucket/index.spec.ts b/test/platform/bitbucket/index.spec.ts index ce2bfbe8cb392f415508776db3b67c5ee96dbd48..a097eeaf568174c02be7563e9ecc61894d481282 100644 --- a/test/platform/bitbucket/index.spec.ts +++ b/test/platform/bitbucket/index.spec.ts @@ -17,7 +17,6 @@ describe('platform/bitbucket', () => { jest.mock('../../../lib/platform/git/storage'); jest.mock('../../../lib/util/host-rules'); hostRules = require('../../../lib/util/host-rules'); - hostRules.sanitize = jest.fn(input => input); api = require('../../../lib/platform/bitbucket/bb-got-wrapper').api; bitbucket = require('../../../lib/platform/bitbucket'); GitStorage = require('../../../lib/platform/git/storage').Storage; diff --git a/test/platform/github/index.spec.ts b/test/platform/github/index.spec.ts index e19d8149427cbef7f35581f13e4a37483a56b5a2..60bab0e5fbfc64394475a9787c897a4414707aef 100644 --- a/test/platform/github/index.spec.ts +++ b/test/platform/github/index.spec.ts @@ -18,7 +18,6 @@ describe('platform/github', () => { .api as any; github = await import('../../../lib/platform/github'); hostRules = (await import('../../../lib/util/host-rules')) as any; - hostRules.sanitize = jest.fn(input => input); jest.mock('../../../lib/platform/git/storage'); GitStorage = (await import('../../../lib/platform/git/storage')) .Storage as any; diff --git a/test/platform/gitlab/index.spec.ts b/test/platform/gitlab/index.spec.ts index 252daf41b2b7c7332ee3d90343684b249484b310..106d2f0bb4d39afc8acd2accc2128fc65cf84283 100644 --- a/test/platform/gitlab/index.spec.ts +++ b/test/platform/gitlab/index.spec.ts @@ -19,7 +19,6 @@ describe('platform/gitlab', () => { api = require('../../../lib/platform/gitlab/gl-got-wrapper').api; jest.mock('../../../lib/util/host-rules'); hostRules = require('../../../lib/util/host-rules'); - hostRules.sanitize = jest.fn(input => input); jest.mock('../../../lib/platform/git/storage'); GitStorage = require('../../../lib/platform/git/storage').Storage; GitStorage.mockImplementation(() => ({ diff --git a/test/util/__snapshots__/host-rules.spec.ts.snap b/test/util/__snapshots__/host-rules.spec.ts.snap index 4ed77aa00ea991f3e78c07c7c388e69ca0a51c80..2eb17cea817e76bdc7dd6a2dfc8f2aa12c9726ff 100644 --- a/test/util/__snapshots__/host-rules.spec.ts.snap +++ b/test/util/__snapshots__/host-rules.spec.ts.snap @@ -33,5 +33,3 @@ Array [ "my.local.registry", ] `; - -exports[`util/host-rules find() sanitizes secrets from strings 1`] = `"My token is **redacted**, username is \\"userabc\\" and password is \\"**redacted**\\" (hashed: **redacted**)"`; diff --git a/test/util/__snapshots__/sanitize.spec.ts.snap b/test/util/__snapshots__/sanitize.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..a2b1fff385f13cbf21e99bc945295a4d95359470 --- /dev/null +++ b/test/util/__snapshots__/sanitize.spec.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`util/sanitize sanitizes secrets from strings 1`] = `"My token is **redacted**, username is \\"userabc\\" and password is \\"**redacted**\\" (hashed: **redacted**)"`; diff --git a/test/util/host-rules.spec.ts b/test/util/host-rules.spec.ts index 3304b600263004f8bbbebde4bbbbb3f673d06f38..c3c47ffbe1bec54f3768439b76aeaad3259fd91b 100644 --- a/test/util/host-rules.spec.ts +++ b/test/util/host-rules.spec.ts @@ -1,4 +1,4 @@ -import { add, find, clear, hosts, sanitize } from '../../lib/util/host-rules'; +import { add, find, clear, hosts } from '../../lib/util/host-rules'; describe('util/host-rules', () => { beforeEach(() => { @@ -143,26 +143,5 @@ describe('util/host-rules', () => { expect(res).toMatchSnapshot(); expect(res).toHaveLength(2); }); - it('sanitizes empty string', () => { - expect(sanitize(null)).toEqual(null); - }); - it('sanitizes secrets from strings', () => { - const token = 'abc123token'; - const username = 'userabc'; - const password = 'password123'; - add({ - token, - }); - add({ - username, - password, - }); - const hashed = Buffer.from(`${username}:${password}`).toString('base64'); - expect( - sanitize( - `My token is ${token}, username is "${username}" and password is "${password}" (hashed: ${hashed})` - ) - ).toMatchSnapshot(); - }); }); }); diff --git a/test/util/sanitize.spec.ts b/test/util/sanitize.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..c6cd222c2c611cde83a306d46ade5efe9ce9ba58 --- /dev/null +++ b/test/util/sanitize.spec.ts @@ -0,0 +1,25 @@ +import { add, clear, sanitize } from '../../lib/util/sanitize'; + +describe('util/sanitize', () => { + beforeEach(() => { + clear(); + }); + + it('sanitizes empty string', () => { + expect(sanitize(null)).toEqual(null); + }); + it('sanitizes secrets from strings', () => { + const token = 'abc123token'; + const username = 'userabc'; + const password = 'password123'; + add(token); + const hashed = Buffer.from(`${username}:${password}`).toString('base64'); + add(hashed); + add(password); + expect( + sanitize( + `My token is ${token}, username is "${username}" and password is "${password}" (hashed: ${hashed})` + ) + ).toMatchSnapshot(); + }); +});