Removing sensitive information from HTTP headers in Lambda functions

We can avoid displaying sensitive header information by interrupting the HTTP response when our Lambda function receives an error. We can quickly create a function that returns only the properties we need and hides the rest.

1. The scenario

Example Corp has several microservices. Some of these services run on Lambda functions. One such function uses a 3rd party API to fetch some data. The application uses the popular HTTP client axios for making the requests, and authenticates with a JWT.

One day the 3rd party API responded with an error. Alice, who is a developer at Example Corp, looked at the logs, and to her surprise, she saw something like this:

{
  // lots of other properties
  "headers": {
    "Accept": "application/json",
    "Authorzation": "Bearer <JWT TOKEN HERE>",
    "User-Agent": "axios/0.27.2"
  },
  "method": "get",
}
// more properties

It’s not good, Alice said to herself. There is sensitive information in the logs. She quickly created a ticket on the issue and started to remove the token from the response.

2. The solution

She wanted to apply the solution specifically to the requests the application made to the 3rd party API. She decided to dedicate an axios client to these calls and use an interceptor function to remove the sensitive information from the response.

2.1. Code

She didn’t have to do a lot of work. In the end, her code was similar to this:

import axios from 'axios';

const axiosInstance = axios.create();
axiosInstance.interceptors.response.use(
  (response) => {
    return response;
  },
  (error) => {
    const sanitizedError = {
      message: error.message,
      name: error.name,
      stack: error.stack,
      method: error.config.method,
      url: error.config.url,
      status: error.stats,
    };

    return Promise.reject(sanitizedError);
  }
);


// Lambda handler
export const handler = async () => {
  try {
    const { data } = await axiosInstance.get('<URL_TO_CALL>', {
      headers: {
        Authorization: 'Bearer TOKEN',
      },
    });
    // process response
  } catch (error) {
    console.log('An error occurred', error);
    throw error;
  }
};

The axios interceptor is available on both the request and response sides. Here we implement the interceptors for the response, but request interceptors work similarly.

2.2. Interceptor

Response interceptors (provided by the axios.interceptors.response.use() method) stop the response and allow us to implement custom logic before we can see the result.

Similarly to Promises the interceptor accepts two function arguments.

The first function (successHandler) intercepts the response when everything works as expected. Its argument will be the response object. In this example, we don’t want to do anything with the response, so we will return it.

The second argument (errorHandler) is the function that modifies the thrown errors. The middleware provides the error object as an argument to the callback function. We can create a custom object with the properties we want. As the code snippet shows, we ignore the axios config object that contains the token. We only keep the properties that we want to use: name, message, method, and a couple of others.

Let’s invoke the Lambda function now. We should only see the properties we have set in the sanitizedError object. sanitizedError doesn’t contain the headers, so we won’t see the Authorization header in the logs anymore.

It’s important to return a rejected Promise (or throw an error), or axios will consider the response a success.

3. Summary

When an axios request throws an error, we will see the entire response object in the logs. The response might contain sensitive information, for example, authorization tokens. We can remove these fields by intercepting the response and rejecting a promise with a custom error object.

4. References, further reading

Axios Interceptors - More about axios interceptors