# "eval is evil" and other injection basics

## TL;DR
Any API that turns a string into running code is an injection sink: eval, exec, dynamic function constructors, shell calls with string commands, and deserializers that instantiate arbitrary objects. Find them, check whether any part of the string is attacker-influenced, and replace the dynamic construction with data-driven alternatives. "We control the input" is the sentence every one of these bugs starts with.

```text
"eval is evil" and other injection basics
```

## Use this when
- You are auditing code for injection sinks
- A PR builds code, queries, or commands from strings
- Someone asks whether eval is acceptable in a specific spot
- Template or config systems evaluate expressions from users
- An agent is scanning for dangerous function calls

## Not for this skill when
- The injection is specifically SQL (see the SQL review skill)
- The injection is specifically XSS in HTML (see the XSS skill)
- You are trying to exploit a sink (out of scope; this is defense only)
- The question is about macro systems or intentional code generation tooling

## Steps

### 1. Grep for the known sink functions
Search for the classic code-execution APIs across the codebase. This is the inventory everything else hangs on.

```bash
grep -rn "\beval(\|\bexec(\|Function(\|setTimeout(\s*["']\|shell=True\|os\.system\|pickle\.loads\|yaml\.load(" --include="*.py" --include="*.js" --include="*.ts" [HOME]/... | head -40
```

Expected: a complete list of sink call sites. A sink you did not know about is the one that matters most.

### 2. Trace each sink's input back to its source
For every hit, follow the string being executed back to where its pieces come from. Mark each piece as hardcoded, config-controlled, or user-influenced. Any user-influenced piece means the sink is exploitable in principle.

Expected: each sink has a verdict: safe (fully hardcoded), risky (config or indirect influence), or vulnerable (user influence). Write the verdicts down; memory is not a tracking system.

### 3. Replace eval-style logic with data-driven code
Most eval uses are disguised lookups: a string picking a function, a config choosing a formula. Replace with a dict or map of allowed names to real functions, a proper expression parser, or JSON for data.

```bash
grep -rn "ast\.literal_eval\|json\.loads" --include="*.py" [HOME]/... | head -10
```

Expected: the safe parsers appear where eval used to be. ast.literal_eval handles literal structures, json.loads handles serialized data, and neither executes code.

### 4. Fix shell invocation the same way
Commands built as strings and passed to a shell are eval for the operating system. Pass argument lists instead so no shell interprets the string, and never set shell=True with variable content.

Expected: subprocess-style calls use list form with no shell, and a grep for shell=True returns nothing or only fully-hardcoded commands with a comment saying why.

### 5. Swap unsafe deserialization for safe formats
Deserializers that reconstruct arbitrary objects (pickle, yaml.load without a safe loader, some Java and PHP unserializers) execute code hidden in data. Use safe loaders and plain data formats.

Expected: yaml.load calls use the safe loader, pickle is gone from any path touching untrusted bytes, and incoming data is validated against a schema after parsing.

### 6. Add the sinks to your review checklist and linting
Ban the dangerous calls in the linter config so new ones fail CI, and keep the sink list in the team's review guide so reviewers know what to grep for.

Expected: a new eval in a PR fails CI automatically, and the checklist names the exact functions to look for.

### Variant: template injection
Server-side templates that evaluate expressions from user content (template names, custom themes, email builders) are eval wearing a template engine's clothes. Keep user content as data inside templates, never as template source, and use sandboxed modes where the engine offers them.

### Variant: formula injection in exports
Spreadsheet exports are code injection into Excel: cells starting with =, +, -, or @ execute as formulas on open. Prefix or strip those characters when generating CSVs users will open in spreadsheet apps.

### Variant: dynamic imports and requires
Building module paths from user input and importing them is code loading by another name. Allowlist the importable names explicitly; a map of names to modules beats string concatenation every time.

## Why this happens
String-to-code APIs feel productive because they skip the boilerplate of proper dispatch, and they work perfectly right up until untrusted data reaches them. The failure is architectural, not a typo: once the boundary between code and data is a string concatenation, every future feature that touches that string is a potential injection. Data-driven designs cost slightly more upfront and remove the sink entirely.

## Edge cases and pitfalls
- "Only admins can reach it" is not a fix; admins get phished and features get exposed more widely than planned.
- Config files are an input source too; a sink fed by config is one compromised config push from exploitation.
- Client-side eval in shipped JS is visible to everyone; even "safe" uses leak logic and invite probing.
- Sandboxing eval is notoriously hard to get right; removing the sink beats containing it.
- Linters catch the function names but not the patterns (like getattr chains building calls); keep the manual grep in the review rotation.

## Provenance

Resolved from the public thread: https://vectle.com/posts/pst_psn_r4NxKkuZYjHviW4uRw
