From 98136d4470fa04dd29c27febdb524ebe4fa5e284 Mon Sep 17 00:00:00 2001 From: Kai Koenig Date: Thu, 8 Oct 2026 09:48:22 +1300 Subject: [PATCH 1/2] fix: address dependency vulnerabilities and Lambda context privacy Patch vulnerable development dependencies and capture only documented invocation metadata in reports and automatic breadcrumbs. Add async and callback overlap tests and real sample reporting checks in CI. --- .github/workflows/node.js.yml | 16 +---- CHANGELOG.md | 6 ++ CONTRIBUTING.md | 13 ++++ README.md | 30 +++++++- lib/raygun.aws.ts | 18 ++++- package-lock.json | 40 ++++++----- package.json | 3 +- test/example_integration.js | 59 ++++++++++++++++ test/raygun_aws_test.js | 126 ++++++++++++++++++++++++++++++++++ 9 files changed, 274 insertions(+), 37 deletions(-) create mode 100644 test/example_integration.js diff --git a/.github/workflows/node.js.yml b/.github/workflows/node.js.yml index 5b7c5a4..2ff95e6 100644 --- a/.github/workflows/node.js.yml +++ b/.github/workflows/node.js.yml @@ -105,17 +105,5 @@ jobs: working-directory: example - run: npm audit signatures working-directory: example - - name: Smoke test example handler - working-directory: example - run: | - node <<'NODE' - const assert = require('node:assert/strict') - const { handler } = require('./index') - - handler({}, {}) - .then((result) => assert.equal(result, 'all good!')) - .catch((error) => { - console.error(error) - process.exitCode = 1 - }) - NODE + - name: Test example error reporting against a local endpoint + run: npm run test:example diff --git a/CHANGELOG.md b/CHANGELOG.md index baaacca..5beccf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +- Updated vulnerable transitive development dependencies. +- Replaced full Lambda context capture with a snapshot of the eight documented invocation metadata fields in custom data and automatic breadcrumbs. Identity, client context, and application-added fields are no longer captured automatically. +- Documented context capture boundaries and redaction with the Raygun client's `onBeforeSend` hook. + ## 0.0.9 - Maintenance-only release; no public API or runtime behavior changes. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a3ecced..ca83d72 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,6 +17,19 @@ The recommended IDE for working on this project is Visual Studio Code. To run tests, run `npm run test` or run all tests from VSCode. +To test the actual example's success and error paths against a local mock +Raygun endpoint, run these commands from the repository root: + +```sh +npm run prepare +npm --prefix example ci --ignore-scripts +npm run test:example +``` + +The example test uses a test API key and does not contact AWS or Raygun. +The main test suite also checks overlapping async and callback invocations +for breadcrumb and context isolation. + ### Code analysis To check the code, run `npm run eslint` and `npm run tseslint`. diff --git a/README.md b/README.md index 6bfc015..c2a799c 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,11 @@ exports.handler = awsHandler({ client }, async function (event, context) { ### AWS Lambda function context in Custom Data -The `awsHandler` also adds automatically the function call context in the "Custom Data" payload of the error report. +The `awsHandler` captures an allowlisted snapshot of invocation metadata before +running your handler. It adds this snapshot to the error report's "Custom Data" +and the automatic "Running AWS Function" breadcrumb. Only the eight fields below +are captured; `identity`, `clientContext`, methods, and application-added fields +are excluded. The original event and context still reach your handler unchanged. This payload can be found in the "Custom" tab in the Raygun Crash Reporting error report page. @@ -124,6 +128,30 @@ context: { } ``` +### Protecting sensitive data + +The wrapper does not automatically capture the event. Avoid adding credentials, +personal data, or entire event/context objects to custom data, breadcrumbs, or +error messages. Invocation metadata still contains infrastructure identifiers, +including the function ARN and log names; review whether your application needs them. + +The Raygun client's `filters` option applies only to request headers, query +parameters, and parsed request bodies. It does not redact Lambda context metadata, +custom data, breadcrumbs, or error messages. Use the client's `onBeforeSend` hook +to remove or redact these sections before transport. For example, to omit all +custom data and breadcrumbs: + +```js +client.onBeforeSend((payload) => { + delete payload.details.userCustomData; + delete payload.details.breadcrumbs; + return payload; +}); +``` + +See the Raygun client's [sensitive-data guidance](https://github.com/MindscapeHQ/raygun4node#protecting-sensitive-data) +for request filtering and logging precautions. + ## Release History [View the changelog here](CHANGELOG.md) diff --git a/lib/raygun.aws.ts b/lib/raygun.aws.ts index a4c2d85..3e56ced 100644 --- a/lib/raygun.aws.ts +++ b/lib/raygun.aws.ts @@ -36,9 +36,21 @@ async function runHandler( context: Context, asyncHandler: AsyncHandler, ) { + // Capture only invocation metadata, not identity, clientContext, or added fields. + const contextData = { + callbackWaitsForEmptyEventLoop: context.callbackWaitsForEmptyEventLoop, + functionVersion: context.functionVersion, + functionName: context.functionName, + memoryLimitInMB: context.memoryLimitInMB, + logGroupName: context.logGroupName, + logStreamName: context.logStreamName, + invokedFunctionArn: context.invokedFunctionArn, + awsRequestId: context.awsRequestId, + }; + awsHandlerConfig.client.addBreadcrumb({ - message: `Running AWS Function: ${context.functionName}`, - customData: context, + message: `Running AWS Function: ${contextData.functionName}`, + customData: contextData, level: "info", category: "AWS Handler", }); @@ -51,7 +63,7 @@ async function runHandler( // Prepare send parameters const customData = { - context: context, + context: contextData, }; const tags = ["AWS Handler"]; const sendParams = { diff --git a/package-lock.json b/package-lock.json index 6b2964e..6953ca9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2207,9 +2207,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3161,9 +3161,9 @@ } }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "dev": true, "funding": [ { @@ -3525,9 +3525,9 @@ "license": "MIT" }, "node_modules/http-cache-semantics": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", - "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.3.0.tgz", + "integrity": "sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==", "dev": true, "license": "BSD-2-Clause" }, @@ -3746,9 +3746,9 @@ } }, "node_modules/ip-address": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.4.0.tgz", - "integrity": "sha512-oSK96Grm3aP6OrS263xVxbNDGVL7rzBtYdpGqlDG8iQdoenDoTs/nkki+DflYbAEE8Xl6o5YxhxlrKvI3nqKXQ==", + "version": "10.7.3", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.3.tgz", + "integrity": "sha512-A1kdq/tSb5QjvKvAMgIoEvDBIgL7qaqVP/jkvSwYYRZ9iEzvPpopxp2wQfu3SuZRHtpHNxMn8Fs0bS+gf5Xmwg==", "dev": true, "license": "MIT", "engines": { @@ -4912,9 +4912,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4923,6 +4923,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/punycode": { @@ -6080,9 +6084,9 @@ } }, "node_modules/undici": { - "version": "6.28.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.28.0.tgz", - "integrity": "sha512-LIY910g9TI13YS95lrMFrs8Rm/u/irgHeTWoKCoteeJ04CUJ92eEfj0rVn+7VKMPBpUPiUoBKfhNyLI23EE/KA==", + "version": "6.29.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.29.0.tgz", + "integrity": "sha512-R+RODBqp6i2pPflGdq+xIOUkl+RNfGgHwoinecKu/JCuf2uO06cOKoDbI2P7Dn6KcswdKwrczbU6IYJ6K8X+wg==", "dev": true, "license": "MIT", "engines": { diff --git a/package.json b/package.json index 6e0e87a..65d4174 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,8 @@ "//prepare": "prepare project for distribution", "prepare": "tsc", "//test": "runs tests on main project", - "test": "tap --disable-coverage test/*_test.js" + "test": "tap --disable-coverage test/*_test.js", + "test:example": "tap --disable-coverage test/example_integration.js" }, "devDependencies": { "@eslint/js": "^10.0.1", diff --git a/test/example_integration.js b/test/example_integration.js new file mode 100644 index 0000000..4ed9582 --- /dev/null +++ b/test/example_integration.js @@ -0,0 +1,59 @@ +const test = require("tap").test; +const { Client } = require("raygun"); +const { makeClientWithMockServer } = require("./utils"); + +test("actual Lambda sample reports its error to a local endpoint", async (t) => { + const environment = await makeClientWithMockServer(); + t.teardown(() => environment.stop()); + const init = Client.prototype.init; + let sampleClient; + Client.prototype.init = function (options) { + sampleClient = this; + return init.call(this, { + ...options, + apiKey: "TEST_API_KEY", + host: "localhost", + port: environment.address.port, + useSSL: false, + }); + }; + let handler; + try { + ({ handler } = require("../example/index")); + } finally { + Client.prototype.init = init; + } + t.teardown(() => sampleClient.stop()); + + t.equal(await handler({}, { functionName: "sample-success" }), "all good!"); + t.equal(environment.server.entries.length, 0); + const nextRequest = environment.nextRequest(); + await t.rejects( + handler( + { error: true }, + { + functionName: "sample-failure", + awsRequestId: "sample-request", + identity: { cognitoIdentityId: "private-sample-sentinel" }, + }, + ), + /It's an AWS error!/, + ); + const message = await nextRequest; + t.equal(environment.server.entries.length, 1); + t.equal(message.details.error.message, "It's an AWS error!"); + t.same(message.details.tags, ["AWS Handler"]); + t.same(message.details.userCustomData.context, { + functionName: "sample-failure", + awsRequestId: "sample-request", + }); + t.same( + message.details.breadcrumbs.map((crumb) => crumb.message), + [ + "Running AWS Function: sample-failure", + "breadcrumb on event received", + "event has error data!", + ], + ); + t.notMatch(JSON.stringify(message), "private-sample-sentinel"); +}); diff --git a/test/raygun_aws_test.js b/test/raygun_aws_test.js index a8f7442..5ac0804 100644 --- a/test/raygun_aws_test.js +++ b/test/raygun_aws_test.js @@ -162,3 +162,129 @@ test("include scoped breadcrumbs", async function (t) { // custom breadcrumb t.equal(message.details.breadcrumbs[1].message, "custom breadcrumb"); }); + +for (const callbackStyle of [false, true]) { + test(`allowlist context metadata (${callbackStyle ? "callback" : "async"})`, async (t) => { + const environment = await makeClientWithMockServer(); + t.teardown(() => environment.stop()); + const metadata = { + callbackWaitsForEmptyEventLoop: false, + functionVersion: "42", + functionName: "privacy-test", + memoryLimitInMB: "256", + logGroupName: "/aws/lambda/privacy-test", + logStreamName: "test-stream", + invokedFunctionArn: + "arn:aws:lambda:ap-southeast-2:123456789012:function:privacy-test", + awsRequestId: "request-123", + }; + const secret = "private-context-sentinel"; + const context = { + ...metadata, + identity: { cognitoIdentityId: secret }, + clientContext: { custom: { token: secret } }, + password: secret, + }; + const error = new Error("handler failed"); + const checkAndMutate = (event, receivedContext) => { + t.equal(receivedContext, context, "handler receives original context"); + t.equal(event.token, secret, "handler receives original event"); + receivedContext.functionName = secret; + receivedContext.extra = secret; + }; + let handler; + if (callbackStyle) { + handler = (event, receivedContext, callback) => { + checkAndMutate(event, receivedContext); + callback(error); + }; + } else { + handler = async (event, receivedContext) => { + checkAndMutate(event, receivedContext); + throw error; + }; + } + const lambda = awsHandler({ client: environment.client }, handler); + const nextRequest = environment.nextRequest(); + try { + await lambda({ token: secret }, context); + t.fail("handler error must be rethrown"); + } catch (caught) { + t.equal(caught, error); + } + const message = await nextRequest; + t.same(message.details.userCustomData.context, metadata); + t.same(message.details.breadcrumbs[0].customData, metadata); + t.equal( + message.details.breadcrumbs[0].message, + "Running AWS Function: privacy-test", + ); + t.notMatch(JSON.stringify(message), secret); + }); +} + +for (const callbackStyle of [false, true]) { + test(`overlapping invocations stay isolated (${callbackStyle ? "callback" : "async"})`, async (t) => { + const environment = await makeClientWithMockServer(); + t.teardown(() => environment.stop()); + const { client } = environment; + const release = {}; + const gates = { + A: new Promise((resolve) => { + release.A = resolve; + }), + B: new Promise((resolve) => { + release.B = resolve; + }), + }; + const errors = { A: new Error("failure-A"), B: new Error("failure-B") }; + const work = async (event) => { + client.addBreadcrumb(`start-${event.id}`); + await gates[event.id]; + client.addBreadcrumb(`finish-${event.id}`); + throw errors[event.id]; + }; + let handler; + if (callbackStyle) { + handler = (event, context, callback) => { + work(event).catch(callback); + }; + } else { + handler = work; + } + const lambda = awsHandler({ client }, handler); + const first = lambda( + { id: "A" }, + { functionName: "function-A", awsRequestId: "request-A" }, + ).catch((error) => error); + const second = lambda( + { id: "B" }, + { functionName: "function-B", awsRequestId: "request-B" }, + ).catch((error) => error); + + release.B(); + t.equal(await second, errors.B); + t.equal( + environment.server.entries.length, + 1, + "B completes while A is suspended", + ); + release.A(); + t.equal(await first, errors.A); + t.equal(environment.server.entries.length, 2); + for (const [index, id] of ["B", "A"].entries()) { + const message = environment.server.entries[index]; + const context = { + functionName: `function-${id}`, + awsRequestId: `request-${id}`, + }; + t.equal(message.details.error.message, `failure-${id}`); + t.same(message.details.userCustomData.context, context); + t.same(message.details.breadcrumbs[0].customData, context); + t.same( + message.details.breadcrumbs.map((crumb) => crumb.message), + [`Running AWS Function: function-${id}`, `start-${id}`, `finish-${id}`], + ); + } + }); +} From c95b27af74c67bd90181acd4cbd57bd5682c0210 Mon Sep 17 00:00:00 2001 From: Kai Koenig Date: Thu, 8 Oct 2026 10:15:56 +1300 Subject: [PATCH 2/2] fix: preserve existing Lambda context payloads Restore full context capture without an allowlist or snapshot. Keep dependency fixes and integration coverage, and assert preservation of identity, client context, added fields, and context mutations. Update redaction guidance to describe existing opt-in behavior. --- CHANGELOG.md | 2 +- README.md | 21 +++++++++-------- lib/raygun.aws.ts | 18 +++----------- test/example_integration.js | 18 +++++++++++--- test/raygun_aws_test.js | 47 +++++++++++++++++++++++++++++-------- 5 files changed, 67 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5beccf9..69a28d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ## Unreleased - Updated vulnerable transitive development dependencies. -- Replaced full Lambda context capture with a snapshot of the eight documented invocation metadata fields in custom data and automatic breadcrumbs. Identity, client context, and application-added fields are no longer captured automatically. +- Preserved existing full Lambda context capture and added regression tests for identity, client context, application-added fields, and overlapping invocations. - Documented context capture boundaries and redaction with the Raygun client's `onBeforeSend` hook. ## 0.0.9 diff --git a/README.md b/README.md index c2a799c..17b8651 100644 --- a/README.md +++ b/README.md @@ -107,11 +107,11 @@ exports.handler = awsHandler({ client }, async function (event, context) { ### AWS Lambda function context in Custom Data -The `awsHandler` captures an allowlisted snapshot of invocation metadata before -running your handler. It adds this snapshot to the error report's "Custom Data" -and the automatic "Running AWS Function" breadcrumb. Only the eight fields below -are captured; `identity`, `clientContext`, methods, and application-added fields -are excluded. The original event and context still reach your handler unchanged. +The `awsHandler` adds the full function call context to the error report's +"Custom Data" and the automatic "Running AWS Function" breadcrumb. The example +below shows common metadata fields, not an exhaustive list. Serializable +`identity`, `clientContext`, and application-added fields are also included when +present. Context changes made by your handler are reflected in the report. This payload can be found in the "Custom" tab in the Raygun Crash Reporting error report page. @@ -130,13 +130,14 @@ context: { ### Protecting sensitive data -The wrapper does not automatically capture the event. Avoid adding credentials, -personal data, or entire event/context objects to custom data, breadcrumbs, or -error messages. Invocation metadata still contains infrastructure identifiers, -including the function ARN and log names; review whether your application needs them. +The wrapper does not automatically capture the event, but it does capture the +full context. Review context fields such as `identity`, `clientContext`, and +application-added data for credentials or personal information. Context also +contains infrastructure identifiers, including the function ARN and log names. +Avoid adding sensitive data to custom data, breadcrumbs, or error messages. The Raygun client's `filters` option applies only to request headers, query -parameters, and parsed request bodies. It does not redact Lambda context metadata, +parameters, and parsed request bodies. It does not redact Lambda context, custom data, breadcrumbs, or error messages. Use the client's `onBeforeSend` hook to remove or redact these sections before transport. For example, to omit all custom data and breadcrumbs: diff --git a/lib/raygun.aws.ts b/lib/raygun.aws.ts index 3e56ced..a4c2d85 100644 --- a/lib/raygun.aws.ts +++ b/lib/raygun.aws.ts @@ -36,21 +36,9 @@ async function runHandler( context: Context, asyncHandler: AsyncHandler, ) { - // Capture only invocation metadata, not identity, clientContext, or added fields. - const contextData = { - callbackWaitsForEmptyEventLoop: context.callbackWaitsForEmptyEventLoop, - functionVersion: context.functionVersion, - functionName: context.functionName, - memoryLimitInMB: context.memoryLimitInMB, - logGroupName: context.logGroupName, - logStreamName: context.logStreamName, - invokedFunctionArn: context.invokedFunctionArn, - awsRequestId: context.awsRequestId, - }; - awsHandlerConfig.client.addBreadcrumb({ - message: `Running AWS Function: ${contextData.functionName}`, - customData: contextData, + message: `Running AWS Function: ${context.functionName}`, + customData: context, level: "info", category: "AWS Handler", }); @@ -63,7 +51,7 @@ async function runHandler( // Prepare send parameters const customData = { - context: contextData, + context: context, }; const tags = ["AWS Handler"]; const sendParams = { diff --git a/test/example_integration.js b/test/example_integration.js index 4ed9582..ad17376 100644 --- a/test/example_integration.js +++ b/test/example_integration.js @@ -30,11 +30,13 @@ test("actual Lambda sample reports its error to a local endpoint", async (t) => const nextRequest = environment.nextRequest(); await t.rejects( handler( - { error: true }, + { error: true, token: "event-only-sentinel" }, { functionName: "sample-failure", awsRequestId: "sample-request", - identity: { cognitoIdentityId: "private-sample-sentinel" }, + identity: { cognitoIdentityId: "sample-identity" }, + clientContext: { custom: { client: "sample-client" } }, + applicationField: "sample-application-field", }, ), /It's an AWS error!/, @@ -46,6 +48,16 @@ test("actual Lambda sample reports its error to a local endpoint", async (t) => t.same(message.details.userCustomData.context, { functionName: "sample-failure", awsRequestId: "sample-request", + identity: { cognitoIdentityId: "sample-identity" }, + clientContext: { custom: { client: "sample-client" } }, + applicationField: "sample-application-field", + }); + t.same(message.details.breadcrumbs[0].customData, { + functionName: "sample-failure", + awsRequestId: "sample-request", + identity: { cognitoIdentityId: "sample-identity" }, + clientContext: { custom: { client: "sample-client" } }, + applicationField: "sample-application-field", }); t.same( message.details.breadcrumbs.map((crumb) => crumb.message), @@ -55,5 +67,5 @@ test("actual Lambda sample reports its error to a local endpoint", async (t) => "event has error data!", ], ); - t.notMatch(JSON.stringify(message), "private-sample-sentinel"); + t.notMatch(JSON.stringify(message), "event-only-sentinel"); }); diff --git a/test/raygun_aws_test.js b/test/raygun_aws_test.js index 5ac0804..1867932 100644 --- a/test/raygun_aws_test.js +++ b/test/raygun_aws_test.js @@ -164,7 +164,7 @@ test("include scoped breadcrumbs", async function (t) { }); for (const callbackStyle of [false, true]) { - test(`allowlist context metadata (${callbackStyle ? "callback" : "async"})`, async (t) => { + test(`preserve full context payload (${callbackStyle ? "callback" : "async"})`, async (t) => { const environment = await makeClientWithMockServer(); t.teardown(() => environment.stop()); const metadata = { @@ -188,9 +188,13 @@ for (const callbackStyle of [false, true]) { const error = new Error("handler failed"); const checkAndMutate = (event, receivedContext) => { t.equal(receivedContext, context, "handler receives original context"); - t.equal(event.token, secret, "handler receives original event"); - receivedContext.functionName = secret; - receivedContext.extra = secret; + t.equal( + event.token, + "event-only-sentinel", + "handler receives original event", + ); + receivedContext.functionName = "updated-function"; + receivedContext.extra = "added-by-handler"; }; let handler; if (callbackStyle) { @@ -207,19 +211,27 @@ for (const callbackStyle of [false, true]) { const lambda = awsHandler({ client: environment.client }, handler); const nextRequest = environment.nextRequest(); try { - await lambda({ token: secret }, context); + await lambda({ token: "event-only-sentinel" }, context); t.fail("handler error must be rethrown"); } catch (caught) { t.equal(caught, error); } const message = await nextRequest; - t.same(message.details.userCustomData.context, metadata); - t.same(message.details.breadcrumbs[0].customData, metadata); + const expectedContext = { + ...metadata, + functionName: "updated-function", + identity: { cognitoIdentityId: secret }, + clientContext: { custom: { token: secret } }, + password: secret, + extra: "added-by-handler", + }; + t.same(message.details.userCustomData.context, expectedContext); + t.same(message.details.breadcrumbs[0].customData, expectedContext); t.equal( message.details.breadcrumbs[0].message, "Running AWS Function: privacy-test", ); - t.notMatch(JSON.stringify(message), secret); + t.notMatch(JSON.stringify(message), "event-only-sentinel"); }); } @@ -255,11 +267,23 @@ for (const callbackStyle of [false, true]) { const lambda = awsHandler({ client }, handler); const first = lambda( { id: "A" }, - { functionName: "function-A", awsRequestId: "request-A" }, + { + functionName: "function-A", + awsRequestId: "request-A", + identity: { cognitoIdentityId: "identity-A" }, + clientContext: { custom: { invocation: "A" } }, + applicationField: "application-A", + }, ).catch((error) => error); const second = lambda( { id: "B" }, - { functionName: "function-B", awsRequestId: "request-B" }, + { + functionName: "function-B", + awsRequestId: "request-B", + identity: { cognitoIdentityId: "identity-B" }, + clientContext: { custom: { invocation: "B" } }, + applicationField: "application-B", + }, ).catch((error) => error); release.B(); @@ -277,6 +301,9 @@ for (const callbackStyle of [false, true]) { const context = { functionName: `function-${id}`, awsRequestId: `request-${id}`, + identity: { cognitoIdentityId: `identity-${id}` }, + clientContext: { custom: { invocation: id } }, + applicationField: `application-${id}`, }; t.equal(message.details.error.message, `failure-${id}`); t.same(message.details.userCustomData.context, context);