Skip to content

Commit

Permalink
Merge pull request #1055 from gemini-testing/users/kroman512/unhandle…
Browse files Browse the repository at this point in the history
…d_rejection_remove_tokens

fix: remove tokens in unhandled rejection log
  • Loading branch information
KuznetsovRoman authored Jan 30, 2025
2 parents a1324da + e1dba75 commit 6b55940
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 7 deletions.
8 changes: 4 additions & 4 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import path from "node:path";
import util from "node:util";
import { Command } from "@gemini-testing/commander";

import defaults from "../config/defaults";
Expand All @@ -8,6 +7,7 @@ import { Testplane } from "../testplane";
import pkg from "../../package.json";
import logger from "../utils/logger";
import { shouldIgnoreUnhandledRejection } from "../utils/errors";
import { utilInspectSafe } from "../utils/secret-replacer";
import { withCommonCliOptions, collectCliValues, handleRequires } from "../utils/cli";
import { CliCommands } from "./constants";

Expand All @@ -16,7 +16,7 @@ export type TestplaneRunOpts = { cliName?: string };
let testplane: Testplane;

process.on("uncaughtException", err => {
logger.error(util.inspect(err));
logger.error(utilInspectSafe(err));
process.exit(1);
});

Expand All @@ -28,8 +28,8 @@ process.on("unhandledRejection", (reason, p) => {

const error = [
`Unhandled Rejection in testplane:master:${process.pid}:`,
`Promise: ${util.inspect(p)}`,
`Reason: ${util.inspect(reason)}`,
`Promise: ${utilInspectSafe(p)}`,
`Reason: ${utilInspectSafe(reason)}`,
].join("\n");

if (testplane) {
Expand Down
6 changes: 3 additions & 3 deletions src/utils/processor.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
"use strict";

const _ = require("lodash");
const util = require("util");
const { WORKER_UNHANDLED_REJECTION } = require("../constants/process-messages");
const logger = require("./logger");
const ipc = require("./ipc");
const { shouldIgnoreUnhandledRejection } = require("./errors");
const { utilInspectSafe } = require("./secret-replacer");

process.on("unhandledRejection", (reason, p) => {
if (shouldIgnoreUnhandledRejection(reason)) {
Expand All @@ -15,8 +15,8 @@ process.on("unhandledRejection", (reason, p) => {

const error = [
`Unhandled Rejection in testplane:worker:${process.pid}:`,
`Promise: ${util.inspect(p)}`,
`Reason: ${util.inspect(reason)}`,
`Promise: ${utilInspectSafe(p)}`,
`Reason: ${utilInspectSafe(reason)}`,
].join("\n");

ipc.emit(WORKER_UNHANDLED_REJECTION, { error });
Expand Down
27 changes: 27 additions & 0 deletions src/utils/secret-replacer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { inspect } from "node:util";

const secretPatterns = {
BEARER_TOKEN: /Bearer [A-Za-z0-9-._~+/]{30,}/gi,
OAUTH_KEY: /OAuth [A-Za-z0-9-._~+/]{30,}/gi,
OAUTH_TOKEN: /oauth_token=[A-Za-z0-9-._~+/]{30,}/g,
OAUTH_ACCESS_TOKEN: /access_token=[A-Za-z0-9-._~+/]{30,}/g,
JWT_TOKEN: /ey[A-Za-z0-9=_-]+\.[A-Za-z0-9=_-]+\.[A-Za-z0-9=_-]*/g,
AWS_ACCESS_KEY: /AKIA[A-Z0-9]{16}/g,
GOOGLE_CLOUD_SECRET_KEY: /AIza[a-zA-Z0-9]{35}/g,
STRIPE_LIVE_API_KEY: /sk_live_[a-zA-Z0-9]{24}/g,
STRIPE_TEST_API_KEY: /sk_test_[a-zA-Z0-9]{24}/g,
GITHUB_PAGES_ACCESS_TOKEN: /ghp_[a-zA-Z0-9]{36}/g,
SLACK_API_TOKEN: /xox[baprs]-[a-zA-Z0-9]{12,}/g,
REFRESH_TOKEN: /refresh_token_[a-zA-Z0-9-_]{32,}/g,
SESSION_ID: /sess_[a-zA-Z0-9-_]{22,}/g,
} as const;

export const hideSecrets = (source: string): string => {
return Object.keys(secretPatterns).reduce((result, patternName) => {
const pattern = secretPatterns[patternName as keyof typeof secretPatterns];

return result.replaceAll(pattern, `<${patternName}>`);
}, source);
};

export const utilInspectSafe = <T>(obj: T): string => hideSecrets(inspect(obj));
125 changes: 125 additions & 0 deletions test/src/utils/secret-replacer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import util from "node:util";
import { utilInspectSafe } from "../../../src/utils/secret-replacer";

describe("utilInspectSafe", () => {
describe("String Inputs", () => {
it("should replace OAuth Key patterns", () => {
const input = "OAuth abcdefghijklmnopqrstuvwxyz012345";

const result = utilInspectSafe(input);

assert.equal(result, util.inspect("<OAUTH_KEY>"));
});

it("should replace multiple patterns in a single string", () => {
const input = "Bearer abcdefghijklmnopqrstuvwxyz012345 OAuth xyz0123456789abcdefghijklmnopqrstuvwxyz012";

const result = utilInspectSafe(input);

assert.equal(result, util.inspect("<BEARER_TOKEN> <OAUTH_KEY>"));
});
});

describe("Object Inputs", () => {
it("should recursively replace secrets in nested objects", () => {
const input = {
token: "Bearer abcdefghijklmnopqrstuvwxyz012345",
user: {
apiKey: "sk_live_abcdefghijklmnopqrstuvwx",
metadata: {
refreshToken: "refresh_token_abcdefghijklmnopqrstuvwxyz012345",
},
},
};

const result = utilInspectSafe(input);

assert.deepEqual(
result,
util.inspect({
token: "<BEARER_TOKEN>",
user: {
apiKey: "<STRIPE_LIVE_API_KEY>",
metadata: {
refreshToken: "<REFRESH_TOKEN>",
},
},
}),
);
});

it("should handle objects with mixed data types", () => {
const input = {
id: 123,
secret: "AKIAIOSFODNN7EXAMPLE",
isActive: true,
details: {
token: "Bearer abcdefghijklmnopqrstuvwxyz012345",
},
};

const result = utilInspectSafe(input);

assert.deepEqual(
result,
util.inspect({
id: 123,
secret: "<AWS_ACCESS_KEY>",
isActive: true,
details: {
token: "<BEARER_TOKEN>",
},
}),
);
});
});

describe("Array Inputs", () => {
it("should replace secrets in an array of strings", () => {
const input = ["Bearer abcdefghijklmnopqrstuvwxyz012345", "OAuth xyz0123456789abcdefghijklmnopqrstuvwxyz"];

const result = utilInspectSafe(input);

assert.deepEqual(
result.replaceAll(/\s/g, ""),
util.inspect(["<BEARER_TOKEN>", "<OAUTH_KEY>"]).replaceAll(/\s/g, ""),
);
});

it("should recursively replace secrets in an array of objects", () => {
const input = [
{ token: "Bearer abcdefghijklmnopqrstuvwxyz012345" },
{ apiKey: "sk_live_abcdefghijklmnopqrstuvwx" },
];

const result = utilInspectSafe(input);

assert.deepEqual(
result.replaceAll(/\s/g, ""),
util.inspect([{ token: "<BEARER_TOKEN>" }, { apiKey: "<STRIPE_LIVE_API_KEY>" }]).replaceAll(/\s/g, ""),
);
});
});

describe("Edge Cases", () => {
it("should return null for null input", () => {
const input = null;

assert.equal(utilInspectSafe(input), util.inspect(input));
});

it("should return undefined for undefined input", () => {
const input = undefined;

assert.equal(utilInspectSafe(input), util.inspect(input));
});

it("should handle empty strings and objects", () => {
const input1 = "";
const input2 = {};

assert.equal(utilInspectSafe(input1), util.inspect(input1));
assert.equal(utilInspectSafe(input2), util.inspect(input2));
});
});
});

0 comments on commit 6b55940

Please sign in to comment.