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
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.

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.