@@ -13,6 +13,30 @@ import type { ScoutTestFailureExtended } from './get_scout_failures';
1313import type { GithubApi } from './github_api' ;
1414import { getIssueMetadata , updateIssueMetadata } from './issue_metadata' ;
1515
16+ function redactHostnameSuffix ( text : string , suffix : string ) : string {
17+ const escaped = suffix . replace ( / \. / g, '\\.' ) ;
18+ return text . replace (
19+ new RegExp (
20+ `(?:https?:\\/\\/)?[a-zA-Z0-9](?:[a-zA-Z0-9.-]*[a-zA-Z0-9])?\\.${ escaped } (?:[^\\s]*)?` ,
21+ 'g'
22+ ) ,
23+ ( ) => `<redacted>.${ suffix } `
24+ ) ;
25+ }
26+
27+ const REDACT_HOST_SUFFIXES = [ 'found.no' , 'elastic.co' , 'qa.elastic.cloud' ] as const ;
28+
29+ /**
30+ * Redacts emails and sensitive hostnames (e.g. *.found.no, *.elastic.co, *.qa.elastic.cloud) from text posted to public GitHub issues.
31+ */
32+ export function redactSensitiveGithubFailureText ( text : string ) : string {
33+ let out = text . replace ( / \b c o n s o l e \. q a \. c l d \. e l s t c \. c o \b / g, '<redacted>' ) ;
34+ for ( const suffix of REDACT_HOST_SUFFIXES ) {
35+ out = redactHostnameSuffix ( out , suffix ) ;
36+ }
37+ return out . replace ( / \S + @ e l a s t i c \. c o \b / g, '<redacted>@elastic.co' ) ;
38+ }
39+
1640function isScoutFailure ( failure : TestFailure ) : failure is ScoutTestFailureExtended {
1741 return 'id' in failure && 'target' in failure && 'location' in failure ;
1842}
@@ -52,7 +76,7 @@ function createFTRBody(
5276 branch : string ,
5377 pipeline : string
5478) : string {
55- const failureBody = truncateFailureBody ( failure . failure ) ;
79+ const failureBody = redactSensitiveGithubFailureText ( truncateFailureBody ( failure . failure ) ) ;
5680
5781 const bodyContent = [
5882 'A test failed on a tracked branch' ,
@@ -89,7 +113,7 @@ function createScoutBody(
89113 branch : string ,
90114 pipeline : string
91115) : string {
92- const failureBody = truncateFailureBody ( failure . failure ) ;
116+ const failureBody = redactSensitiveGithubFailureText ( truncateFailureBody ( failure . failure ) ) ;
93117
94118 // Create table format for Scout test details
95119 const scoutDetailsTable = [
@@ -236,7 +260,9 @@ function createScoutComment(
236260 * ```
237261 */
238262
239- return `${ base } \n\nNew error message:\n\`\`\`\n${ newErrorMessage } \n\`\`\`` ;
263+ return `${ base } \n\nNew error message:\n\`\`\`\n${ redactSensitiveGithubFailureText (
264+ newErrorMessage
265+ ) } \n\`\`\``;
240266}
241267
242268async function updateFTRFailureIssue (
@@ -278,8 +304,13 @@ async function updateScoutFailureIssue(
278304 let newErrorMessage : string | undefined ;
279305 if ( failure . errorMessage && previousFailureBody ) {
280306 const currentErrorMsg = truncateFailureBody ( failure . errorMessage ) . trim ( ) ;
281- if ( ! previousFailureBody . includes ( currentErrorMsg ) ) {
282- newErrorMessage = currentErrorMsg ;
307+ // Current error.message from CI is raw. The issue's first code block is usually already
308+ // redacted (we redact on create), but older issues may still hold raw text. Redacting
309+ // previous again is idempotent.
310+ const redactedPrevious = redactSensitiveGithubFailureText ( previousFailureBody ) ;
311+ const redactedCurrent = redactSensitiveGithubFailureText ( currentErrorMsg ) ;
312+ if ( ! redactedPrevious . includes ( redactedCurrent ) ) {
313+ newErrorMessage = redactedCurrent ;
283314 }
284315 }
285316
0 commit comments