Node.js - Startup Troubleshooting

  • Updated

Issue

Some behaviors noticed when starting up your application with the Node agent:

  • Your application starts but it is not reporting to the Contrast Server

  • Your application/container crashes without any indicators or error messages

  • You notice a large memory/CPU spike during application start-up

 

Cause

As far as start-up issues are concerned, they fall into two buckets:

  1. Misconfigurations
  2. Exceeding available resources.

Running the Contrast Security agent along with a Node.js application (and any other technology actually) will always provide a little bit of an overhead resulting in increased Memory and CPU usage and request latency. As we know this is inevitable because we are running additional code - the agent’s code along with the code of the given application.

We can divide the application life into two phases - application start-up and handling requests.
At the start of each new application, the agent is performing a rewriting of some of the code provided so it can be instrumented. And during the handling requests phase, the agent is tracking each request made. There is a possibility of performance issues in both phases. Here, we will talk only about performance issues in the first phase - during application start-up. If your performance issues are experienced during the handling requests phase, please follow the runtime performance troubleshooting guide: Node.js - Runtime Performance Troubleshooting

 

Resolution

Misconfiguration

Your application doesn’t start after being configured to use the agent, or it starts up fine, but you do not see your server or application in the Contrast UI.

What to check:

  • You’re not blocked by a proxy or firewall rule

  • Ensure you have the latest LTS version of Node.js
  • Ensure your application is supported by Contrast - Node.js Supported Technologies
    • You’re not bundling your server-side Node.js code with a tool like webpack, parcel, or esbuild

  • Your agent configurations are being picked up by the agent
  • The command used to run your application preloads our module at start-up: node -r @contrast/agent <app-main>.js

    • If you’re already using a --require command when running this application to set environment variables or secrets then make sure to include that --require when composing the new run command with the Contrast agent. Always try to have the Contrast agent --require as the first or left-most command.

    • eg. You normally start your application with node -r dotenv/config index.js. You’ll want to change that to node -r @contrast/agent -r dotenv/config index.js.

Exceeding available resources

The agent transpiles code differently depending on whether Protect or Assess are enabled. Protect requires fewer source transformations than Assess and doesn't cause the same startup delays.

When you start your instrumented application, Contrast applies source transformations to both your application and the dependency code your application loads. The rewriting process can take a long time and can cause container health checks to fail and cause containers to get stuck in a bad re-start cycle.

The Node.js agent includes a command line utility you can use to pre-compile applications before starting them. The CLI-rewriter was built to deal with start-up memory, CPU spikes, and health-check timeouts. When started with Contrast, the pre-compiled application loads the rewritten files from disk and significantly improves startup time.

Use the rewriter

  1. In the Node.js agent's configuration file (contrast_security.yaml):
    • (Optional) Enable rewrite caching and specify the path of the cache location.

      • By default, the cache is enabled and will be written to the .contrast folder under the application directory.

      • If either agent.node.rewrite.enable or agent.node.rewrite.cache.enable are false, the CLI will throw an error.

    • (Optional) Specify the logging level.

      • The rewriter provides minimal logging at the default INFO level to inform the user which mode it is running under. At the DEBUG level, it will inform the user about failures in the rewriting process. At the TRACE level, it will log information about each file that is rewritten.

    • Enable assess or protect to specify the mode under which to run the rewriter. If both modes are enabled then the rewriter is in assess mode, since assess includes all of the transforms from protect. You can also specify modes at runtime with the -a, --assess or -p, --protect flags.
      For example:
      // example config
      agent:
        logger:
          level: TRACE // Default: INFO
        node:
          rewrite:
            enable: true // default: true
            cache:
              enable: true // default: true
              path: ./path/relative/to/app/root // default: .contrast/
      assess:
       enable: true
  2. (Optional) Install the @contrast/cli package:

    • This can be a devDependency since it is not used by the agent at runtime.
    • npx will ask you to install the package when performing step 3 if you do not install it beforehand. This is fine, but installing it manually (and including @contrast/cli as a dependency in your package.json) will ensure you are using the specified version
      npm install --save-dev @contrast/cli
  3. Invoke the executable and provide it with your application’s entry point (for example index.js).
    npx -p @contrast/cli rewrite index.js
    # or
    npx -p @contrast/cli rewrite -a index.js # force assess mode
    npx -p @contrast/cli rewrite -p index.js # force protect mode
  4. You can verify that the precompilation has occurred by checking the cache directory (.contrast by default). You should see several nested folders like .contrast/PACKAGE_NAME/CONTRAST_REWRITER_VERSION_NUMBER/MODE, for example, .contrast/my-package/1.7.0/assess. The contents of the _ directory in this path should match the structure of your application directory.

 

If your application is containerized, you could perform the rewriting as part of the Docker image creation. Here is an example Dockerfile without the rewriter and with the rewriter:

Without the rewriter:

# Dockerfile
ARG node_version=16                                      
FROM node:$node_version                                  
                                                         
WORKDIR /app                                             
COPY . .                                                 
RUN npm ci                                               
ENV DEBUG=contrast:*                                     
ENTRYPOINT ["node", "-r", "@contrast/agent", "server.js"]

With the rewriter:

# Dockerfile.precompile
ARG node_version=16                                            
FROM node:$node_version                                        
                                                               
WORKDIR /app                                                   
ENV DEBUG=contrast:*                                           
COPY . .                                                       
RUN npm ci                                                     

# ---Add the following lines for Contrast rewriter---
# install the Node.js agent as well as its CLI utilities
RUN npm install @contrast/agent @contrast/cli
# the entry point provided to `rewrite` should match the application entry point below.
# `rewrite -a` indicates we are rewriting for assess mode.
# Take note that the new rewiter CLI does not require any yaml or ENV VAR config settings
RUN npx -p @contrast/cli rewrite -a server.js
# ---
ENTRYPOINT ["node", "-r", "@contrast/agent", "server.js"]

 

Known Limitations

A few scenarios cannot currently be handled by the rewriter CLI.

The rewriter CLI uses static analysis to determine the files to rewrite, so it cannot handle dynamic imports or requires. If a file is included in any of the following ways, the agent will instead rewrite that file when it is included at runtime.

// dynamic imports or requires
const name = 'foo.js';
await import(name);require(name);

// direct calls to createRequire
createRequire(import.meta.url)('foo.js');
// this should work, however:
const require = createRequire(import.meta.url);
require('foo.js');

// renamed require functions
const r = createRequire('...');
r('foo.js');
const myRequire = require;
myRequire('bar.js');

 

 If issues persist after making this change, please submit a ticket to our online support portal.

Was this article helpful?

0 out of 0 found this helpful

Have more questions? Submit a request