diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts
index ceb270426349876c24aaee7c47001480aa8a89a4..94a7bc142ca291232c29d289a9e451b955c901ce 100644
--- a/lib/platform/azure/index.ts
+++ b/lib/platform/azure/index.ts
@@ -353,7 +353,7 @@ export async function createPr(
   const targetRefName = azureHelper.getNewBranchName(
     useDefaultBranch ? config.defaultBranch : config.baseBranch
   );
-  const description = azureHelper.max4000Chars(body);
+  const description = azureHelper.max4000Chars(hostRules.sanitize(body));
   const azureApiGit = await azureApi.gitApi();
   const workItemRefs = [
     {
@@ -405,7 +405,9 @@ export async function updatePr(prNo: number, title: string, body?: string) {
     title,
   };
   if (body) {
-    objToUpdate.description = azureHelper.max4000Chars(body);
+    objToUpdate.description = azureHelper.max4000Chars(
+      hostRules.sanitize(body)
+    );
   }
   await azureApiGit.updatePullRequest(objToUpdate, config.repoId, prNo);
 }
@@ -416,7 +418,7 @@ export async function ensureComment(
   content: string
 ) {
   logger.debug(`ensureComment(${issueNo}, ${topic}, content)`);
-  const body = `### ${topic}\n\n${content}`;
+  const body = `### ${topic}\n\n${hostRules.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 8f15c9cca219e6a43c333f832464aced236a0da6..b0b5aa73cc3e9c6015e4af18f24bcc91511e3bbc 100644
--- a/lib/platform/bitbucket-server/index.ts
+++ b/lib/platform/bitbucket-server/index.ts
@@ -459,7 +459,6 @@ export /* istanbul ignore next */ function ensureIssue(
   title: string,
   body: string
 ) {
-  logger.debug(`ensureIssue(${title}, body={${body}})`);
   logger.warn({ title }, 'Cannot ensure issue');
   // TODO: Needs implementation
   // This is used by Renovate when creating its own issues, e.g. for deprecated package warnings, config error notifications, or "masterIssue"
@@ -591,8 +590,9 @@ async function deleteComment(prNo: number, commentId: number) {
 export async function ensureComment(
   prNo: number,
   topic: string | null,
-  content: string
+  rawContent: string
 ) {
+  const content = hostRules.sanitize(rawContent);
   try {
     const comments = await getComments(prNo);
     let body: string;
@@ -724,10 +724,11 @@ export async function findPr(
 export async function createPr(
   branchName: string,
   title: string,
-  description: string,
+  rawDescription: string,
   _labels?: string[] | null,
   useDefaultBranch?: boolean
 ) {
+  const description = hostRules.sanitize(rawDescription);
   logger.debug(`createPr(${branchName}, title=${title})`);
   const base = useDefaultBranch ? config.defaultBranch : config.baseBranch;
   let reviewers = [];
@@ -880,8 +881,9 @@ export async function getPrFiles(prNo: number) {
 export async function updatePr(
   prNo: number,
   title: string,
-  description: string
+  rawDescription: string
 ) {
+  const description = hostRules.sanitize(rawDescription);
   logger.debug(`updatePr(${prNo}, title=${title})`);
 
   try {
diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts
index fff83a933977ab0e32ec997d4581e9b9aac777bd..7835ad4ed374d6c0016d3f5d2bfa3952073c511e 100644
--- a/lib/platform/bitbucket/index.ts
+++ b/lib/platform/bitbucket/index.ts
@@ -353,12 +353,12 @@ async function closeIssue(issueNumber: number) {
 
 export async function ensureIssue(title: string, body: string) {
   logger.debug(`ensureIssue()`);
-  const description = getPrBody(body);
+  const description = getPrBody(hostRules.sanitize(body));
 
   /* istanbul ignore if */
   if (!config.has_issues) {
     logger.warn('Issues are disabled - cannot ensureIssue');
-    logger.info({ title, body }, 'Failed to ensure Issue');
+    logger.info({ title }, 'Failed to ensure Issue');
     return null;
   }
   try {
@@ -476,7 +476,12 @@ export function ensureComment(
   content: string
 ) {
   // https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment
-  return comments.ensureComment(config, prNo, topic, content);
+  return comments.ensureComment(
+    config,
+    prNo,
+    topic,
+    hostRules.sanitize(content)
+  );
 }
 
 export function ensureCommentRemoval(prNo: number, topic: string) {
@@ -531,7 +536,7 @@ export async function createPr(
 
   const body = {
     title,
-    description,
+    description: hostRules.sanitize(description),
     source: {
       branch: {
         name: branchName,
@@ -647,7 +652,7 @@ export async function updatePr(
 ) {
   logger.debug(`updatePr(${prNo}, ${title}, body)`);
   await api.put(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, {
-    body: { title, description },
+    body: { title, description: hostRules.sanitize(description) },
   });
 }
 
diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts
index f4ef1496960c03352e4dc70a63d49588ab35abc0..7e85b45ef0123f708b4b96d86ab9fe4ee5cb7dca 100644
--- a/lib/platform/github/index.ts
+++ b/lib/platform/github/index.ts
@@ -830,11 +830,12 @@ export async function findIssue(title: string) {
 
 export async function ensureIssue(
   title: string,
-  body: string,
+  rawbody: string,
   once = false,
   reopen = true
 ) {
-  logger.debug(`ensureIssue()`);
+  logger.debug(`ensureIssue(${title})`);
+  const body = hostRules.sanitize(rawbody);
   try {
     const issueList = await getIssueList();
     const issues = issueList.filter(i => i.title === title);
@@ -1032,8 +1033,9 @@ async function deleteComment(commentId: number) {
 export async function ensureComment(
   issueNo: number,
   topic: string | null,
-  content: string
+  rawContent: string
 ) {
+  const content = hostRules.sanitize(rawContent);
   try {
     const comments = await getComments(issueNo);
     let body: string;
@@ -1041,7 +1043,7 @@ export async function ensureComment(
     let commentNeedsUpdating = false;
     if (topic) {
       logger.debug(`Ensuring comment "${topic}" in #${issueNo}`);
-      body = `### ${topic}\n\n${content}`;
+      body = hostRules.sanitize(`### ${topic}\n\n${content}`);
       comments.forEach(comment => {
         if (comment.body.startsWith(`### ${topic}\n\n`)) {
           commentId = comment.id;
@@ -1180,11 +1182,12 @@ export async function findPr(
 export async function createPr(
   branchName: string,
   title: string,
-  body: string,
+  rawBody: string,
   labels: string[] | null,
   useDefaultBranch: boolean,
   platformOptions: { statusCheckVerify?: boolean } = {}
 ) {
+  const body = hostRules.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}`;
@@ -1583,8 +1586,9 @@ export async function getPrFiles(prNo: number) {
   return files.map((f: { filename: string }) => f.filename);
 }
 
-export async function updatePr(prNo: number, title: string, body?: string) {
+export async function updatePr(prNo: number, title: string, rawBody?: string) {
   logger.debug(`updatePr(${prNo}, ${title}, body)`);
+  const body = rawBody ? hostRules.sanitize(rawBody) : null;
   const patchBody: any = { title };
   if (body) {
     patchBody.body = body;
diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts
index 906043aa8d237405ceef34ae68e0d299532a01cb..e34ea3b7c02d14a23dfba382a13a9cd7e9460818 100644
--- a/lib/platform/gitlab/index.ts
+++ b/lib/platform/gitlab/index.ts
@@ -453,7 +453,7 @@ export async function findIssue(title: string) {
 
 export async function ensureIssue(title: string, body: string) {
   logger.debug(`ensureIssue()`);
-  const description = getPrBody(body);
+  const description = getPrBody(hostRules.sanitize(body));
   try {
     const issueList = await getIssueList();
     const issue = issueList.find((i: { title: string }) => i.title === title);
@@ -572,8 +572,9 @@ async function deleteComment(issueNo: number, commentId: number) {
 export async function ensureComment(
   issueNo: number,
   topic: string | null | undefined,
-  content: string
+  rawContent: string
 ) {
+  const content = hostRules.sanitize(rawContent);
   const massagedTopic = topic
     ? topic.replace(/Pull Request/g, 'Merge Request').replace(/PR/g, 'MR')
     : topic;
@@ -687,10 +688,11 @@ export async function findPr(
 export async function createPr(
   branchName: string,
   title: string,
-  description: string,
+  rawDescription: string,
   labels?: string[] | null,
   useDefaultBranch?: boolean
 ) {
+  const description = hostRules.sanitize(rawDescription);
   const targetBranch = useDefaultBranch
     ? config.defaultBranch
     : config.baseBranch;
@@ -798,7 +800,7 @@ export async function updatePr(
   await api.put(`projects/${config.repository}/merge_requests/${iid}`, {
     body: {
       title,
-      description,
+      description: hostRules.sanitize(description),
     },
   });
 }
diff --git a/lib/util/host-rules.ts b/lib/util/host-rules.ts
index beb26180d7eb34c8d8641d14108076217355886d..1f045a24e1efacf5a438b4f99db6023c8572cf6e 100644
--- a/lib/util/host-rules.ts
+++ b/lib/util/host-rules.ts
@@ -15,6 +15,8 @@ export interface HostRule {
   timeout?: number;
 }
 
+let secrets: string[] = [];
+
 let hostRules: HostRule[] = [];
 
 export function add(params: HostRule) {
@@ -28,6 +30,18 @@ export function add(params: HostRule) {
     throw new Error('hostRules cannot contain both a hostName and baseUrl');
   }
   hostRules.push(params);
+  const confidentialFields = ['password', 'token'];
+  confidentialFields.forEach(field => {
+    const secret = params[field];
+    if (secret && secret.length > 3 && !secrets.includes(secret))
+      secrets.push(secret);
+  });
+  if (params.username && params.password) {
+    const secret = Buffer.from(
+      `${params.username}:${params.password}`
+    ).toString('base64');
+    if (!secrets.includes(secret)) secrets.push(secret);
+  }
 }
 
 export interface HostRuleSearch {
@@ -152,6 +166,18 @@ 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 = [];
 }
diff --git a/lib/workers/branch/index.js b/lib/workers/branch/index.js
index 8dc43b522079e0f5aa5e929dc14ef8a3f3cef6f5..fcbfd0b491af25c529043f36580b789060e877d6 100644
--- a/lib/workers/branch/index.js
+++ b/lib/workers/branch/index.js
@@ -437,6 +437,11 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) {
           ' - you check the rebase/retry checkbox if found above, or\n';
         content +=
           ' - you rename this PR\'s title to start with "rebase!" to trigger it manually';
+        content += '\n\nThe artifact failure details are included below:\n\n';
+        config.artifactErrors.forEach(error => {
+          content += `##### File name: ${error.lockFile}\n\n`;
+          content += `\`\`\`\n${error.stderr}\n\`\`\`\n\n`;
+        });
         if (
           !(
             config.suppressNotifications.includes('artifactErrors') ||
diff --git a/test/platform/azure/index.spec.ts b/test/platform/azure/index.spec.ts
index 7c7ad7c8f1d9a31633aba9a5e015a582d4022ba7..ce772c70ac66307237fede17d08f4ee7992ce75f 100644
--- a/test/platform/azure/index.spec.ts
+++ b/test/platform/azure/index.spec.ts
@@ -20,6 +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);
     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 063944356a0707eab4516514cdc1e1a144fe955a..abdeb193421d3cfe5b8f027347fc1dc455f9ce41 100644
--- a/test/platform/bitbucket-server/index.spec.ts
+++ b/test/platform/bitbucket-server/index.spec.ts
@@ -34,6 +34,7 @@ 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 a097eeaf568174c02be7563e9ecc61894d481282..ce2bfbe8cb392f415508776db3b67c5ee96dbd48 100644
--- a/test/platform/bitbucket/index.spec.ts
+++ b/test/platform/bitbucket/index.spec.ts
@@ -17,6 +17,7 @@ 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 7d63b3dbf226dffb5ee34e424459a9f57ef661b8..14b9891cbd2a2a6742e4aee603a859419dbc18bd 100644
--- a/test/platform/github/index.spec.ts
+++ b/test/platform/github/index.spec.ts
@@ -17,6 +17,7 @@ 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 106d2f0bb4d39afc8acd2accc2128fc65cf84283..252daf41b2b7c7332ee3d90343684b249484b310 100644
--- a/test/platform/gitlab/index.spec.ts
+++ b/test/platform/gitlab/index.spec.ts
@@ -19,6 +19,7 @@ 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 2eb17cea817e76bdc7dd6a2dfc8f2aa12c9726ff..4ed77aa00ea991f3e78c07c7c388e69ca0a51c80 100644
--- a/test/util/__snapshots__/host-rules.spec.ts.snap
+++ b/test/util/__snapshots__/host-rules.spec.ts.snap
@@ -33,3 +33,5 @@ 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/host-rules.spec.ts b/test/util/host-rules.spec.ts
index c3c47ffbe1bec54f3768439b76aeaad3259fd91b..3304b600263004f8bbbebde4bbbbb3f673d06f38 100644
--- a/test/util/host-rules.spec.ts
+++ b/test/util/host-rules.spec.ts
@@ -1,4 +1,4 @@
-import { add, find, clear, hosts } from '../../lib/util/host-rules';
+import { add, find, clear, hosts, sanitize } from '../../lib/util/host-rules';
 
 describe('util/host-rules', () => {
   beforeEach(() => {
@@ -143,5 +143,26 @@ 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();
+    });
   });
 });