Multiple Lines
Multiple LinesMultiple Lines
Up Arrow
Back to Blog
Security

Why Static Analysis Falls Short in Dynamic Programming Languages

By 
Omer Yair

The Airport Security Scanner

Your application is smuggling in hidden vulnerabilities. Static analysis is like an airport x-ray scanner.

It checks every suitcase before the passenger boards the plane - but it does not look inside the suitcase once it’s opened mid-flight.

In statically typed languages like Go, Rust, or C#, this is mostly fine, all the contents of the suitcase are clearly labeled and checked before takeoff, and their shape can’t really change during the flight.

But in dynamic languages like Python, NodeJS, Ruby, and to some extent Java, people can pack whatever they want during the flight based on config, user input, or even environment behavior.

So the scanner, confident in its early inspection, completely misses what’s actually happening when it matters most - when you’re most vulnerable.

Reality Check: What the Industry Is Doing

In recent years, several companies have pushed static reachability analysis as a next-gen enhancement to traditional Software Composition Analysis (SCA).

The goal is simple and pragmatic:

“Let’s only alert developers on vulnerable functions that are actually reachable from their code.”

This helps reduce alert fatigue and narrows focus to actionable risks.

But there’s a problem:

These approaches still rely entirely on static understanding of the codebase.

And in dynamic environments, this visibility is forced to be limited.

Wait,  What’s a Static vs Dynamic Language?

Static Languages (work well with static analysis):

  • Go, Rust, C, C#, TypeScript (strict mode)
  • Variables and types are declared explicitly
  • Call paths are mostly visible and predictable
  • Very little code is generated or loaded at runtime

Dynamic Languages (hard for static analysis):

  • Python, JavaScript, Ruby, PHP, Node.js
  • Functions and modules can be created, swapped, or imported dynamically
  • Types are inferred at runtime, not declared ahead of time
  • Heavy use of reflection, configuration, and metaprogramming

Java - Somewhere in the Middle

Java looks static, but it supports reflection, dependency injection, and class loading — especially in large frameworks like Spring.

That means tools like static reachability analysis can miss calls that happen through annotations, runtime proxies, or config-based wiring.

So while Java isn’t as wild as Python or JS, it still has dangerous blind spots when analyzed statically.

Let’s Look at the Problem in Code

Example 1: Dynamic Function Execution (Python)

func_name = "do_something"          # Function name as a string
funcs = {
    "do_something": lambda: print("Doing something!"),
    "do_another": lambda: print("Doing another!")
}
funcs[func_name]()                  # Call the function dynamically

We can do this in every language, even the “static languages”:
In C++:

#include <iostream>
#include <unordered_map>
#include <functional>

int main() {
    std::string func_name = "do_something";

    std::unordered_map<std::string, std::function<void()>> funcs = {
        {"do_something", []() { std::cout << "Doing something!\n"; }},
        {"do_another", []() { std::cout << "Doing another!\n"; }}
    };

    funcs[func_name](); // Call the function dynamically
    return 0;
}

In Go:

package main

import (
    "fmt"
)

func main() {
    funcName := "do_something"

    funcs := map[string]func(){
        "do_something": func() { fmt.Println("Doing something!") },
        "do_another":   func() { fmt.Println("Doing another!") },
    }

    funcs[funcName]() // Call the function dynamically
}

In plain English:

  • A string (func_name) controls which function is called
  • The functions live in a dictionary
  • Nothing in this code explicitly calls do_something() or do_another()

Static analyzers can’t trace this behavior, the actual call is hidden behind data, not code. The value of the funcName variable will determine the executed code path. This value is only known during runtime. Static analyzers only scan code at rest, and thus are blind to this kind of behavior.

Example 2: eval() – Executing Code That Doesn’t Exist Yet (JavaScript)

const userInput = "2 + 2";
const result = eval(userInput);
console.log(result);  // 4

  • eval() runs the contents of the string like it’s code
  • Static analysis can’t inspect "2 + 2" because it’s not real code until runtime

Imagine that string came from a user or external API — the risk is invisible until it’s too late.

These languages support native eval without using any third-party libraries:

  • JavaScript
  • TypeScript
  • Python
  • Ruby
  • PHP
  • Node.js

Example 3: Dynamic Module Import (Python)

module_name = "math"
module = __import__(module_name)
result = module.sqrt(16)

  • The module is selected via string
  • The string could change based on config or user input
  • Static tools don’t know which module is involved — or what’s in it

These languages support dynamic module import without using any third-party libraries:

  • JavaScript
  • TypeScript
  • Python
  • Ruby
  • PHP
  • Node.js

Meta’s Pysa

In Meta’s own words, even Pysa — a purpose-built static analyzer for Python — can’t catch code execution vulnerabilities when modules are imported dynamically. For example, in this code:

importlib.import_module("os").system(request.GET["command"])

A remote code execution issue is hiding in plain sight. But because the os module is imported at runtime using importlib, Pysa doesn’t recognize the variable as os — and misses the entire vulnerability.

Why? Because Python allows dynamic importing, dynamic function dispatching, and monkey patching at any level. While static tools can be patched to detect very specific patterns, Meta admits the truth:

“There are endless pathological examples of flows of data that Pysa cannot detect.”

This isn’t a Pysa flaw — it’s a static analysis limitation. In highly dynamic languages, even dangerous behavior can look perfectly harmless to a static scanner. Only by observing real execution can you be sure of what’s actually running.

Example 4: Java Reflection

Class<?> clazz = Class.forName("com.app.UserManager");
Method method = clazz.getMethod("deleteUser", String.class);
method.invoke(clazz.newInstance(), "admin");

  • We load a class and method based on strings
  • The method call isn’t visible in the static call graph
  • If deleteUser() had a vulnerability? A static tool might not even see it’s being used

Why It Matters

Dynamic behavior is everywhere — especially in modern frameworks like:

  • Spring Boot / Hibernate (Java)
  • Django / Flask (Python)
  • Express / NestJS (Node.js)
  • Ruby on Rails, PHP

They rely on:

  • Config-driven logic
  • Runtime injection
  • Code generation
  • Reflection
  • Plugins

This breaks the static model. The static tool only sees:

“I don’t see a direct call, so I’ll assume it’s not used.”

Runtime Is the Only Real Visibility

Runtime-first security solutions (like Raven) observe what actually happens in the application:

  • Which libraries are loaded
  • Which functions are executed
  • Which vulnerabilities are truly reachable
  • Including all those “invisible” cases — reflection, dynamic imports, eval, etc.

Why Static Analysis Fails in Dynamic Languages – Summary Table

Problem Example Code / Pattern Why Static Fails
Dynamic function execution funcs[func_name]()
object(method)
Function name is a string; static analysis can’t resolve the actual call
Dynamic module loading __import__(\"math\")
require(variable)
Module name is unknown at analysis time
Reflection / introspection Class.forName(\"...\")
getattr(obj, name)
Methods/classes loaded by name are not directly visible
eval() / exec() eval(\"2 + 2\")
exec(user_input)
Code is generated at runtime; does not exist during static scan
Feature flags / config-based logic if config.enabled:
    load('feature')
Behavior changes per environment; sees all or none
Dependency injection Spring, NestJS, Flask DI Static analysis can’t see external or runtime-injected code
Code generation tools GraphQL Codegen, ORM models, Swagger clients Function behavior changes at runtime; still original in analysis

Best of both worlds

In Raven, we combine both static analysis and runtime analysis in a method that brings the best of both worlds. We use static analysis with our agentic AI approach to identify vulnerable functions and artifacts suggesting the vulnerable functions were called for each CVE we analyze. Then, during runtime, Raven sensor looks at runtime for those functions or artifacts to detect if a vulnerable path is executing in a customer’s code.

A static call graph vs the real execution trace. The static version has gaps (dotted “missing” link), whereas the runtime version shows all actual calls (including a dynamic call in green).

Summary

Static analysis is like scanning luggage at the airport. It can give a threat assessment of each individual item. However, dynamic languages are like making different combinations of those items that can happen mid-flight and are the real threat.

And while next-gen static SCA vendors are building better scanners, they’re still watching the gate while the real risks board mid-air.

If you want to know what’s really happening, you need runtime SCA.

Share this post
Yellow Lines

Get a Demo

Meeting Booked!
See you soon!
Until we meet, you might want to check out our blog
Oops! Something went wrong while submitting the form.
Ellipse
Security

7 Reasons Why Attackers Shifted Towards Cloud Applications

Attackers are increasingly shifting their focus from infrastructure to applications, exploiting vulnerabilities that traditional security measures cannot protect.
Read more
Security

The Critical Need for Cloud Runtime Application Security

While shift left strategies are essential for building secure applications, they are not sufficient on their own. Cloud runtime application security, or protect right, is crucial especially as attackers are increasingly shifting their focus to applications.
Read more
Security

What are CVE-Less Threats?

What CVE-less threats are, why they are becoming more prevalent, and how organizations can protect themselves against these insidious risks.
Read more
Yellow Lines