Removing sensitive information from HTTP headers in Lambda functions
Tags: typescript, security, aws, serverless
Published: 2022-05-26 08:33:46

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 <a href="https://aws.amazon.com/lambda/" class="link" target="_blank" rel="noopener noreferrer">Lambda</a> functions. One such function uses a 3rd party API to fetch some data. The application uses the popular HTTP client <a href="https://www.npmjs.com/package/axios" class="link" target="_blank" rel="noopener noreferrer">axios</a> for making the requests, and authenticates with a <a href="https://jwt.io/" class="link" target="_blank" rel="noopener noreferrer">JWT</a>.

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:

```jsonc
{
  // 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:

```ts
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 <a href="https://nodejs.dev/learn/understanding-javascript-promises" class="link" target="_blank" rel="noopener noreferrer">Promises</a> 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

<a href="https://masteringjs.io/tutorials/axios/interceptors" class="link" target="_blank" rel="noopener noreferrer">Axios Interceptors</a> - More about axios interceptors