security-injection
Load when reviewing code that touches subprocess calls, eval/exec, template rendering, deserialization, or deep-merge of user-controlled objects.
$ npx -y skills add notque/vexjoy-agent --agent claude-codeHow it fires
How this agent gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
Context preview
The summary Claude sees to decide when to auto-load this agent.
Load when reviewing code that touches subprocess calls, eval/exec, template rendering, deserialization, or deep-merge of user-controlled objects.
Agent definition
security-injection.mdInjection and Code Execution Patterns
Load when reviewing code that touches subprocess calls, eval/exec, template rendering, deserialization, or deep-merge of user-controlled objects.
Untrusted input reaches a sink that executes code. Keep user data out of code-execution paths — use safe APIs, parameterized interfaces, and strict input validation.
---
Pass Arguments as Arrays to Subprocess Calls
Use array form with `shell=False`. Prevents shell metacharacter interpretation.
Correct Pattern
**Python:**
import subprocess
subprocess.run(["git", "clone", "--", user_repo_url], check=True)
subprocess.run(["convert", user_filename, "out.png"], shell=False, check=True)
**TypeScript/Node:**
import { execFile } from 'node:child_process';
execFile('git', ['clone', '--', userRepoUrl], (err, stdout) => {
if (err) throw err;
});**Go:**
cmd := exec.Command("git", "clone", "--", userRepoURL)
if err := cmd.Run(); err != nil {
return fmt.Errorf("clone failed: %w", err)
}Why This Matters
Shell injection: `subprocess.run(f"git clone {url}", shell=True)` lets attacker supply `; rm -rf /`.
**CVEs:** CVE-2021-22205 (GitLab ExifTool RCE), CVE-2024-27980 (Node.js BatBadBut).
Detection
rg -n 'shell=True' --type py
rg -n 'os\.system\(|os\.popen\(' --type py
rg -n "exec\(|execSync\(" --type ts | rg -v 'execFile'
rg -n 'exec\.Command\("sh"|exec\.Command\("bash"|exec\.Command\("/bin/sh"' --type go---
Use Safe Loaders for Deserialization
Use format-restricted loaders. For YAML: `safe_load`. For data exchange: JSON. Never use `pickle`, `marshal`, or `node-serialize` on untrusted input.
Correct Pattern
**Python (YAML):**
import yaml
config = yaml.safe_load(request.data)
**Python (data exchange):**
import json
data = json.loads(request.data)
**Python (ML models):**
from safetensors import safe_open
model = safe_open(uploaded_path, framework="pt")
**TypeScript:**
const data = JSON.parse(req.body);
import { z } from 'zod';
const Config = z.object({ name: z.string(), value: z.number() });
const validated = Config.parse(JSON.parse(req.body));Why This Matters
`pickle.loads` calls `__reduce__` which returns `(os.system, ("rm -rf /",))`. YAML default loader processes `!!python/object` tags. `node-serialize` calls `eval()`.
**CVEs:** CVE-2020-1747 (PyYAML FullLoader), CVE-2017-5941 (node-serialize), CVE-2021-44228 (Log4Shell), CVE-2022-22965 (Spring4Shell), Picklescan CVE-2025-1716.
Detection
rg -n 'pickle\.loads|cloudpickle\.loads|joblib\.load|dill\.loads|marshal\.loads' --type py
rg -n 'yaml\.load\(' --type py | rg -v 'SafeLoader|safe_load'
rg -n 'node-serialize|\.unserialize\(' --type ts --type js
rg -n 'ObjectInputStream|readObject\(\)' --type java---
Render Templates from Files, Not from User Input
Template engines compile from trusted file paths. User-controlled template source = code execution sink.
Correct Pattern
**Python (Flask/Jinja2):**
@app.route("/preview")
def preview():
return render_template("preview.html", body=request.args["body"])**Python (Jinja2 direct):**
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates"), autoescape=True)
tmpl = env.get_template("report.html")
output = tmpl.render(data=user_data)**TypeScript (Handlebars):**
import { readFileSync } from 'fs';
import Handlebars from 'handlebars';
const tmpl = Handlebars.compile(readFileSync('views/preview.hbs', 'utf8'));
res.send(tmpl({ user: req.user }));Why This Matters
SSTI: `{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}` executes commands.
**CVEs:** CVE-2019-10906 (Jinja2 sandbox escape), CVE-2016-10745 (Jinja2 bypass), CVE-2019-19919 (Handlebars prototype pollution RCE).
Detection
rg -n 'render_template_string\(' --type py
rg -n 'Template\(.*request|Template\(.*user|Template\(.*data' --type py
rg -n 'Handlebars\.compile\(req\.|\.compile\(req\.body' --type ts --type js
rg -n 'pug\.compile\(|ejs\.render\(' --type ts --type js---
Evaluate Expressions with Restricted Parsers
Use restricted parsers for user expression evaluation. Never pass user strings to `eval`, `exec`, `Function`, or `vm`.
Correct Pattern
**Python:**
import ast
result = ast.literal_eval(request.args["expr"])
from simpleeval import simple_eval
result = simple_eval(request.args["expr"])
**TypeScript:**
import { evaluate } from 'mathjs';
const result = evaluate(userExpr, { scope: {} });Why This Matters
`eval` executes arbitrary code. Node's `vm` module is not a security sandbox — `vm2` was abandoned after CVE-2023-37903 proved it unfixable.
**CVEs:** CVE-2025-55182 (Next.js React2Shell), CVE-2023-37903/CVE-2023-29017/CVE-2023-32314 (vm2 escapes).
Detection
rg -n 'eval\(|exec\(' --type py | rg -v 'ast\.literal_eval|# noqa'
rg -n 'eval\(|new Function\(|vm\.run' --type ts --type js
rg -n 'importlib\.import_module\(|__import__\(' --type py
rg -n 'eval\(|instance_eval|class_eval|send\(' --type ruby---
Validate Object Shape Before Deep Merge
Parse user objects through a schema validator before merging. Deep-merge allows prototype pollution: `{"__proto__": {"isAdmin": true}}`.
Correct Pattern
**TypeScript:**
import { z } from 'zod';
const Config = z.object({
theme: z.enum(['light', 'dark']).optional(),
locale: z.string().max(5).optional(),
});
const validated = Config.parse(req.body);
const merged = { ...defaults, ...validated };**When merge unavoidable:**
const config = Object.create(null);
for (const [key, value] of Object.entries(validated)) {
config[key] = value;
}Why This Matters
Prototype pollution modifies `Object.prototype`, affecting every object in the process. Handlebars + lodash chain: pollute `helperMissing` via `_.merge`, then template c
Read more
Injection and Code Execution Patterns
Load when reviewing code that touches subprocess calls, eval/exec, template rendering, deserialization, or deep-merge of user-controlled objects.
Untrusted input reaches a sink that executes code. Keep user data out of code-execution paths — use safe APIs, parameterized interfaces, and strict input validation.
---
Pass Arguments as Arrays to Subprocess Calls
Use array form with `shell=False`. Prevents shell metacharacter interpretation.
Correct Pattern
**Python:**
import subprocess subprocess.run(["git", "clone", "--", user_repo_url], check=True) subprocess.run(["convert", user_filename, "out.png"], shell=False, check=True)
**TypeScript/Node:**
import { execFile } from 'node:child_process';
execFile('git', ['clone', '--', userRepoUrl], (err, stdout) => {
if (err) throw err;
});**Go:**
cmd := exec.Command("git", "clone", "--", userRepoURL)
if err := cmd.Run(); err != nil {
return fmt.Errorf("clone failed: %w", err)
}Why This Matters
Shell injection: `subprocess.run(f"git clone {url}", shell=True)` lets attacker supply `; rm -rf /`.
**CVEs:** CVE-2021-22205 (GitLab ExifTool RCE), CVE-2024-27980 (Node.js BatBadBut).
Detection
rg -n 'shell=True' --type py
rg -n 'os\.system\(|os\.popen\(' --type py
rg -n "exec\(|execSync\(" --type ts | rg -v 'execFile'
rg -n 'exec\.Command\("sh"|exec\.Command\("bash"|exec\.Command\("/bin/sh"' --type go---
Use Safe Loaders for Deserialization
Use format-restricted loaders. For YAML: `safe_load`. For data exchange: JSON. Never use `pickle`, `marshal`, or `node-serialize` on untrusted input.
Correct Pattern
**Python (YAML):**
import yaml config = yaml.safe_load(request.data)
**Python (data exchange):**
import json data = json.loads(request.data)
**Python (ML models):**
from safetensors import safe_open model = safe_open(uploaded_path, framework="pt")
**TypeScript:**
const data = JSON.parse(req.body);
import { z } from 'zod';
const Config = z.object({ name: z.string(), value: z.number() });
const validated = Config.parse(JSON.parse(req.body));Why This Matters
`pickle.loads` calls `__reduce__` which returns `(os.system, ("rm -rf /",))`. YAML default loader processes `!!python/object` tags. `node-serialize` calls `eval()`.
**CVEs:** CVE-2020-1747 (PyYAML FullLoader), CVE-2017-5941 (node-serialize), CVE-2021-44228 (Log4Shell), CVE-2022-22965 (Spring4Shell), Picklescan CVE-2025-1716.
Detection
rg -n 'pickle\.loads|cloudpickle\.loads|joblib\.load|dill\.loads|marshal\.loads' --type py
rg -n 'yaml\.load\(' --type py | rg -v 'SafeLoader|safe_load'
rg -n 'node-serialize|\.unserialize\(' --type ts --type js
rg -n 'ObjectInputStream|readObject\(\)' --type java---
Render Templates from Files, Not from User Input
Template engines compile from trusted file paths. User-controlled template source = code execution sink.
Correct Pattern
**Python (Flask/Jinja2):**
@app.route("/preview")
def preview():
return render_template("preview.html", body=request.args["body"])**Python (Jinja2 direct):**
from jinja2 import Environment, FileSystemLoader
env = Environment(loader=FileSystemLoader("templates"), autoescape=True)
tmpl = env.get_template("report.html")
output = tmpl.render(data=user_data)**TypeScript (Handlebars):**
import { readFileSync } from 'fs';
import Handlebars from 'handlebars';
const tmpl = Handlebars.compile(readFileSync('views/preview.hbs', 'utf8'));
res.send(tmpl({ user: req.user }));Why This Matters
SSTI: `{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}` executes commands.
**CVEs:** CVE-2019-10906 (Jinja2 sandbox escape), CVE-2016-10745 (Jinja2 bypass), CVE-2019-19919 (Handlebars prototype pollution RCE).
Detection
rg -n 'render_template_string\(' --type py
rg -n 'Template\(.*request|Template\(.*user|Template\(.*data' --type py
rg -n 'Handlebars\.compile\(req\.|\.compile\(req\.body' --type ts --type js
rg -n 'pug\.compile\(|ejs\.render\(' --type ts --type js---
Evaluate Expressions with Restricted Parsers
Use restricted parsers for user expression evaluation. Never pass user strings to `eval`, `exec`, `Function`, or `vm`.
Correct Pattern
**Python:**
import ast result = ast.literal_eval(request.args["expr"]) from simpleeval import simple_eval result = simple_eval(request.args["expr"])
**TypeScript:**
import { evaluate } from 'mathjs';
const result = evaluate(userExpr, { scope: {} });Why This Matters
`eval` executes arbitrary code. Node's `vm` module is not a security sandbox — `vm2` was abandoned after CVE-2023-37903 proved it unfixable.
**CVEs:** CVE-2025-55182 (Next.js React2Shell), CVE-2023-37903/CVE-2023-29017/CVE-2023-32314 (vm2 escapes).
Detection
rg -n 'eval\(|exec\(' --type py | rg -v 'ast\.literal_eval|# noqa'
rg -n 'eval\(|new Function\(|vm\.run' --type ts --type js
rg -n 'importlib\.import_module\(|__import__\(' --type py
rg -n 'eval\(|instance_eval|class_eval|send\(' --type ruby---
Validate Object Shape Before Deep Merge
Parse user objects through a schema validator before merging. Deep-merge allows prototype pollution: `{"__proto__": {"isAdmin": true}}`.
Correct Pattern
**TypeScript:**
import { z } from 'zod';
const Config = z.object({
theme: z.enum(['light', 'dark']).optional(),
locale: z.string().max(5).optional(),
});
const validated = Config.parse(req.body);
const merged = { ...defaults, ...validated };**When merge unavoidable:**
const config = Object.create(null);
for (const [key, value] of Object.entries(validated)) {
config[key] = value;
}Why This Matters
Prototype pollution modifies `Object.prototype`, affecting every object in the process. Handlebars + lodash chain: pollute `helperMissing` via `_.merge`, then template c
Essays and writing behind this toolkit live at vexjoy.com. AI agents skip steps. "Looks correct" replaces running tests. "Trivial change" replaces verification.
Repo: notque/vexjoy-agent
Other agents on vexjoy-agent.
- ansible-automation-engineer
Ansible automation: playbooks, roles, collections, Molecule testing, Vault security.
Open agent - modules
**Scope**: Module selection patterns, builtin vs command/shell decisions, collection modules, and version-specific module changes **Version range**: ansible-core 2.14+ / Ansible Collections (community.general 7.0+) **Generated**: 2026-04-04 — verify against current Ansible
Open agent - testing
**Scope**: Molecule test scenarios, ansible-lint rules, idempotency validation, and check-mode patterns **Version range**: Molecule 6.0+ / ansible-lint 6.0+ / ansible-core 2.14+ **Generated**: 2026-04-04 — verify against current Molecule and ansible-lint documentation
Open agent - base-instructions
Universal operational rules injected by /do at agent dispatch. Domain-specific rules live in each agent's .md file.
Open agent - communication-patterns
**Scope**: Failure modes in agent output style — over-reporting, self-congratulation, verbose narration, and hedging. Covers what to detect and how to fix each. **Version range**: all versions **Generated**: 2026-05-11
Open agent - combat-effects-upgrade
Zero-dependency combat visual upgrades: CSS particle replacement, Framer Motion combat juice, CSS 3D card transforms.
Open agent

