diff --git a/module/s3-scan-object/app.js b/module/s3-scan-object/app.js index c39b3a14..6a9a32c0 100644 --- a/module/s3-scan-object/app.js +++ b/module/s3-scan-object/app.js @@ -7,7 +7,9 @@ */ const axios = require("axios"); +const pino = require("pino"); const util = require("util"); +const { lambdaRequestTracker, pinoLambdaDestination } = require("pino-lambda"); const { S3Client, PutObjectTaggingCommand } = require("@aws-sdk/client-s3"); const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const { STSClient, AssumeRoleCommand } = require("@aws-sdk/client-sts"); @@ -15,6 +17,7 @@ const { STSClient, AssumeRoleCommand } = require("@aws-sdk/client-sts"); const AWS_ROLE_TO_ASSUME = process.env.AWS_ROLE_TO_ASSUME ? process.env.AWS_ROLE_TO_ASSUME : "ScanFilesGetObjects"; const REGION = process.env.REGION; const ENDPOINT_URL = process.env.AWS_SAM_LOCAL ? "http://host.docker.internal:3001" : undefined; +const LOGGING_LEVEL = process.env.LOGGING_LEVEL ? process.env.LOGGING_LEVEL : "info"; const SCAN_FILES_URL = process.env.SCAN_FILES_URL; const SCAN_FILES_API_KEY_SECRET_ARN = process.env.SCAN_FILES_API_KEY_SECRET_ARN; const SCAN_IN_PROGRESS = "in_progress"; @@ -27,6 +30,16 @@ const EVENT_SNS = "aws:sns"; const stsClient = new STSClient({ region: REGION, endpoint: ENDPOINT_URL }); const secretsManagerClient = new SecretsManagerClient({ region: REGION, endpoint: ENDPOINT_URL }); +// Setup logging and add a custom requestId attribute to all log messages +const logger = pino({ level: LOGGING_LEVEL }, pinoLambdaDestination()); +const withRequest = lambdaRequestTracker({ + requestMixin: (event) => { + return { + requestId: event.RequestId ? event.RequestId : undefined, + }; + }, +}); + /** * Performs function initialization outside of the Lambda handler so that * it only occurs once per cold start of the function rather than on @@ -42,7 +55,7 @@ const initConfig = async () => { const response = await secretsManagerClient.send(command); return { apiKey: response.SecretString }; } catch (error) { - console.error(`Unable to get '${SCAN_FILES_API_KEY_SECRET_ARN}' secret: ${error}`); + logger.error(`Unable to get '${SCAN_FILES_API_KEY_SECRET_ARN}' secret: ${error}`); throw error; } })(); @@ -58,7 +71,9 @@ const configPromise = initConfig(); * is received with an update scan status. * @param {Object} event Lambda invocation event */ -exports.handler = async (event) => { +exports.handler = async (event, context) => { + withRequest(event, context); + const config = await configPromise; const s3Clients = {}; let errorCount = 0; @@ -80,6 +95,9 @@ exports.handler = async (event) => { sns: "request-id", }); + // Make sure SNS events have a top-level RequestId attribute for logging + event.RequestId = requestId || event.RequestId; + // Do not scan S3 folder objects if (isS3Folder(s3Object)) { continue; @@ -104,7 +122,7 @@ exports.handler = async (event) => { scanStatus = record.Sns.MessageAttributes["av-status"].Value; } } else { - console.error(`[${requestId}] Unsupported event record: ${util.inspect(record)}`); + logger.error(`Unsupported event record: ${util.inspect(record)}`); } // Tag the S3 object if we've got a scan status @@ -124,8 +142,8 @@ exports.handler = async (event) => { } roleArn = `arn:aws:iam::${awsAccountId}:role/${AWS_ROLE_TO_ASSUME}`; - s3Clients[awsAccountId] = await getS3Client(s3Clients[awsAccountId], stsClient, roleArn, requestId); - isObjectTagged = await tagS3Object(s3Clients[awsAccountId], s3Object, tags, requestId); + s3Clients[awsAccountId] = await getS3Client(s3Clients[awsAccountId], stsClient, roleArn); + isObjectTagged = await tagS3Object(s3Clients[awsAccountId], s3Object, tags); } // Track if there were any errors processing this record @@ -176,10 +194,9 @@ const getRecordEventSource = (record) => { * be used by other SDK clients. * @param {STSClient} stsClient AWS SDK STS client used to assume the role * @param {string} roleArn ARN of the role to assume - * @param {string} requestId Request ID of the scan * @returns Credentials object for the role */ -const getRoleCredentials = async (stsClient, roleArn, requestId) => { +const getRoleCredentials = async (stsClient, roleArn) => { let credentials = null; try { @@ -192,7 +209,7 @@ const getRoleCredentials = async (stsClient, roleArn, requestId) => { sessionToken: response.Credentials.SessionToken, }; } catch (error) { - console.error(`[${requestId}] Failed to assume role ${roleArn}: ${error}`); + logger.error(`Failed to assume role ${roleArn}: ${error}`); } return credentials; @@ -204,12 +221,11 @@ const getRoleCredentials = async (stsClient, roleArn, requestId) => { * @param {S3Client} s3Client Initialized S3 client or null * @param {STSClient} stsClient STS client used to assume the role * @param {string} roleArn ARN of the role to assume to get tempoary credentials - * @param {string} requestId Request ID of the scan * @returns S3 client */ -const getS3Client = async (s3Client, stsClient, roleArn, requestId) => { +const getS3Client = async (s3Client, stsClient, roleArn) => { if (!s3Client) { - const credentials = await getRoleCredentials(stsClient, roleArn, requestId); + const credentials = await getRoleCredentials(stsClient, roleArn); return new S3Client({ region: REGION, endpoint: ENDPOINT_URL, @@ -301,10 +317,10 @@ const startS3ObjectScan = async (apiEndpoint, apiKey, s3Object, awsAccountId, sn }, } ); - console.info(`[${requestId}] Scan response ${response.status}: ${util.inspect(response.data)}`); + logger.info(`Scan response ${response.status}: ${util.inspect(response.data)}`); return response; } catch (error) { - console.error(`[${requestId}] Could not start scan for ${util.inspect(s3Object)}: ${util.inspect(error.response)}`); + logger.error(`Could not start scan for ${util.inspect(s3Object)}: ${util.inspect(error.response)}`); return error.response; } }; @@ -314,9 +330,8 @@ const startS3ObjectScan = async (apiEndpoint, apiKey, s3Object, awsAccountId, sn * @param {S3Client} s3Client AWS SDK S3 client used to tag the object * @param {{Bucket: string, Key: string}} s3Object S3 object to tag * @param {Array<{Key: string, Value: string}>} tags Array of Key/Value pairs to tag the S3 object with - * @param {string} requestId Request ID of the scan */ -const tagS3Object = async (s3Client, s3Object, tags, requestId) => { +const tagS3Object = async (s3Client, s3Object, tags) => { const tagging = { Tagging: { TagSet: tags, @@ -329,7 +344,7 @@ const tagS3Object = async (s3Client, s3Object, tags, requestId) => { const response = await s3Client.send(command); isSuccess = response.VersionId !== undefined; } catch (error) { - console.error(`[${requestId}] Failed to tag S3 object: ${error}`); + logger.error(`Failed to tag S3 object: ${error}`); } return isSuccess; diff --git a/module/s3-scan-object/app.test.js b/module/s3-scan-object/app.test.js index 0cd373fa..6415b71a 100644 --- a/module/s3-scan-object/app.test.js +++ b/module/s3-scan-object/app.test.js @@ -28,11 +28,6 @@ const { } = helpers; jest.mock("axios"); -global.console = { - ...console, - error: jest.fn(), - info: jest.fn(), -}; const TEST_TIME = new Date(1978, 3, 30).getTime(); beforeAll(() => { @@ -70,6 +65,7 @@ describe("handler", () => { EventSource: "aws:sns", Sns: { MessageAttributes: { + "request-id": { Value: "zxcv9584" }, "av-filepath": { Value: "s3://bam/baz" }, "av-status": { Value: "SPIFY" }, "av-checksum": { Value: "42" }, @@ -104,7 +100,7 @@ describe("handler", () => { mockS3Client.on(PutObjectTaggingCommand).resolves({ VersionId: "yeet" }); mockSTSClient.on(AssumeRoleCommand).resolves({ Credentials: {} }); - const response = await handler(event); + const response = await handler(event, {}); expect(response).toEqual(expectedResponse); expect(mockS3Client).toHaveReceivedNthCommandWith(1, PutObjectTaggingCommand, { Bucket: "foo", @@ -127,6 +123,7 @@ describe("handler", () => { { Key: "av-status", Value: "SPIFY" }, { Key: "av-timestamp", Value: TEST_TIME }, { Key: "av-checksum", Value: "42" }, + { Key: "request-id", Value: "zxcv9584" }, ], }, }); @@ -138,7 +135,7 @@ describe("handler", () => { { Key: "av-scanner", Value: "clamav" }, { Key: "av-status", Value: "in_progress" }, { Key: "av-timestamp", Value: TEST_TIME }, - { Key: "request-id", Value: "1234asdf" }, + { Key: "request-id", Value: "zxcv9584" }, ], }, }); @@ -187,7 +184,7 @@ describe("handler", () => { mockS3Client.on(PutObjectTaggingCommand).resolves({ VersionId: "yeet" }); mockSTSClient.on(AssumeRoleCommand).resolves({ Credentials: {} }); - const response = await handler(event); + const response = await handler(event, {}); expect(response).toEqual(expectedResponse); expect(mockS3Client).toHaveReceivedNthCommandWith(1, PutObjectTaggingCommand, { Bucket: "foo", @@ -230,7 +227,7 @@ describe("handler", () => { mockS3Client.on(PutObjectTaggingCommand).resolves({ VersionId: "yeet" }); mockSTSClient.on(AssumeRoleCommand).resolves({ Credentials: {} }); - const response = await handler(event); + const response = await handler(event, {}); expect(response).toEqual(expectedResponse); expect(mockS3Client).toHaveReceivedNthCommandWith(1, PutObjectTaggingCommand, { Bucket: "foo", @@ -266,7 +263,7 @@ describe("handler", () => { axios.post.mockResolvedValue({ status: 200 }); mockS3Client.on(PutObjectTaggingCommand).resolves({ VersionId: "yeet" }); - const response = await handler(event); + const response = await handler(event, {}); expect(response).toEqual(expectedResponse); }); }); @@ -331,7 +328,7 @@ describe("getRoleCredentials", () => { }, }; mockSTSClient.on(AssumeRoleCommand).resolves(response); - const credentials = await getRoleCredentials(mockSTSClient, "foo", "123"); + const credentials = await getRoleCredentials(mockSTSClient, "foo"); expect(credentials).toEqual({ accessKeyId: "why", @@ -346,7 +343,7 @@ describe("getRoleCredentials", () => { test("fails to assume role", async () => { mockSTSClient.on(AssumeRoleCommand).rejects(new Error("nope")); - const credentials = await getRoleCredentials(mockSTSClient, "foo", "123"); + const credentials = await getRoleCredentials(mockSTSClient, "foo"); expect(credentials).toBe(null); }); }); @@ -354,7 +351,7 @@ describe("getRoleCredentials", () => { describe("getS3Client", () => { test("successfully gets new client", async () => { mockSTSClient.on(AssumeRoleCommand).resolves({ Credentials: { foo: "bar" } }); - const s3Client = await getS3Client(null, mockSTSClient, "bar", "bam"); + const s3Client = await getS3Client(null, mockSTSClient, "bar"); expect(s3Client).toBeInstanceOf(S3Client); expect(mockSTSClient).toHaveReceivedNthCommandWith(1, AssumeRoleCommand, { RoleArn: "bar", @@ -363,7 +360,7 @@ describe("getS3Client", () => { }); test("successfully returns cached client", async () => { - const s3Client = await getS3Client("mellow", mockSTSClient, "bar", "baz"); + const s3Client = await getS3Client("mellow", mockSTSClient, "bar"); expect(s3Client).toBe("mellow"); expect(mockSTSClient.calls().length).toBe(0); }); @@ -543,19 +540,16 @@ describe("tagS3Object", () => { TagSet: [{ Key: "some-tag", Value: "some-value" }], }, }; - const response = await tagS3Object( - mockS3Client, - { Bucket: "foo", Key: "bar" }, - [{ Key: "some-tag", Value: "some-value" }], - "foo" - ); + const response = await tagS3Object(mockS3Client, { Bucket: "foo", Key: "bar" }, [ + { Key: "some-tag", Value: "some-value" }, + ]); expect(response).toBe(true); expect(mockS3Client).toHaveReceivedCommandWith(PutObjectTaggingCommand, input); }); test("fails to tag", async () => { mockS3Client.on(PutObjectTaggingCommand).resolvesOnce({}); - const response = await tagS3Object(mockS3Client, {}, [], null); + const response = await tagS3Object(mockS3Client, {}, []); expect(response).toBe(false); }); }); diff --git a/module/s3-scan-object/package.json b/module/s3-scan-object/package.json index 0c74e200..97658a1f 100644 --- a/module/s3-scan-object/package.json +++ b/module/s3-scan-object/package.json @@ -13,7 +13,9 @@ "@aws-sdk/client-s3": "^3.113.0", "@aws-sdk/client-secrets-manager": "^3.118.1", "@aws-sdk/client-sts": "^3.121.0", - "axios": "^0.27.2" + "axios": "^0.27.2", + "pino": "7.11.0", + "pino-lambda": "^4.0.0" }, "devDependencies": { "aws-sdk-client-mock": "1.0.0", diff --git a/module/s3-scan-object/yarn.lock b/module/s3-scan-object/yarn.lock index 9bd9e866..9b0b23f4 100644 --- a/module/s3-scan-object/yarn.lock +++ b/module/s3-scan-object/yarn.lock @@ -2269,6 +2269,11 @@ asynckit@^0.4.0: resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q== +atomic-sleep@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" + integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== + aws-sdk-client-mock@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/aws-sdk-client-mock/-/aws-sdk-client-mock-1.0.0.tgz" @@ -2566,6 +2571,16 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +duplexify@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-4.1.2.tgz#18b4f8d28289132fa0b9573c898d9f903f81c7b0" + integrity sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw== + dependencies: + end-of-stream "^1.4.1" + inherits "^2.0.3" + readable-stream "^3.1.1" + stream-shift "^1.0.0" + electron-to-chromium@^1.4.147: version "1.4.154" resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.154.tgz" @@ -2581,6 +2596,13 @@ emoji-regex@^8.0.0: resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +end-of-stream@^1.4.1: + version "1.4.4" + resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" + integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== + dependencies: + once "^1.4.0" + entities@2.2.0: version "2.2.0" resolved "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz" @@ -2763,6 +2785,11 @@ fast-levenshtein@^2.0.6: resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz" integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw== +fast-redact@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.1.1.tgz#790fcff8f808c2e12fabbfb2be5cb2deda448fa0" + integrity sha512-odVmjC8x8jNeMZ3C+rPMESzXVSEU8tSWSHv9HFxP2mm89G/1WwqhrerJDQm9Zus8X6aoRgQDThKqptdNA6bt+A== + fast-xml-parser@3.19.0: version "3.19.0" resolved "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-3.19.0.tgz" @@ -2961,7 +2988,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2: +inherits@2, inherits@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3611,7 +3638,12 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -once@^1.3.0: +on-exit-leak-free@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz#b39c9e3bf7690d890f4861558b0d7b90a442d209" + integrity sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg== + +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz" integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== @@ -3710,6 +3742,41 @@ picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1: resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +pino-abstract-transport@v0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz#4b54348d8f73713bfd14e3dc44228739aa13d9c0" + integrity sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ== + dependencies: + duplexify "^4.1.2" + split2 "^4.0.0" + +pino-lambda@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pino-lambda/-/pino-lambda-4.0.0.tgz#6d140afc7f94ddb5186446d66e9ba384500d28c2" + integrity sha512-R/OpX1m+2xGVgrsQmXYJxXtjx0gWYdURULAms+eKrYpj6sZw7O3t30L007Nb4DkPhwCDmttob3XNm5cWLEyV8w== + +pino-std-serializers@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz#1791ccd2539c091ae49ce9993205e2cd5dbba1e2" + integrity sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q== + +pino@7.11.0: + version "7.11.0" + resolved "https://registry.yarnpkg.com/pino/-/pino-7.11.0.tgz#0f0ea5c4683dc91388081d44bff10c83125066f6" + integrity sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg== + dependencies: + atomic-sleep "^1.0.0" + fast-redact "^3.0.0" + on-exit-leak-free "^0.2.0" + pino-abstract-transport v0.5.0 + pino-std-serializers "^4.0.0" + process-warning "^1.0.0" + quick-format-unescaped "^4.0.3" + real-require "^0.1.0" + safe-stable-stringify "^2.1.0" + sonic-boom "^2.2.1" + thread-stream "^0.15.1" + pirates@^4.0.4: version "4.0.5" resolved "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz" @@ -3742,6 +3809,11 @@ pretty-format@^28.1.1: ansi-styles "^5.0.0" react-is "^18.0.0" +process-warning@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-1.0.0.tgz#980a0b25dc38cd6034181be4b7726d89066b4616" + integrity sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q== + prompts@^2.0.1: version "2.4.2" resolved "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz" @@ -3755,11 +3827,30 @@ punycode@^2.1.0: resolved "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== +quick-format-unescaped@^4.0.3: + version "4.0.4" + resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" + integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== + react-is@^18.0.0: version "18.1.0" resolved "https://registry.npmjs.org/react-is/-/react-is-18.1.0.tgz" integrity sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg== +readable-stream@^3.1.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" + integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + +real-require@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.1.0.tgz#736ac214caa20632847b7ca8c1056a0767df9381" + integrity sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg== + regexpp@^3.2.0: version "3.2.0" resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz" @@ -3813,6 +3904,16 @@ safe-buffer@~5.1.1: resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== +safe-buffer@~5.2.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +safe-stable-stringify@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.3.1.tgz#ab67cbe1fe7d40603ca641c5e765cb942d04fc73" + integrity sha512-kYBSfT+troD9cDA85VDnHZ1rpHC50O0g1e6WlGHVCz/g+JS+9WKLj+XwFYyR8UbrZN8ll9HUpDAAddY58MGisg== + semver@^6.0.0, semver@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz" @@ -3864,6 +3965,13 @@ slash@^3.0.0: resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +sonic-boom@^2.2.1: + version "2.8.0" + resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-2.8.0.tgz#c1def62a77425090e6ad7516aad8eb402e047611" + integrity sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg== + dependencies: + atomic-sleep "^1.0.0" + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.13.tgz" @@ -3877,6 +3985,11 @@ source-map@^0.6.0, source-map@^0.6.1: resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== +split2@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" + integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" @@ -3889,6 +4002,11 @@ stack-utils@^2.0.3: dependencies: escape-string-regexp "^2.0.0" +stream-shift@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" + integrity sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ== + string-length@^4.0.1: version "4.0.2" resolved "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz" @@ -3906,6 +4024,13 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string_decoder@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" + integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== + dependencies: + safe-buffer "~5.2.0" + strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" @@ -3984,6 +4109,13 @@ text-table@^0.2.0: resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== +thread-stream@^0.15.1: + version "0.15.2" + resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-0.15.2.tgz#fb95ad87d2f1e28f07116eb23d85aba3bc0425f4" + integrity sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA== + dependencies: + real-require "^0.1.0" + throat@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz" @@ -4045,6 +4177,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +util-deprecate@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + uuid@^8.3.2: version "8.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"