Skip to main content

Getting started with JavaScript static analysis

著者:
0 分で読めます

When writing JavaScript code, static analysis is crucial in maintaining code quality and readability and ensuring code security. It is an automated process of scanning and checking your source code before it's executed to help identify problematic coding patterns, bugs, vulnerabilities, and errors that could lead to serious security issues or application failures.

JavaScript is a language known for its dynamism and flexibility, so, static analysis can be particularly valuable. It may compensate for the absence of a traditional compiler, providing a tool for developers to detect potential issues early in the development lifecycle. This is probably one of the reasons you have considered adopting TypeScript, right?

Understanding static analysis

Static analysis, also known as static code analysis, is a method of debugging that automatically examines source code before a program is run. It analyzes a set of code against a set (or multiple sets) of coding rules.

Static analysis involves inspecting the source code of a software application without executing the application itself. In contrast to dynamic analysis, which evaluates code by executing it, static analysis is performed in a non-runtime environment. This method is instrumental in finding errors, bugs, and other issues that could harm the application or its functionality. It also aids developers in understanding the codebase, ensuring code quality, and maintaining coding standards across the project.

In JavaScript, static analysis can be performed using tools such as ESLint, JSHint, and TSLint. Here's a simple example of how ESLint works:

var user = {};
console.log(user.name);

When running ESLint on this code, it will provide a warning: Unexpected console statement no-console

This warning helps developers avoid using console.log statements in their production code, which is considered a bad practice.

Static vs dynamic analysis

Let’s take an in-depth look at static and dynamic analysis and the value of static analysis over dynamic analysis in certain situations. 

Here's an example of static analysis using ESLint:

const ESLint = require('eslint');

const eslint = new ESLint({ fix: true });

async function lintCode() {
  const results = await eslint.lintFiles(["./**/*.js"]);
  await ESLint.outputFixes(results);
}

lintCode().catch((error) => {
  process.exitCode = 1;
  console.error(error);
});

This script uses ESLint in its programmatic API to perform static analysis on all JavaScript files in your project, fixing any issues it can along the way.

What is dynamic analysis?

On the other hand, dynamic analysis involves analyzing an application during its execution. It provides a real-time view of the system's performance, behavior, and output. Unlike static analysis, dynamic analysis requires the program to be running. It is often used in debugging to find runtime issues that static analysis might miss.

For example, using JavaScript in the browser or a Node.js runtime, you can leverage the console.time() and console.timeEnd() for dynamic analysis:

function someFunction() {
  // Some code here...
}

console.time('someFunction');

someFunction();

console.timeEnd('someFunction');

This script will output the time someFunction () takes to execute, providing a simple form of dynamic analysis.

For native Node.js applications, you can use the built-in node:perf_hooks module to measure the time taken for a function to execute:

const { performance } = require('perf_hooks');

function someFunction() {
  // Some code here...
}

const start = performance.now();

someFunction();

const end = performance.now();

console.log(`Time taken: ${end - start}ms`);

In application security, dynamic application security test (DAST) tools are used to analyze web applications in a running state. They can help identify security vulnerabilities such as injection attacks, cross-site scripting (XSS), and more. Read more on SAST vs DAST and how to combine both of them.

The value of static analysis over dynamic analysis in certain situations

Static and dynamic analysis have unique advantages and are often used together during different stages of the software development life cycle to ensure code quality and security. However, in certain situations, static analysis holds a distinct advantage over dynamic analysis.

  • Early Bug Detection: Static analysis can be executed as soon as code is written, allowing you to catch and fix issues early in the development cycle, even before testing begins. This can save a significant amount of time and reduce costs.

  • Comprehensive Code Coverage: Static analysis tools scan the entire codebase, unlike dynamic analysis, which only tests the parts of the code executed during a particular run.

  • Enforcing Coding Standards: Static analysis tools enforce coding standards and best practices. They can be configured to flag deviations, helping maintain consistency in code quality.

Using static analysis in JavaScript 

JavaScript, often used for creating interactive web applications, is a dynamic, loosely typed language. This means that variables in JavaScript can hold values of any type without any type-checking enforcement. This flexibility can also lead to undetected bugs and vulnerabilities.

For instance, consider the following JavaScript code:

function addNumbers(a, b) {
  return a+b;
}

console.log(addNumbers(1, 2));
// Outputs: 3

console.log(addNumbers(1, "2"));
// Outputs: "12"

In the example above, addNumbers is designed to add two numbers together. However, due to JavaScript's loose type system, it also concatenates strings and numbers, which is likely not the intended behavior. This example underscores the importance of static analysis in JavaScript.

In JavaScript, static analysis is vital in maintaining code security. It can help uncover security vulnerabilities hidden within the code, which malicious actors could exploit. Given the increasing importance of cybersecurity, employing static analysis tools in your development process is more of a necessity than a choice.

How static analysis improves code quality and security in JavaScript

Code quality

Static analysis helps enforce coding standards and styles, making the code more consistent and easier to read and understand. It can also detect potential problems such as unused variables, unreachable code, redundant conditions, and more, making your code cleaner and more efficient.

For instance, consider the following JavaScript code:

let x;
if (x = 10) {
  console.log("x is 10");
} else {
  console.log("x is not 10");
}

When running ESLint on this code, it will provide an error: Expected a conditional expression and instead saw an assignment no-cond-assign

This error helps developers avoid common mistakes like using a single equal sign for comparison, which is actually an assignment operator.

Code security

Static analysis tools can detect a wide range of security vulnerabilities, like injection attacks, cross-site scripting (XSS), insecure random number generation, and more. They provide a way to check for these vulnerabilities automatically, which is much more efficient and reliable than manual code review.

For example, consider the following Node.js code:

const express = require('express');
const Sequelize = require('sequelize');

const access_token = 'a_sample_token';
const app = express();
const sequelize = new Sequelize('database', 'username', 'password', {
  dialect: 'sqlite',
  storage: 'data/juiceshop.sqlite'
});

app.post('/login', function (req, res) {
  sequelize.query('SELECT * FROM Products WHERE name LIKE ' +  req.body.username);
});

app.get('/test', function(req,res) {
  const test = null;
  const dog = test?.dog;
  console.log(dog);
});

app.get('/some/path', function(req, res) {
  res.redirect(req.param("target"));
});

window.addEventListener("message", (event) => {
  console.log(event.data);
});

Can you find all the vulnerabilities here?

Luckily, if you use Snyk, it's a simple task. Simply having the Snyk VS Code extension installed in your IDE will provide you with real-time feedback on the security of your code. After saving the file, you'll see Snyk's security analysis results in the editor as a linter.

When running Snyk on this code, it will provide the following warnings:

  • SQL Injection in the /login route

  • Hardcoded secret used for access token in the code

An Open Redirect vulnerability in the /some/path route

Snyk Code Checker findings for static code analysis that finds security vulnerabilities using Snyk.

And actually, there are a couple more issues hidden there. Care to find out? Sign up for a free Snyk account and try it out!

Introduction to Abstract Syntax Tree (AST)

The Abstract Syntax Tree, commonly known as AST, is a critical concept in programming and code security. It can be defined as a tree representation of the abstract syntactic structure of source code written in a programming language. Each node of the tree denotes a construct occurring in the source code.

In JavaScript static analysis, an Abstract Syntax Tree plays a pivotal role. Static analysis refers to examining the code without executing it. It's like a code review on autopilot, scanning for potential bugs, vulnerabilities, and violations of programming conventions. ASTs are instrumental in this process because they provide a structured and more easily navigable representation of the source code. They enable the static analysis tools to traverse the code more efficiently and identify patterns that may denote bugs or security vulnerabilities.

Consider the following JavaScript code:

function greet (name) {
  return `Hello, ${name}!`;
}

A simplified version of its AST would look like this:

Program
  FunctionDeclaration
    Identifier (name: greet)
    BlockStatement
      ReturnStatement
        TemplateLiteral
          TemplateElement (value: Hello, )
          Identifier (name: name)
          TemplateElement (value: !)

This representation helps static analysis tools understand the code's structure and flow, making it easier to spot potential issues.

Using AST Explorer to understand the structure of JavaScript code

AST Explorer is a powerful tool that can help developers understand how a piece of code is transformed into an Abstract Syntax Tree. It supports various languages, including JavaScript, and is an excellent tool for learning and debugging AST-related issues.

To use AST Explorer with JavaScript, follow these steps:

  1. Go to AST Explorer.

  2. In the top menu, select 'JavaScript' as your language.

  3. Paste or write JavaScript code in the left pane.

  4. The right pane will automatically update to display the AST of the provided code.

For instance, if you input the greet function from our previous example:

function greet (name) {
  return `Hello, ${name}!`;
}

The corresponding AST will appear in the right pane. It will show you the tree structure of your code, and you can navigate through it to better understand how your code is interpreted.

The ASTExplorer web tool to understand abstract syntax tree and static analysis in JavaScript code.

Understanding ASTs is a crucial part of mastering JavaScript static analysis. It provides valuable insights into how your code is structured, how the JavaScript engine reads it, and how static analysis tools interpret it. This knowledge can help you write more efficient, secure, and bug-free code.

Three static analysis tools for JavaScript

Static analysis tools analyze the code without executing it, hence the term "static." They parse the source code, building an abstract syntax tree (AST) to understand the structure and semantics of the code. They then apply various rules and heuristics to the AST to detect potential problems.

Several popular static analysis tools are available for JavaScript; here, we will discuss a few of them.

1. Snyk Code

Snyk Code is a tool that you can use in the CLI, IDE or through a Git integration, and provides a security static analysis. In addition to the standard static analysis features, Snyk Code offers deep code analysis powered by Symbolic AI. This allows it to detect complex, multi-step vulnerabilities that other tools may miss.

Let's take a look at an example of how static analysis can help identify a common security issue - SQL Injection. Consider the following JavaScript code snippet:

let query = "SELECT * FROM users WHERE name = '" + userInput + "'";

In the above code, userInput is directly concatenated into a SQL query. If an attacker controls userInput, they could manipulate the query to extract sensitive information or perform malicious actions. A static analysis tool like Snyk could flag this pattern as a potential SQL Injection vulnerability.

To use Snyk Code, you'll need to sign up for a free Snyk account and install the Snyk CLI, or use the Snyk VS Code extension to get real-time feedback on the security of your code:

npm install -g snyk
snyk auth
snyk test

In the above commands, we're installing the Snyk CLI, authenticating with our Snyk account, and running a Snyk test on our code.

2. ESLint for JavaScript static analysis

ESLint checks your JavaScript code for style issues, potential bugs, and security vulnerabilities. It offers a pluggable architecture, allowing developers to define and use custom rules.

ESLint analyzes your code for patterns and alerts you about potential issues that could lead to bugs or inconsistencies, such as unused variables, unreachable code, and missing semicolons. It does this by parsing and checking your code against a set of rules you define.

Here's a basic .eslintrc.json configuration file for ESLint:

{
  "extends": ["eslint:recommended", "plugin:security/recommended"],
  "plugins": ["security"],
  "rules": {
     "security/detect-sql-injection": "warn"
  }
}

In the above configuration, we're extending the recommended ESLint rules and the security plugin's recommended rules. We're also enabling a warning for potential SQL Injection vulnerabilities.

You can configure ESLint to enforce a wide variety of style rules, including indentation, use of semicolons, line length, and the use of single quotes vs double quotes for strings. For instance, if you prefer two spaces for indentation, you can set up ESLint to warn you whenever it encounters code that doesn't adhere to this rule.

Moreover, ESLint helps prevent errors by alerting you about common mistakes and potential issues in your code. Integrating ESLint into your development workflow lets you catch and fix errors before they make it into production.

3. Prettier

Prettier is an open-source JavaScript library that serves as an opinionated code formatter. It helps to enforce a consistent coding style across an entire codebase or project. As a developer, you may have experienced how disagreements over code style can take away from what really matters. Prettier takes care of this, so you can focus on writing code, rather than worrying about how it looks.

Prettier supports many languages, including JavaScript, TypeScript, CSS, HTML, and Markdown among others. It integrates with most editors, and it works out of the box, which means you don't need to spend time setting up complex configuration files. Prettier works by parsing your code into an Abstract Syntax Tree (AST) that we learned about earlier, then applies formatting rules, and then printing out the newly formatted code.

Here's an example of code before and after running through Prettier:

Before Prettier:

let foo=function( arg1, arg2 ) {
    if (arg1>arg2) {
        return arg1
    } else {
        return arg2
    }
}

After Prettier:

let foo = function(arg1, arg2) {
  if (arg1 > arg2) {
    return arg1;
  } else {
    return arg2;
  }
};

As you can observe, Prettier has standardized the spacing and placement of brackets and also added a semicolon at the end of the return statements.

While Prettier focuses solely on formatting code, ESLint, is a tool that checks for patterns in JavaScript code that could potentially lead to errors. ESLint is highly configurable and can be fine-tuned to enforce specific coding standards and conventions.

This is where Prettier and ESLint can complement each other. While Prettier ensures that your code is formatted consistently, ESLint ensures that your code adheres to best practices, and doesn't contain any potential bugs. Using these two tools together can greatly improve your code quality.

You can even set up Prettier to format your code whenever you save a file in your editor and have ESLint check your code for potential problems. This way, you can catch and fix issues before they become problems.

Here's an example of how to set this up in a .eslintrc.json file:

{
  "plugins": ["prettier"],
  "extends": ["eslint:recommended", "plugin:prettier/recommended"],
  "rules": {
    "prettier/prettier": "error"
  }
}

In this configuration, we're extending ESLint's recommended rules and adding Prettier as a plugin. We're also setting the prettier/prettier rule to error, which means any time our code doesn't conform to Prettier's formatting, ESLint will throw an error.