Compare commits

...
Author SHA1 Message Date
AbbanMustafa 80494c297f redact 2026-05-11 17:31:31 -04:00
AbbanMustafa 9052e3e446 redact 2026-05-11 16:20:31 -04:00
AbbanMustafa ec6772317a redact 2026-05-11 15:07:25 -04:00
3 changed files with 50 additions and 13 deletions

No files matched your search

+2 -12
View File
@@ -4,7 +4,7 @@ import { LoggerLevel, bunyan, createLogger } from '@expo/logger';
import { Readable, Transform, TransformCallback, Writable } from 'stream';
import config from './config';
import { maybeStringBase64Decode, simpleSecretsWhitelist } from './secrets';
import { redactSecrets } from './secrets';
import { uuidv7 } from 'uuidv7';
import HttpLogStream from './utils/HttpLogStream';
@@ -58,12 +58,6 @@ export class WorkerLogBuffer extends Writable implements LogBuffer {
}
function createTransformStream(secrets: EnvironmentSecret[]): Transform {
const secretValues = secrets.map(({ value }) => value);
const secretList: string[] = [
...secretValues,
...secretValues.map(maybeStringBase64Decode).filter((i): i is string => !!i),
].filter(i => i.length > 1 && !simpleSecretsWhitelist.includes(i));
return new Transform({
readableObjectMode: true,
writableObjectMode: true,
@@ -80,13 +74,9 @@ function createTransformStream(secrets: EnvironmentSecret[]): Transform {
}
if (chunk && typeof chunk === 'object' && chunk.msg) {
const msgWithoutSecrets = secretList.reduce((acc: string, pattern: string): string => {
return acc.replaceAll(pattern, '*'.repeat(pattern.length));
}, chunk.msg as string);
callback(null, {
...chunk,
msg: msgWithoutSecrets,
msg: redactSecrets(chunk.msg as string, secrets),
});
} else {
callback(null, chunk);
+22
View File
@@ -1,3 +1,5 @@
import { EnvironmentSecret } from '@expo/eas-build-job';
export function maybeStringBase64Decode(maybeEncodedString: string): string | null {
try {
const buffer = Buffer.from(maybeEncodedString, 'base64');
@@ -10,6 +12,26 @@ export function maybeStringBase64Decode(maybeEncodedString: string): string | nu
}
}
export function redactSecrets(
message: string,
secrets: EnvironmentSecret[],
additionalSecrets?: string[]
): string {
const secretValues = [
...secrets.map(({ value }) => value),
...(additionalSecrets ?? []),
];
const secretList = [
...secretValues,
...secretValues.map(maybeStringBase64Decode).filter((i): i is string => !!i),
].filter((i) => i.length > 1 && !simpleSecretsWhitelist.includes(i));
return secretList.reduce(
(acc, pattern) => acc.replaceAll(pattern, '*'.repeat(pattern.length)),
message
);
}
export const simpleSecretsWhitelist = [
'production',
'prod',
+26 -1
View File
@@ -31,6 +31,7 @@ import { createBuildContext } from './context';
import { Analytics } from './external/analytics';
import { LauncherMessage, Worker, WorkerMessage } from './external/turtle';
import logger, { createBuildLoggerWithSecretsFilter } from './logger';
import { redactSecrets } from './secrets';
import sentry from './sentry';
import State from './state';
import { WebSocketServer } from './utils/WebSocketServer';
@@ -353,13 +354,37 @@ export default class BuildService {
}
try {
const additionalSecrets: string[] = [];
if (robotAccessToken) {
additionalSecrets.push(robotAccessToken);
}
const secrets = job.secrets as any;
const keystore = secrets?.buildCredentials?.keystore;
additionalSecrets.push(
keystore?.dataBase64,
keystore?.keystorePassword,
keystore?.keyPassword,
);
for (const targetCreds of Object.values(secrets?.buildCredentials ?? {})) {
const creds = targetCreds as any;
additionalSecrets.push(
creds?.provisioningProfileBase64,
creds?.distributionCertificate?.dataBase64,
creds?.distributionCertificate?.password,
);
}
const redactedErrorMessage = redactSecrets(
rawErrorMessage,
job.secrets?.environmentSecrets ?? [],
additionalSecrets.filter((s): s is string => typeof s === 'string')
);
await turtleFetch(
new URL('turtle-builds/logs', config.wwwApiV2BaseUrl).toString(),
'POST',
{
json: {
buildId: this.buildId,
message: rawErrorMessage,
message: redactedErrorMessage,
level: 'error',
tags: {
build_phase: err.buildPhase ?? null,