Safeguard
Application Security•

ASVS 5.0 V1.3.2: Avoid eval and Dynamic Code Execution

ASVS V1.3.2 says avoid eval and expression engines like SpEL. See safe replacements in Python, JavaScript and Java, plus tests and review tips.

Safeguard Security Team
5 min read

ASVS 5.0 V1.3.2 (Level 1) requires that your application avoids eval() and other dynamic code execution features, such as the Spring Expression Language (SpEL), and where there is truly no alternative, that any user input included is sanitized before it runs. The simplest path is to remove the feature and use data structures instead.

Why it matters

Dynamic evaluation turns a string into running code (CWE-95). If any part of that string comes from a user, the user can run code with the privileges of your application. Real incidents include Spring expression injection in framework features, eval of JSON in old front ends, and Python eval used to parse numbers or filters.

Sanitizing the string first is a weak defense. Language syntax is large, and attackers find constructs your filter did not expect. Removing the capability is the reliable fix.

How to implement it

Look at why eval was used and replace it with the matching safe tool.

Parsing values:

import ast, json

value = ast.literal_eval(text)     # only literals: numbers, strings, lists, dicts
data = json.loads(text)            # JSON, which is what you usually want

ast.literal_eval refuses function calls and names, so it cannot run code. Prefer json.loads for anything that crosses a trust boundary.

Dispatching on a name from the user:

// instead of: eval(req.query.action + '()')
const handlers = {
  refresh: () => refreshCache(),
  export: () => runExport(),
};
const fn = Object.hasOwn(handlers, req.query.action) ? handlers[req.query.action] : null;
if (!fn) return res.status(400).end();
fn();

Calculators, filters and rules engines: use a parser that builds an expression tree from a tiny grammar you control, or a purpose built library with no access to the host runtime. A filter like price > 10 and stock < 5 needs a handful of operators, and writing a safe parser for them is a smaller risk than a general evaluator.

Spring and expression languages: never pass request data into ExpressionParser.parseExpression. If you must evaluate expressions, use SimpleEvaluationContext rather than StandardEvaluationContext, which limits it to property access and blocks method calls and type references:

ExpressionParser parser = new SpelExpressionParser();
EvaluationContext ctx = SimpleEvaluationContext.forReadOnlyDataBinding().build();
Object v = parser.parseExpression("name").getValue(ctx, person);

Even with that, the expression text itself should come from your code or configuration, not from users.

Other forms of dynamic code to look for:

  • new Function(...) and setTimeout("string") in JavaScript.
  • exec, compile and pickle.loads on untrusted data in Python.
  • Runtime compilation APIs, scripting engines and ScriptEngine.eval in Java.
  • Template strings compiled at runtime from user text.
  • Deserialization of untrusted objects, which is a related risk of its own.

If you have no alternative, run the code in a separate sandboxed process or WebAssembly runtime with no network, a short time limit and a minimal set of capabilities, and treat its result as untrusted output.

How to test it

  1. Search for the dangerous functions listed above in your code and in your dependencies that you configure.
  2. For each hit, trace whether any value that reaches it can come from a request, a file, a queue message or a database field that users edit.
  3. Test dynamically by sending expression syntax: ${7*7}, #{7*7}, __import__('os').system('id'), process.exit(), and T(java.lang.Runtime). A result of 49, a crash or any command output means evaluation is happening.

A static analysis scan can flag these calls across the whole repository and show the data path, so you can sort real risk from dead code. The demo below shows the idea.

Click through it at your own pace, or open it full screen.

Reachability analysis helps for the dependency side. If a library in your tree has a known code injection issue, checking whether your code ever calls the vulnerable function tells you whether it is urgent. You can start with the free scan at gold.safeguard.sh/scan.

Common mistakes

  • Using eval to read JSON, numbers or dates.
  • Building a blocklist of words like import and exec.
  • Trusting configuration files that users can upload.
  • Using SpEL with the full evaluation context because it was the default.
  • Assuming that an internal admin screen needs no protection. Admin accounts get compromised too.

Try it yourself

Spend half an hour on an inventory. For each service, list every place dynamic evaluation appears, using a search such as grep -rnE "eval\(|new Function|ScriptEngine|parseExpression|exec\(" src/. Put the results into a table with three columns: location, source of the evaluated text, and the planned replacement.

You will usually find three groups. Some are dead code that can be deleted. Some evaluate constants and can be rewritten as plain code. A few evaluate user influenced text, and those go to the top of the queue.

For the last group, write the replacement as a small allowlisted interpreter. A filter language with fields, comparison operators and and / or fits in about fifty lines, and because you control the grammar, nothing outside it can run. Add tests with the probe strings from the testing section and keep them in your suite.

Quick check

What replaces eval when the user picks an action by name?

A lookup table of allowed handlers. The name is only a key, and unknown keys are rejected.

Which SpEL context is safer?

SimpleEvaluationContext, because it blocks method invocation and type references. It still should not receive user written expressions.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.